From 7daaac2bc350e2907554a01eb7eb1286247e5832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 9 Oct 2024 21:44:42 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20new=20tutorial=20for=20SQL=20?= =?UTF-8?q?databases=20with=20SQLModel=20(#12285)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/em/docs/advanced/testing-database.md | 101 -- docs/em/docs/how-to/sql-databases-peewee.md | 576 ------------ docs/en/docs/advanced/testing-database.md | 111 --- .../docs/how-to/async-sql-encode-databases.md | 201 ---- .../docs/how-to/nosql-databases-couchbase.md | 178 ---- docs/en/docs/how-to/sql-databases-peewee.md | 594 ------------ docs/en/docs/how-to/testing-database.md | 7 + .../img/tutorial/sql-databases/image01.png | Bin 78949 -> 69755 bytes .../img/tutorial/sql-databases/image02.png | Bin 83482 -> 69197 bytes docs/en/docs/tutorial/sql-databases.md | 888 ++++-------------- docs/en/mkdocs.yml | 9 +- docs/zh/docs/advanced/testing-database.md | 101 -- docs_src/async_sql_databases/tutorial001.py | 65 -- docs_src/nosql_databases/tutorial001.py | 53 -- docs_src/sql_databases/sql_app/__init__.py | 0 docs_src/sql_databases/sql_app/alt_main.py | 62 -- docs_src/sql_databases/sql_app/crud.py | 36 - docs_src/sql_databases/sql_app/database.py | 13 - docs_src/sql_databases/sql_app/main.py | 55 -- docs_src/sql_databases/sql_app/models.py | 26 - docs_src/sql_databases/sql_app/schemas.py | 37 - .../sql_databases/sql_app/tests/__init__.py | 0 .../sql_app/tests/test_sql_app.py | 50 - .../sql_databases/sql_app_py310/__init__.py | 0 .../sql_databases/sql_app_py310/alt_main.py | 60 -- docs_src/sql_databases/sql_app_py310/crud.py | 36 - .../sql_databases/sql_app_py310/database.py | 13 - docs_src/sql_databases/sql_app_py310/main.py | 53 -- .../sql_databases/sql_app_py310/models.py | 26 - .../sql_databases/sql_app_py310/schemas.py | 35 - .../sql_app_py310/tests/__init__.py | 0 .../sql_app_py310/tests/test_sql_app.py | 47 - .../sql_databases/sql_app_py39/__init__.py | 0 .../sql_databases/sql_app_py39/alt_main.py | 60 -- docs_src/sql_databases/sql_app_py39/crud.py | 36 - .../sql_databases/sql_app_py39/database.py | 13 - docs_src/sql_databases/sql_app_py39/main.py | 53 -- docs_src/sql_databases/sql_app_py39/models.py | 26 - .../sql_databases/sql_app_py39/schemas.py | 37 - .../sql_app_py39/tests/__init__.py | 0 .../sql_app_py39/tests/test_sql_app.py | 47 - docs_src/sql_databases/tutorial001.py | 71 ++ docs_src/sql_databases/tutorial001_an.py | 74 ++ .../sql_databases/tutorial001_an_py310.py | 73 ++ docs_src/sql_databases/tutorial001_an_py39.py | 73 ++ docs_src/sql_databases/tutorial001_py310.py | 69 ++ docs_src/sql_databases/tutorial001_py39.py | 71 ++ docs_src/sql_databases/tutorial002.py | 104 ++ docs_src/sql_databases/tutorial002_an.py | 104 ++ .../sql_databases/tutorial002_an_py310.py | 103 ++ docs_src/sql_databases/tutorial002_an_py39.py | 103 ++ docs_src/sql_databases/tutorial002_py310.py | 102 ++ docs_src/sql_databases/tutorial002_py39.py | 104 ++ requirements-docs.txt | 2 +- requirements-tests.txt | 5 +- scripts/playwright/sql_databases/image01.py | 37 + scripts/playwright/sql_databases/image02.py | 37 + .../test_async_sql_databases/__init__.py | 0 .../test_tutorial001.py | 146 --- .../test_sql_databases/test_sql_databases.py | 419 --------- .../test_sql_databases_middleware.py | 421 --------- .../test_sql_databases_middleware_py310.py | 433 --------- .../test_sql_databases_middleware_py39.py | 433 --------- .../test_sql_databases_py310.py | 432 --------- .../test_sql_databases_py39.py | 432 --------- .../test_testing_databases.py | 27 - .../test_testing_databases_py310.py | 28 - .../test_testing_databases_py39.py | 28 - .../test_sql_databases/test_tutorial001.py | 373 ++++++++ .../test_sql_databases/test_tutorial002.py | 481 ++++++++++ 70 files changed, 2154 insertions(+), 6336 deletions(-) delete mode 100644 docs/em/docs/advanced/testing-database.md delete mode 100644 docs/em/docs/how-to/sql-databases-peewee.md delete mode 100644 docs/en/docs/advanced/testing-database.md delete mode 100644 docs/en/docs/how-to/async-sql-encode-databases.md delete mode 100644 docs/en/docs/how-to/nosql-databases-couchbase.md delete mode 100644 docs/en/docs/how-to/sql-databases-peewee.md create mode 100644 docs/en/docs/how-to/testing-database.md delete mode 100644 docs/zh/docs/advanced/testing-database.md delete mode 100644 docs_src/async_sql_databases/tutorial001.py delete mode 100644 docs_src/nosql_databases/tutorial001.py delete mode 100644 docs_src/sql_databases/sql_app/__init__.py delete mode 100644 docs_src/sql_databases/sql_app/alt_main.py delete mode 100644 docs_src/sql_databases/sql_app/crud.py delete mode 100644 docs_src/sql_databases/sql_app/database.py delete mode 100644 docs_src/sql_databases/sql_app/main.py delete mode 100644 docs_src/sql_databases/sql_app/models.py delete mode 100644 docs_src/sql_databases/sql_app/schemas.py delete mode 100644 docs_src/sql_databases/sql_app/tests/__init__.py delete mode 100644 docs_src/sql_databases/sql_app/tests/test_sql_app.py delete mode 100644 docs_src/sql_databases/sql_app_py310/__init__.py delete mode 100644 docs_src/sql_databases/sql_app_py310/alt_main.py delete mode 100644 docs_src/sql_databases/sql_app_py310/crud.py delete mode 100644 docs_src/sql_databases/sql_app_py310/database.py delete mode 100644 docs_src/sql_databases/sql_app_py310/main.py delete mode 100644 docs_src/sql_databases/sql_app_py310/models.py delete mode 100644 docs_src/sql_databases/sql_app_py310/schemas.py delete mode 100644 docs_src/sql_databases/sql_app_py310/tests/__init__.py delete mode 100644 docs_src/sql_databases/sql_app_py310/tests/test_sql_app.py delete mode 100644 docs_src/sql_databases/sql_app_py39/__init__.py delete mode 100644 docs_src/sql_databases/sql_app_py39/alt_main.py delete mode 100644 docs_src/sql_databases/sql_app_py39/crud.py delete mode 100644 docs_src/sql_databases/sql_app_py39/database.py delete mode 100644 docs_src/sql_databases/sql_app_py39/main.py delete mode 100644 docs_src/sql_databases/sql_app_py39/models.py delete mode 100644 docs_src/sql_databases/sql_app_py39/schemas.py delete mode 100644 docs_src/sql_databases/sql_app_py39/tests/__init__.py delete mode 100644 docs_src/sql_databases/sql_app_py39/tests/test_sql_app.py create mode 100644 docs_src/sql_databases/tutorial001.py create mode 100644 docs_src/sql_databases/tutorial001_an.py create mode 100644 docs_src/sql_databases/tutorial001_an_py310.py create mode 100644 docs_src/sql_databases/tutorial001_an_py39.py create mode 100644 docs_src/sql_databases/tutorial001_py310.py create mode 100644 docs_src/sql_databases/tutorial001_py39.py create mode 100644 docs_src/sql_databases/tutorial002.py create mode 100644 docs_src/sql_databases/tutorial002_an.py create mode 100644 docs_src/sql_databases/tutorial002_an_py310.py create mode 100644 docs_src/sql_databases/tutorial002_an_py39.py create mode 100644 docs_src/sql_databases/tutorial002_py310.py create mode 100644 docs_src/sql_databases/tutorial002_py39.py create mode 100644 scripts/playwright/sql_databases/image01.py create mode 100644 scripts/playwright/sql_databases/image02.py delete mode 100644 tests/test_tutorial/test_async_sql_databases/__init__.py delete mode 100644 tests/test_tutorial/test_async_sql_databases/test_tutorial001.py delete mode 100644 tests/test_tutorial/test_sql_databases/test_sql_databases.py delete mode 100644 tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py delete mode 100644 tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py delete mode 100644 tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py delete mode 100644 tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py delete mode 100644 tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py delete mode 100644 tests/test_tutorial/test_sql_databases/test_testing_databases.py delete mode 100644 tests/test_tutorial/test_sql_databases/test_testing_databases_py310.py delete mode 100644 tests/test_tutorial/test_sql_databases/test_testing_databases_py39.py create mode 100644 tests/test_tutorial/test_sql_databases/test_tutorial001.py create mode 100644 tests/test_tutorial/test_sql_databases/test_tutorial002.py diff --git a/docs/em/docs/advanced/testing-database.md b/docs/em/docs/advanced/testing-database.md deleted file mode 100644 index 71b29f9b7..000000000 --- a/docs/em/docs/advanced/testing-database.md +++ /dev/null @@ -1,101 +0,0 @@ -# ๐Ÿ”ฌ ๐Ÿ’ฝ - -๐Ÿ‘† ๐Ÿ’ช โš™๏ธ ๐ŸŽ ๐Ÿ”— ๐Ÿ” โšช๏ธโžก๏ธ [๐Ÿ”ฌ ๐Ÿ”— โฎ๏ธ ๐Ÿ”](testing-dependencies.md){.internal-link target=_blank} ๐Ÿ“‰ ๐Ÿ’ฝ ๐Ÿ”ฌ. - -๐Ÿ‘† ๐Ÿ’ช ๐Ÿ’š โš’ ๐Ÿ†™ ๐ŸŽ ๐Ÿ’ฝ ๐Ÿ”ฌ, ๐Ÿ’พ ๐Ÿ’ฝ โฎ๏ธ ๐Ÿ’ฏ, ๐Ÿค-๐Ÿฅง โšซ๏ธ โฎ๏ธ ๐Ÿ”ฌ ๐Ÿ’ฝ, โ™’๏ธ. - -๐Ÿ‘‘ ๐Ÿ’ญ โšซ๏ธโ” ๐ŸŽ ๐Ÿ‘† ๐Ÿ‘€ ๐Ÿ‘ˆ โฎ๏ธ ๐Ÿ“ƒ. - -## ๐Ÿšฎ ๐Ÿ’ฏ ๐Ÿ—„ ๐Ÿ“ฑ - -โžก๏ธ โ„น ๐Ÿ–ผ โšช๏ธโžก๏ธ [๐Ÿ—„ (๐Ÿ”—) ๐Ÿ’ฝ](../tutorial/sql-databases.md){.internal-link target=_blank} โš™๏ธ ๐Ÿ”ฌ ๐Ÿ’ฝ. - -๐ŸŒ ๐Ÿ“ฑ ๐Ÿ“Ÿ ๐ŸŽ, ๐Ÿ‘† ๐Ÿ’ช ๐Ÿšถ ๐Ÿ”™ ๐Ÿ‘ˆ ๐Ÿ“ƒ โœ… โ” โšซ๏ธ. - -๐Ÿ•ด ๐Ÿ”€ ๐Ÿ“ฅ ๐Ÿ†• ๐Ÿ”ฌ ๐Ÿ“. - -๐Ÿ‘† ๐Ÿ˜ ๐Ÿ”— `get_db()` ๐Ÿ”œ ๐Ÿ“จ ๐Ÿ’ฝ ๐ŸŽ‰. - -๐Ÿ’ฏ, ๐Ÿ‘† ๐Ÿ’ช โš™๏ธ ๐Ÿ”— ๐Ÿ” ๐Ÿ“จ ๐Ÿ‘† *๐Ÿ›ƒ* ๐Ÿ’ฝ ๐ŸŽ‰ โ†ฉ๏ธ 1๏ธโƒฃ ๐Ÿ‘ˆ ๐Ÿ”œ โš™๏ธ ๐Ÿ›Ž. - -๐Ÿ‘‰ ๐Ÿ–ผ ๐Ÿ‘ฅ ๐Ÿ”œ โœ ๐Ÿ• ๐Ÿ’ฝ ๐Ÿ•ด ๐Ÿ’ฏ. - -## ๐Ÿ“ ๐Ÿ“Š - -๐Ÿ‘ฅ โœ ๐Ÿ†• ๐Ÿ“ `sql_app/tests/test_sql_app.py`. - -๐Ÿ†• ๐Ÿ“ ๐Ÿ“Š ๐Ÿ‘€ ๐Ÿ’–: - -``` hl_lines="9-11" -. -โ””โ”€โ”€ sql_app - โ”œโ”€โ”€ __init__.py - โ”œโ”€โ”€ crud.py - โ”œโ”€โ”€ database.py - โ”œโ”€โ”€ main.py - โ”œโ”€โ”€ models.py - โ”œโ”€โ”€ schemas.py - โ””โ”€โ”€ tests - โ”œโ”€โ”€ __init__.py - โ””โ”€โ”€ test_sql_app.py -``` - -## โœ ๐Ÿ†• ๐Ÿ’ฝ ๐ŸŽ‰ - -๐Ÿฅ‡, ๐Ÿ‘ฅ โœ ๐Ÿ†• ๐Ÿ’ฝ ๐ŸŽ‰ โฎ๏ธ ๐Ÿ†• ๐Ÿ’ฝ. - -๐Ÿ’ฏ ๐Ÿ‘ฅ ๐Ÿ”œ โš™๏ธ ๐Ÿ“ `test.db` โ†ฉ๏ธ `sql_app.db`. - -โœ‹๏ธ ๐ŸŽ‚ ๐ŸŽ‰ ๐Ÿ“Ÿ ๐ŸŒ… โš–๏ธ ๐ŸŒ˜ ๐ŸŽ, ๐Ÿ‘ฅ ๐Ÿ“ โšซ๏ธ. - -```Python hl_lines="8-13" -{!../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} -``` - -/// tip - -๐Ÿ‘† ๐Ÿ’ช ๐Ÿ“‰ โŽ ๐Ÿ‘ˆ ๐Ÿ“Ÿ ๐Ÿšฎ โšซ๏ธ ๐Ÿ”ข & โš™๏ธ โšซ๏ธ โšช๏ธโžก๏ธ ๐Ÿ‘ฏโ€โ™‚๏ธ `database.py` & `tests/test_sql_app.py`. - -๐Ÿฆ & ๐ŸŽฏ ๐Ÿ”› ๐ŸŽฏ ๐Ÿ”ฌ ๐Ÿ“Ÿ, ๐Ÿ‘ฅ ๐Ÿ–จ โšซ๏ธ. - -/// - -## โœ ๐Ÿ’ฝ - -โ†ฉ๏ธ ๐Ÿ”œ ๐Ÿ‘ฅ ๐Ÿ”œ โš™๏ธ ๐Ÿ†• ๐Ÿ’ฝ ๐Ÿ†• ๐Ÿ“, ๐Ÿ‘ฅ ๐Ÿ’ช โš’ ๐Ÿ’ญ ๐Ÿ‘ฅ โœ ๐Ÿ’ฝ โฎ๏ธ: - -```Python -Base.metadata.create_all(bind=engine) -``` - -๐Ÿ‘ˆ ๐Ÿ›Ž ๐Ÿค™ `main.py`, โœ‹๏ธ โธ `main.py` โš™๏ธ ๐Ÿ’ฝ ๐Ÿ“ `sql_app.db`, & ๐Ÿ‘ฅ ๐Ÿ’ช โš’ ๐Ÿ’ญ ๐Ÿ‘ฅ โœ `test.db` ๐Ÿ’ฏ. - -๐Ÿ‘ฅ ๐Ÿšฎ ๐Ÿ‘ˆ โธ ๐Ÿ“ฅ, โฎ๏ธ ๐Ÿ†• ๐Ÿ“. - -```Python hl_lines="16" -{!../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} -``` - -## ๐Ÿ”— ๐Ÿ” - -๐Ÿ”œ ๐Ÿ‘ฅ โœ ๐Ÿ”— ๐Ÿ” & ๐Ÿšฎ โšซ๏ธ ๐Ÿ” ๐Ÿ‘† ๐Ÿ“ฑ. - -```Python hl_lines="19-24 27" -{!../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} -``` - -/// tip - -๐Ÿ“Ÿ `override_get_db()` ๐ŸŒ– โšซ๏ธโ” ๐ŸŽ `get_db()`, โœ‹๏ธ `override_get_db()` ๐Ÿ‘ฅ โš™๏ธ `TestingSessionLocal` ๐Ÿ”ฌ ๐Ÿ’ฝ โ†ฉ๏ธ. - -/// - -## ๐Ÿ’ฏ ๐Ÿ“ฑ - -โคด๏ธ ๐Ÿ‘ฅ ๐Ÿ’ช ๐Ÿ’ฏ ๐Ÿ“ฑ ๐Ÿ›Ž. - -```Python hl_lines="32-47" -{!../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} -``` - -& ๐ŸŒ ๐Ÿ› ๏ธ ๐Ÿ‘ฅ โš’ ๐Ÿ’ฝ โฎ๏ธ ๐Ÿ’ฏ ๐Ÿ”œ `test.db` ๐Ÿ’ฝ โ†ฉ๏ธ ๐Ÿ‘‘ `sql_app.db`. diff --git a/docs/em/docs/how-to/sql-databases-peewee.md b/docs/em/docs/how-to/sql-databases-peewee.md deleted file mode 100644 index d25b77894..000000000 --- a/docs/em/docs/how-to/sql-databases-peewee.md +++ /dev/null @@ -1,576 +0,0 @@ -# ๐Ÿ—„ (๐Ÿ”—) ๐Ÿ’ฝ โฎ๏ธ ๐Ÿ’ - -/// warning - -๐Ÿšฅ ๐Ÿ‘† โ–ถ๏ธ, ๐Ÿ”ฐ [๐Ÿ—„ (๐Ÿ”—) ๐Ÿ’ฝ](../tutorial/sql-databases.md){.internal-link target=_blank} ๐Ÿ‘ˆ โš™๏ธ ๐Ÿ‡ธ๐Ÿ‡ฒ ๐Ÿ”œ ๐Ÿฅƒ. - -๐Ÿ’ญ ๐Ÿ†“ ๐Ÿšถ ๐Ÿ‘‰. - -/// - -๐Ÿšฅ ๐Ÿ‘† โ–ถ๏ธ ๐Ÿ— โšช๏ธโžก๏ธ ๐Ÿ–Œ, ๐Ÿ‘† ๐ŸŽฒ ๐Ÿ‘ป ๐Ÿ“† โฎ๏ธ ๐Ÿ‡ธ๐Ÿ‡ฒ ๐Ÿœ ([๐Ÿ—„ (๐Ÿ”—) ๐Ÿ’ฝ](../tutorial/sql-databases.md){.internal-link target=_blank}), โš–๏ธ ๐Ÿ™† ๐ŸŽ ๐Ÿ” ๐Ÿœ. - -๐Ÿšฅ ๐Ÿ‘† โช โœ”๏ธ ๐Ÿ“Ÿ ๐Ÿงข ๐Ÿ‘ˆ โš™๏ธ ๐Ÿ’ ๐Ÿœ, ๐Ÿ‘† ๐Ÿ’ช โœ… ๐Ÿ“ฅ โ” โš™๏ธ โšซ๏ธ โฎ๏ธ **FastAPI**. - -/// warning | "๐Ÿ 3๏ธโƒฃ.7๏ธโƒฃ โž• โœ”" - -๐Ÿ‘† ๐Ÿ”œ ๐Ÿ’ช ๐Ÿ 3๏ธโƒฃ.7๏ธโƒฃ โš–๏ธ ๐Ÿ”› ๐Ÿ”’ โš™๏ธ ๐Ÿ’ โฎ๏ธ FastAPI. - -/// - -## ๐Ÿ’ ๐Ÿ” - -๐Ÿ’ ๐Ÿšซ ๐Ÿ”ง ๐Ÿ” ๐Ÿ› ๏ธ, โš–๏ธ โฎ๏ธ ๐Ÿ‘ซ ๐Ÿคฏ. - -๐Ÿ’ โœ”๏ธ ๐Ÿ‹๏ธ ๐Ÿ”‘ ๐Ÿ”ƒ ๐Ÿšฎ ๐Ÿ”ข & ๐Ÿ”ƒ โ” โšซ๏ธ ๐Ÿ”œ โš™๏ธ. - -๐Ÿšฅ ๐Ÿ‘† ๐Ÿ› ๏ธ ๐Ÿˆธ โฎ๏ธ ๐Ÿ— ๐Ÿšซ-๐Ÿ” ๐Ÿ› ๏ธ, & ๐Ÿ’ช ๐Ÿ‘ท โฎ๏ธ ๐ŸŒ ๐Ÿšฎ ๐Ÿ”ข, **โšซ๏ธ ๐Ÿ’ช ๐Ÿ‘‘ ๐Ÿงฐ**. - -โœ‹๏ธ ๐Ÿšฅ ๐Ÿ‘† ๐Ÿ’ช ๐Ÿ”€ ๐Ÿ”ข, ๐Ÿ•โ€๐Ÿฆบ ๐ŸŒ– ๐ŸŒ˜ 1๏ธโƒฃ ๐Ÿ” ๐Ÿ’ฝ, ๐Ÿ‘ท โฎ๏ธ ๐Ÿ” ๐Ÿ› ๏ธ (๐Ÿ’– FastAPI), โ™’๏ธ, ๐Ÿ‘† ๐Ÿ”œ ๐Ÿ’ช ๐Ÿšฎ ๐Ÿ— โž• ๐Ÿ“Ÿ ๐Ÿ” ๐Ÿ‘ˆ ๐Ÿ”ข. - -๐Ÿ‘, โšซ๏ธ ๐Ÿ’ช โšซ๏ธ, & ๐Ÿ“ฅ ๐Ÿ‘† ๐Ÿ”œ ๐Ÿ‘€ โšซ๏ธโ” โšซ๏ธโ” ๐Ÿ“Ÿ ๐Ÿ‘† โœ”๏ธ ๐Ÿšฎ ๐Ÿ’ช โš™๏ธ ๐Ÿ’ โฎ๏ธ FastAPI. - -/// note | "๐Ÿ“ก โ„น" - -๐Ÿ‘† ๐Ÿ’ช โœ ๐ŸŒ… ๐Ÿ”ƒ ๐Ÿ’ ๐Ÿง ๐Ÿ”ƒ ๐Ÿ” ๐Ÿ ๐Ÿฉบ, โ”, ๐Ÿ‡ต๐Ÿ‡ท. - -/// - -## ๐ŸŽ ๐Ÿ“ฑ - -๐Ÿ‘ฅ ๐Ÿ”œ โœ ๐ŸŽ ๐Ÿˆธ ๐Ÿ‡ธ๐Ÿ‡ฒ ๐Ÿ”ฐ ([๐Ÿ—„ (๐Ÿ”—) ๐Ÿ’ฝ](../tutorial/sql-databases.md){.internal-link target=_blank}). - -๐ŸŒ… ๐Ÿ“Ÿ ๐Ÿค™ ๐ŸŽ. - -, ๐Ÿ‘ฅ ๐Ÿ”œ ๐ŸŽฏ ๐Ÿ•ด ๐Ÿ”› ๐Ÿ”บ. - -## ๐Ÿ“ ๐Ÿ“Š - -โžก๏ธ ๐Ÿ’ฌ ๐Ÿ‘† โœ”๏ธ ๐Ÿ“ ๐Ÿ“› `my_super_project` ๐Ÿ‘ˆ ๐Ÿ”Œ ๐ŸŽง-๐Ÿ“ ๐Ÿค™ `sql_app` โฎ๏ธ ๐Ÿ“Š ๐Ÿ’– ๐Ÿ‘‰: - -``` -. -โ””โ”€โ”€ sql_app - โ”œโ”€โ”€ __init__.py - โ”œโ”€โ”€ crud.py - โ”œโ”€โ”€ database.py - โ”œโ”€โ”€ main.py - โ””โ”€โ”€ schemas.py -``` - -๐Ÿ‘‰ ๐ŸŒ– ๐ŸŽ ๐Ÿ“Š ๐Ÿ‘ฅ โœ”๏ธ ๐Ÿ‡ธ๐Ÿ‡ฒ ๐Ÿ”ฐ. - -๐Ÿ”œ โžก๏ธ ๐Ÿ‘€ โšซ๏ธโ” ๐Ÿ”  ๐Ÿ“/๐Ÿ•น ๐Ÿ”จ. - -## โœ ๐Ÿ’ ๐Ÿ• - -โžก๏ธ ๐Ÿ”— ๐Ÿ“ `sql_app/database.py`. - -### ๐Ÿฉ ๐Ÿ’ ๐Ÿ“Ÿ - -โžก๏ธ ๐Ÿฅ‡ โœ… ๐ŸŒ ๐Ÿ˜ ๐Ÿ’ ๐Ÿ“Ÿ, โœ ๐Ÿ’ ๐Ÿ’ฝ: - -```Python hl_lines="3 5 22" -{!../../docs_src/sql_databases_peewee/sql_app/database.py!} -``` - -/// tip - -โœ”๏ธ ๐Ÿคฏ ๐Ÿ‘ˆ ๐Ÿšฅ ๐Ÿ‘† ๐Ÿ’š โš™๏ธ ๐ŸŽ ๐Ÿ’ฝ, ๐Ÿ’– โœณ, ๐Ÿ‘† ๐Ÿšซ ๐Ÿšซ ๐Ÿ”€ ๐ŸŽป. ๐Ÿ‘† ๐Ÿ”œ ๐Ÿ’ช โš™๏ธ ๐ŸŽ ๐Ÿ’ ๐Ÿ’ฝ ๐ŸŽ“. - -/// - -#### ๐Ÿ—’ - -โŒ: - -```Python -check_same_thread=False -``` - -๐ŸŒ“ 1๏ธโƒฃ ๐Ÿ‡ธ๐Ÿ‡ฒ ๐Ÿ”ฐ: - -```Python -connect_args={"check_same_thread": False} -``` - -...โšซ๏ธ ๐Ÿ’ช ๐Ÿ•ด `SQLite`. - -/// info | "๐Ÿ“ก โ„น" - -โšซ๏ธโ” ๐ŸŽ ๐Ÿ“ก โ„น [๐Ÿ—„ (๐Ÿ”—) ๐Ÿ’ฝ](../tutorial/sql-databases.md#_7){.internal-link target=_blank} โœ”. - -/// - -### โš’ ๐Ÿ’ ๐Ÿ”-๐Ÿ”— `PeeweeConnectionState` - -๐Ÿ‘‘ โ” โฎ๏ธ ๐Ÿ’ & FastAPI ๐Ÿ‘ˆ ๐Ÿ’ โš“๏ธ ๐Ÿ™‡ ๐Ÿ”› ๐Ÿ `threading.local`, & โšซ๏ธ ๐Ÿšซ โœ”๏ธ ๐ŸŽฏ ๐ŸŒŒ ๐Ÿ” โšซ๏ธ โš–๏ธ โžก๏ธ ๐Ÿ‘† ๐Ÿต ๐Ÿ”—/๐ŸŽ‰ ๐Ÿ”— (๐Ÿ”จ ๐Ÿ‡ธ๐Ÿ‡ฒ ๐Ÿ”ฐ). - -& `threading.local` ๐Ÿšซ ๐Ÿ”— โฎ๏ธ ๐Ÿ†• ๐Ÿ” โš’ ๐Ÿ› ๐Ÿ. - -/// note | "๐Ÿ“ก โ„น" - -`threading.local` โš™๏ธ โœ”๏ธ "๐ŸŽฑ" ๐Ÿ”ข ๐Ÿ‘ˆ โœ”๏ธ ๐ŸŽ ๐Ÿ’ฒ ๐Ÿ”  ๐Ÿงต. - -๐Ÿ‘‰ โš  ๐Ÿ— ๐Ÿ› ๏ธ ๐Ÿ— โœ”๏ธ 1๏ธโƒฃ ๐Ÿ‘ ๐Ÿงต ๐Ÿ“ ๐Ÿ“จ, ๐Ÿ™…โ€โ™‚ ๐ŸŒ–, ๐Ÿ™…โ€โ™‚ ๐ŸŒ˜. - -โš™๏ธ ๐Ÿ‘‰, ๐Ÿ”  ๐Ÿ“จ ๐Ÿ”œ โœ”๏ธ ๐Ÿšฎ ๐Ÿ‘ ๐Ÿ’ฝ ๐Ÿ”—/๐ŸŽ‰, โ” โ˜‘ ๐Ÿ ๐Ÿฅ…. - -โœ‹๏ธ FastAPI, โš™๏ธ ๐Ÿ†• ๐Ÿ” โš’, ๐Ÿ’ช ๐Ÿต ๐ŸŒ… ๐ŸŒ˜ 1๏ธโƒฃ ๐Ÿ“จ ๐Ÿ”› ๐ŸŽ ๐Ÿงต. & ๐ŸŽ ๐Ÿ•ฐ, ๐Ÿ‘ ๐Ÿ“จ, โšซ๏ธ ๐Ÿ’ช ๐Ÿƒ ๐Ÿ’— ๐Ÿ‘œ ๐ŸŽ ๐Ÿงต (๐Ÿงต), โš“๏ธ ๐Ÿ”› ๐Ÿšฅ ๐Ÿ‘† โš™๏ธ `async def` โš–๏ธ ๐Ÿ˜ `def`. ๐Ÿ‘‰ โšซ๏ธโ” ๐Ÿค ๐ŸŒ ๐ŸŽญ ๐Ÿ“ˆ FastAPI. - -/// - -โœ‹๏ธ ๐Ÿ 3๏ธโƒฃ.7๏ธโƒฃ & ๐Ÿ”› ๐Ÿšš ๐ŸŒ– ๐Ÿง ๐ŸŽ› `threading.local`, ๐Ÿ‘ˆ ๐Ÿ’ช โš™๏ธ ๐Ÿฅ‰ ๐ŸŒโ” `threading.local` ๐Ÿ”œ โš™๏ธ, โœ‹๏ธ ๐Ÿ”— โฎ๏ธ ๐Ÿ†• ๐Ÿ” โš’. - -๐Ÿ‘ฅ ๐Ÿ”œ โš™๏ธ ๐Ÿ‘ˆ. โšซ๏ธ ๐Ÿค™ `contextvars`. - -๐Ÿ‘ฅ ๐Ÿ”œ ๐Ÿ” ๐Ÿ”— ๐Ÿ• ๐Ÿ’ ๐Ÿ‘ˆ โš™๏ธ `threading.local` & โŽ ๐Ÿ‘ซ โฎ๏ธ `contextvars`, โฎ๏ธ ๐Ÿ”— โ„น. - -๐Ÿ‘‰ 5๏ธโƒฃ๐Ÿ“† ๐Ÿ˜‘ ๐Ÿ– ๐Ÿ— (& โšซ๏ธ ๐Ÿค™), ๐Ÿ‘† ๐Ÿšซ ๐Ÿค™ ๐Ÿ’ช ๐Ÿ• ๐Ÿค” โ” โšซ๏ธ ๐Ÿ‘ท โš™๏ธ โšซ๏ธ. - -๐Ÿ‘ฅ ๐Ÿ”œ โœ `PeeweeConnectionState`: - -```Python hl_lines="10-19" -{!../../docs_src/sql_databases_peewee/sql_app/database.py!} -``` - -๐Ÿ‘‰ ๐ŸŽ“ ๐Ÿ˜– โšช๏ธโžก๏ธ ๐ŸŽ ๐Ÿ”— ๐ŸŽ“ โš™๏ธ ๐Ÿ’. - -โšซ๏ธ โœ”๏ธ ๐ŸŒ โš› โš’ ๐Ÿ’ โš™๏ธ `contextvars` โ†ฉ๏ธ `threading.local`. - -`contextvars` ๐Ÿ‘ท ๐Ÿ– ๐ŸŽ ๐ŸŒ˜ `threading.local`. โœ‹๏ธ ๐ŸŽ‚ ๐Ÿ’ ๐Ÿ”— ๐Ÿ“Ÿ ๐Ÿค” ๐Ÿ‘ˆ ๐Ÿ‘‰ ๐ŸŽ“ ๐Ÿ‘ท โฎ๏ธ `threading.local`. - -, ๐Ÿ‘ฅ ๐Ÿ’ช โž• ๐ŸŽฑ โš’ โšซ๏ธ ๐Ÿ‘ท ๐Ÿšฅ โšซ๏ธ โš™๏ธ `threading.local`. `__init__`, `__setattr__`, & `__getattr__` ๐Ÿ› ๏ธ ๐ŸŒ โœ” ๐ŸŽฑ ๐Ÿ‘‰ โš™๏ธ ๐Ÿ’ ๐Ÿต ๐Ÿค” ๐Ÿ‘ˆ โšซ๏ธ ๐Ÿ”œ ๐Ÿ”— โฎ๏ธ FastAPI. - -/// tip - -๐Ÿ‘‰ ๐Ÿ”œ โš’ ๐Ÿ’ ๐ŸŽญ โ˜‘ ๐Ÿ•โ” โš™๏ธ โฎ๏ธ FastAPI. ๐Ÿšซ ๐ŸŽฒ ๐Ÿ“‚ โš–๏ธ ๐Ÿ“ช ๐Ÿ”— ๐Ÿ‘ˆ โž– โš™๏ธ, ๐Ÿ— โŒ, โ™’๏ธ. - -โœ‹๏ธ โšซ๏ธ ๐Ÿšซ ๐Ÿค ๐Ÿ’ ๐Ÿ” ๐Ÿ’Ž-๐Ÿ‹๏ธ. ๐Ÿ‘† ๐Ÿ”œ โš™๏ธ ๐Ÿ˜ `def` ๐Ÿ”ข & ๐Ÿšซ `async def`. - -/// - -### โš™๏ธ ๐Ÿ›ƒ `PeeweeConnectionState` ๐ŸŽ“ - -๐Ÿ”œ, ๐Ÿ“ `._state` ๐Ÿ”— ๐Ÿ”ข ๐Ÿ’ ๐Ÿ’ฝ `db` ๐ŸŽš โš™๏ธ ๐Ÿ†• `PeeweeConnectionState`: - -```Python hl_lines="24" -{!../../docs_src/sql_databases_peewee/sql_app/database.py!} -``` - -/// tip - -โš’ ๐Ÿ’ญ ๐Ÿ‘† ๐Ÿ“ `db._state` *โฎ๏ธ* ๐Ÿ— `db`. - -/// - -/// tip - -๐Ÿ‘† ๐Ÿ”œ ๐ŸŽ ๐Ÿ™† ๐ŸŽ ๐Ÿ’ ๐Ÿ’ฝ, ๐Ÿ”Œ `PostgresqlDatabase`, `MySQLDatabase`, โ™’๏ธ. - -/// - -## โœ ๐Ÿ’ฝ ๐Ÿท - -โžก๏ธ ๐Ÿ”œ ๐Ÿ‘€ ๐Ÿ“ `sql_app/models.py`. - -### โœ ๐Ÿ’ ๐Ÿท ๐Ÿ‘† ๐Ÿ’ฝ - -๐Ÿ”œ โœ ๐Ÿ’ ๐Ÿท (๐ŸŽ“) `User` & `Item`. - -๐Ÿ‘‰ ๐ŸŽ ๐Ÿ‘† ๐Ÿ”œ ๐Ÿšฅ ๐Ÿ‘† โฉ ๐Ÿ’ ๐Ÿ”ฐ & โ„น ๐Ÿท โœ”๏ธ ๐ŸŽ ๐Ÿ’ฝ ๐Ÿ‡ธ๐Ÿ‡ฒ ๐Ÿ”ฐ. - -/// tip - -๐Ÿ’ โš™๏ธ โš– "**๐Ÿท**" ๐Ÿ”— ๐Ÿ‘‰ ๐ŸŽ“ & ๐Ÿ‘ ๐Ÿ‘ˆ ๐Ÿ”— โฎ๏ธ ๐Ÿ’ฝ. - -โœ‹๏ธ Pydantic โš™๏ธ โš– "**๐Ÿท**" ๐Ÿ”— ๐Ÿ•ณ ๐ŸŽ, ๐Ÿ’ฝ ๐Ÿ”ฌ, ๐Ÿ› ๏ธ, & ๐Ÿงพ ๐ŸŽ“ & ๐Ÿ‘. - -/// - -๐Ÿ—„ `db` โšช๏ธโžก๏ธ `database` (๐Ÿ“ `database.py` โšช๏ธโžก๏ธ ๐Ÿ”›) & โš™๏ธ โšซ๏ธ ๐Ÿ“ฅ. - -```Python hl_lines="3 6-12 15-21" -{!../../docs_src/sql_databases_peewee/sql_app/models.py!} -``` - -/// tip - -๐Ÿ’ โœ ๐Ÿ“š ๐ŸŽฑ ๐Ÿ”ข. - -โšซ๏ธ ๐Ÿ”œ ๐Ÿ” ๐Ÿšฎ `id` ๐Ÿ”ข ๐Ÿ”ข ๐Ÿ‘‘ ๐Ÿ”‘. - -โšซ๏ธ ๐Ÿ”œ โš’ ๐Ÿ“› ๐Ÿ“ โš“๏ธ ๐Ÿ”› ๐ŸŽ“ ๐Ÿ“›. - - `Item`, โšซ๏ธ ๐Ÿ”œ โœ ๐Ÿ”ข `owner_id` โฎ๏ธ ๐Ÿ”ข ๐Ÿ†” `User`. โœ‹๏ธ ๐Ÿ‘ฅ ๐Ÿšซ ๐Ÿ“ฃ โšซ๏ธ ๐Ÿ™†. - -/// - -## โœ Pydantic ๐Ÿท - -๐Ÿ”œ โžก๏ธ โœ… ๐Ÿ“ `sql_app/schemas.py`. - -/// tip - -โŽ ๐Ÿ˜จ ๐Ÿ–– ๐Ÿ’ *๐Ÿท* & Pydantic *๐Ÿท*, ๐Ÿ‘ฅ ๐Ÿ”œ โœ”๏ธ ๐Ÿ“ `models.py` โฎ๏ธ ๐Ÿ’ ๐Ÿท, & ๐Ÿ“ `schemas.py` โฎ๏ธ Pydantic ๐Ÿท. - -๐Ÿ‘ซ Pydantic ๐Ÿท ๐Ÿ”ฌ ๐ŸŒ… โš–๏ธ ๐ŸŒ˜ "๐Ÿ”—" (โ˜‘ ๐Ÿ“Š ๐Ÿ’ ). - -๐Ÿ‘‰ ๐Ÿ”œ โ„น ๐Ÿ‘ฅ โŽ ๐Ÿ˜จ โช โš™๏ธ ๐Ÿ‘ฏโ€โ™‚๏ธ. - -/// - -### โœ Pydantic *๐Ÿท* / ๐Ÿ”— - -โœ ๐ŸŒ ๐ŸŽ Pydantic ๐Ÿท ๐Ÿ‡ธ๐Ÿ‡ฒ ๐Ÿ”ฐ: - -```Python hl_lines="16-18 21-22 25-30 34-35 38-39 42-48" -{!../../docs_src/sql_databases_peewee/sql_app/schemas.py!} -``` - -/// tip - -๐Ÿ“ฅ ๐Ÿ‘ฅ ๐Ÿ— ๐Ÿท โฎ๏ธ `id`. - -๐Ÿ‘ฅ ๐Ÿšซ ๐ŸŽฏ โœ” `id` ๐Ÿ”ข ๐Ÿ’ ๐Ÿท, โœ‹๏ธ ๐Ÿ’ ๐Ÿšฎ 1๏ธโƒฃ ๐Ÿ”. - -๐Ÿ‘ฅ โŽ ๐ŸŽฑ `owner_id` ๐Ÿ”ข `Item`. - -/// - -### โœ `PeeweeGetterDict` Pydantic *๐Ÿท* / ๐Ÿ”— - -๐Ÿ•โ” ๐Ÿ‘† ๐Ÿ” ๐Ÿ’› ๐Ÿ’ ๐ŸŽš, ๐Ÿ’– `some_user.items`, ๐Ÿ’ ๐Ÿšซ ๐Ÿšš `list` `Item`. - -โšซ๏ธ ๐Ÿšš ๐ŸŽ ๐Ÿ›ƒ ๐ŸŽš ๐ŸŽ“ `ModelSelect`. - -โšซ๏ธ ๐Ÿ’ช โœ `list` ๐Ÿšฎ ๐Ÿฌ โฎ๏ธ `list(some_user.items)`. - -โœ‹๏ธ ๐ŸŽš โšซ๏ธ ๐Ÿšซ `list`. & โšซ๏ธ ๐Ÿšซ โ˜‘ ๐Ÿ ๐Ÿš‚. โ†ฉ๏ธ ๐Ÿ‘‰, Pydantic ๐Ÿšซ ๐Ÿ’ญ ๐Ÿ”ข โ” ๐Ÿ—œ โšซ๏ธ `list` Pydantic *๐Ÿท* / ๐Ÿ”—. - -โœ‹๏ธ โฎ๏ธ โฌ Pydantic โœ” ๐Ÿšš ๐Ÿ›ƒ ๐ŸŽ“ ๐Ÿ‘ˆ ๐Ÿ˜– โšช๏ธโžก๏ธ `pydantic.utils.GetterDict`, ๐Ÿšš ๐Ÿ› ๏ธ โš™๏ธ ๐Ÿ•โ” โš™๏ธ `orm_mode = True` ๐Ÿ—ƒ ๐Ÿ’ฒ ๐Ÿœ ๐Ÿท ๐Ÿ”ข. - -๐Ÿ‘ฅ ๐Ÿ”œ โœ ๐Ÿ›ƒ `PeeweeGetterDict` ๐ŸŽ“ & โš™๏ธ โšซ๏ธ ๐ŸŒ ๐ŸŽ Pydantic *๐Ÿท* / ๐Ÿ”— ๐Ÿ‘ˆ โš™๏ธ `orm_mode`: - -```Python hl_lines="3 8-13 31 49" -{!../../docs_src/sql_databases_peewee/sql_app/schemas.py!} -``` - -๐Ÿ“ฅ ๐Ÿ‘ฅ โœ… ๐Ÿšฅ ๐Ÿ”ข ๐Ÿ‘ˆ โž– ๐Ÿ” (โœ… `.items` `some_user.items`) ๐Ÿ‘ `peewee.ModelSelect`. - -& ๐Ÿšฅ ๐Ÿ‘ˆ ๐Ÿ’ผ, ๐Ÿ“จ `list` โฎ๏ธ โšซ๏ธ. - -& โคด๏ธ ๐Ÿ‘ฅ โš™๏ธ โšซ๏ธ Pydantic *๐Ÿท* / ๐Ÿ”— ๐Ÿ‘ˆ โš™๏ธ `orm_mode = True`, โฎ๏ธ ๐Ÿ“ณ ๐Ÿ”ข `getter_dict = PeeweeGetterDict`. - -/// tip - -๐Ÿ‘ฅ ๐Ÿ•ด ๐Ÿ’ช โœ 1๏ธโƒฃ `PeeweeGetterDict` ๐ŸŽ“, & ๐Ÿ‘ฅ ๐Ÿ’ช โš™๏ธ โšซ๏ธ ๐ŸŒ Pydantic *๐Ÿท* / ๐Ÿ”—. - -/// - -## ๐Ÿ’ฉ ๐Ÿ‡จ๐Ÿ‡ป - -๐Ÿ”œ โžก๏ธ ๐Ÿ‘€ ๐Ÿ“ `sql_app/crud.py`. - -### โœ ๐ŸŒ ๐Ÿ’ฉ ๐Ÿ‡จ๐Ÿ‡ป - -โœ ๐ŸŒ ๐ŸŽ ๐Ÿ’ฉ ๐Ÿ‡จ๐Ÿ‡ป ๐Ÿ‡ธ๐Ÿ‡ฒ ๐Ÿ”ฐ, ๐ŸŒ ๐Ÿ“Ÿ ๐Ÿ“ถ ๐ŸŽ: - -```Python hl_lines="1 4-5 8-9 12-13 16-20 23-24 27-30" -{!../../docs_src/sql_databases_peewee/sql_app/crud.py!} -``` - -๐Ÿ“ค ๐Ÿ”บ โฎ๏ธ ๐Ÿ“Ÿ ๐Ÿ‡ธ๐Ÿ‡ฒ ๐Ÿ”ฐ. - -๐Ÿ‘ฅ ๐Ÿšซ ๐Ÿšถโ€โ™€๏ธ `db` ๐Ÿ”ข ๐Ÿคญ. โ†ฉ๏ธ ๐Ÿ‘ฅ โš™๏ธ ๐Ÿท ๐Ÿ”—. ๐Ÿ‘‰ โ†ฉ๏ธ `db` ๐ŸŽš ๐ŸŒ ๐ŸŽš, ๐Ÿ‘ˆ ๐Ÿ”Œ ๐ŸŒ ๐Ÿ”— โš›. ๐Ÿ‘ˆ โšซ๏ธโ” ๐Ÿ‘ฅ โœ”๏ธ ๐ŸŒ `contextvars` โ„น ๐Ÿ”›. - -๐Ÿ†–, ๐Ÿ•โ” ๐Ÿ›ฌ ๐Ÿ“š ๐ŸŽš, ๐Ÿ’– `get_users`, ๐Ÿ‘ฅ ๐Ÿ”— ๐Ÿค™ `list`, ๐Ÿ’–: - -```Python -list(models.User.select()) -``` - -๐Ÿ‘‰ ๐ŸŽ ๐Ÿค” ๐Ÿ‘ˆ ๐Ÿ‘ฅ โœ”๏ธ โœ ๐Ÿ›ƒ `PeeweeGetterDict`. โœ‹๏ธ ๐Ÿ›ฌ ๐Ÿ•ณ ๐Ÿ‘ˆ โช `list` โ†ฉ๏ธ `peewee.ModelSelect` `response_model` *โžก ๐Ÿ› ๏ธ* โฎ๏ธ `List[models.User]` (๐Ÿ‘ˆ ๐Ÿ‘ฅ ๐Ÿ”œ ๐Ÿ‘€ โช) ๐Ÿ”œ ๐Ÿ‘ท โ˜‘. - -## ๐Ÿ‘‘ **FastAPI** ๐Ÿ“ฑ - -& ๐Ÿ”œ ๐Ÿ“ `sql_app/main.py` โžก๏ธ ๐Ÿ› ๏ธ & โš™๏ธ ๐ŸŒ ๐ŸŽ ๐Ÿ• ๐Ÿ‘ฅ โœ โญ. - -### โœ ๐Ÿ’ฝ ๐Ÿ“ - -๐Ÿ“ถ ๐Ÿ™ƒ ๐ŸŒŒ โœ ๐Ÿ’ฝ ๐Ÿ“: - -```Python hl_lines="9-11" -{!../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -### โœ ๐Ÿ”— - -โœ ๐Ÿ”— ๐Ÿ‘ˆ ๐Ÿ”œ ๐Ÿ”— ๐Ÿ’ฝ โ–ถ๏ธ๏ธ โ–ถ๏ธ ๐Ÿ“จ & ๐Ÿ”Œ โšซ๏ธ ๐Ÿ”š: - -```Python hl_lines="23-29" -{!../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -๐Ÿ“ฅ ๐Ÿ‘ฅ โœ”๏ธ ๐Ÿ› `yield` โ†ฉ๏ธ ๐Ÿ‘ฅ ๐Ÿค™ ๐Ÿšซ โš™๏ธ ๐Ÿ’ฝ ๐ŸŽš ๐Ÿ”—. - -โšซ๏ธ ๐Ÿ”— ๐Ÿ’ฝ & โ™ป ๐Ÿ”— ๐Ÿ’ฝ ๐Ÿ”— ๐Ÿ”ข ๐Ÿ‘ˆ ๐Ÿ”ฌ ๐Ÿ”  ๐Ÿ“จ (โš™๏ธ `contextvars` ๐ŸŽฑ โšช๏ธโžก๏ธ ๐Ÿ”›). - -โ†ฉ๏ธ ๐Ÿ’ฝ ๐Ÿ”— โš  ๐Ÿ‘ค/๐Ÿ…พ ๐Ÿšง, ๐Ÿ‘‰ ๐Ÿ”— โœ โฎ๏ธ ๐Ÿ˜ `def` ๐Ÿ”ข. - -& โคด๏ธ, ๐Ÿ”  *โžก ๐Ÿ› ๏ธ ๐Ÿ”ข* ๐Ÿ‘ˆ ๐Ÿ’ช ๐Ÿ” ๐Ÿ’ฝ ๐Ÿ‘ฅ ๐Ÿšฎ โšซ๏ธ ๐Ÿ”—. - -โœ‹๏ธ ๐Ÿ‘ฅ ๐Ÿšซ โš™๏ธ ๐Ÿ’ฒ ๐Ÿ‘ ๐Ÿ‘‰ ๐Ÿ”— (โšซ๏ธ ๐Ÿค™ ๐Ÿšซ ๐Ÿค ๐Ÿ™† ๐Ÿ’ฒ, โšซ๏ธ โœ”๏ธ ๐Ÿ› `yield`). , ๐Ÿ‘ฅ ๐Ÿšซ ๐Ÿšฎ โšซ๏ธ *โžก ๐Ÿ› ๏ธ ๐Ÿ”ข* โœ‹๏ธ *โžก ๐Ÿ› ๏ธ ๐Ÿ‘จโ€๐ŸŽจ* `dependencies` ๐Ÿ”ข: - -```Python hl_lines="32 40 47 59 65 72" -{!../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -### ๐Ÿ”‘ ๐Ÿ”ข ๐ŸŽง-๐Ÿ”— - -๐ŸŒ `contextvars` ๐Ÿ• ๐Ÿ‘ท, ๐Ÿ‘ฅ ๐Ÿ’ช โš’ ๐Ÿ’ญ ๐Ÿ‘ฅ โœ”๏ธ ๐Ÿ”ฌ ๐Ÿ’ฒ `ContextVar` ๐Ÿ”  ๐Ÿ“จ ๐Ÿ‘ˆ โš™๏ธ ๐Ÿ’ฝ, & ๐Ÿ‘ˆ ๐Ÿ’ฒ ๐Ÿ”œ โš™๏ธ ๐Ÿ’ฝ ๐Ÿ‡ต๐Ÿ‡ธ (๐Ÿ”—, ๐Ÿ’ต, โ™’๏ธ) ๐ŸŽ‚ ๐Ÿ“จ. - -๐Ÿ‘ˆ, ๐Ÿ‘ฅ ๐Ÿ’ช โœ โž•1๏ธโƒฃ `async` ๐Ÿ”— `reset_db_state()` ๐Ÿ‘ˆ โš™๏ธ ๐ŸŽง-๐Ÿ”— `get_db()`. โšซ๏ธ ๐Ÿ”œ โš’ ๐Ÿ’ฒ ๐Ÿ”‘ ๐Ÿ”ข (โฎ๏ธ ๐Ÿ”ข `dict`) ๐Ÿ‘ˆ ๐Ÿ”œ โš™๏ธ ๐Ÿ’ฝ ๐Ÿ‡ต๐Ÿ‡ธ ๐ŸŽ‚ ๐Ÿ“จ. & โคด๏ธ ๐Ÿ”— `get_db()` ๐Ÿ”œ ๐Ÿช โšซ๏ธ ๐Ÿ’ฝ ๐Ÿ‡ต๐Ÿ‡ธ (๐Ÿ”—, ๐Ÿ’ต, โ™’๏ธ). - -```Python hl_lines="18-20" -{!../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -**โญ ๐Ÿ“จ**, ๐Ÿ‘ฅ ๐Ÿ”œ โฒ ๐Ÿ‘ˆ ๐Ÿ”‘ ๐Ÿ”ข ๐Ÿ”„ `async` ๐Ÿ”— `reset_db_state()` & โคด๏ธ โœ ๐Ÿ†• ๐Ÿ”— `get_db()` ๐Ÿ”—, ๐Ÿ‘ˆ ๐Ÿ†• ๐Ÿ“จ ๐Ÿ”œ โœ”๏ธ ๐Ÿšฎ ๐Ÿ‘ ๐Ÿ’ฝ ๐Ÿ‡ต๐Ÿ‡ธ (๐Ÿ”—, ๐Ÿ’ต, โ™’๏ธ). - -/// tip - -FastAPI ๐Ÿ” ๐Ÿ› ๏ธ, 1๏ธโƒฃ ๐Ÿ“จ ๐Ÿ’ช โ–ถ๏ธ โž– ๐Ÿ› ๏ธ, & โญ ๐Ÿ, โž•1๏ธโƒฃ ๐Ÿ“จ ๐Ÿ’ช ๐Ÿ“จ & โ–ถ๏ธ ๐Ÿญ ๐Ÿ‘, & โšซ๏ธ ๐ŸŒ ๐Ÿ’ช ๐Ÿ› ๏ธ ๐ŸŽ ๐Ÿงต. - -โœ‹๏ธ ๐Ÿ”‘ ๐Ÿ”ข ๐Ÿค” ๐Ÿ‘ซ ๐Ÿ” โš’,, ๐Ÿ’ ๐Ÿ’ฝ ๐Ÿ‡ต๐Ÿ‡ธ โš’ `async` ๐Ÿ”— `reset_db_state()` ๐Ÿ”œ ๐Ÿšง ๐Ÿšฎ ๐Ÿ‘ ๐Ÿ’ฝ ๐ŸŽ‚ ๐ŸŽ‚ ๐Ÿ“จ. - - & ๐ŸŽ ๐Ÿ•ฐ, ๐ŸŽ ๐Ÿ› ๏ธ ๐Ÿ“จ ๐Ÿ”œ โœ”๏ธ ๐Ÿšฎ ๐Ÿ‘ ๐Ÿ’ฝ ๐Ÿ‡ต๐Ÿ‡ธ ๐Ÿ‘ˆ ๐Ÿ”œ ๐Ÿ”ฌ ๐ŸŽ‚ ๐Ÿ“จ. - -/// - -#### ๐Ÿ’ ๐Ÿ—ณ - -๐Ÿšฅ ๐Ÿ‘† โš™๏ธ ๐Ÿ’ ๐Ÿ—ณ, โ˜‘ ๐Ÿ’ฝ `db.obj`. - -, ๐Ÿ‘† ๐Ÿ”œ โฒ โšซ๏ธ โฎ๏ธ: - -```Python hl_lines="3-4" -async def reset_db_state(): - database.db.obj._state._state.set(db_state_default.copy()) - database.db.obj._state.reset() -``` - -### โœ ๐Ÿ‘† **FastAPI** *โžก ๐Ÿ› ๏ธ* - -๐Ÿ”œ, ๐Ÿ˜’, ๐Ÿ“ฅ ๐Ÿฉ **FastAPI** *โžก ๐Ÿ› ๏ธ* ๐Ÿ“Ÿ. - -```Python hl_lines="32-37 40-43 46-53 56-62 65-68 71-79" -{!../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -### ๐Ÿ”ƒ `def` ๐Ÿ†š `async def` - -๐ŸŽ โฎ๏ธ ๐Ÿ‡ธ๐Ÿ‡ฒ, ๐Ÿ‘ฅ ๐Ÿšซ ๐Ÿ”จ ๐Ÿ•ณ ๐Ÿ’–: - -```Python -user = await models.User.select().first() -``` - -...โœ‹๏ธ โ†ฉ๏ธ ๐Ÿ‘ฅ โš™๏ธ: - -```Python -user = models.User.select().first() -``` - -, ๐Ÿ”„, ๐Ÿ‘ฅ ๐Ÿ”œ ๐Ÿ“ฃ *โžก ๐Ÿ› ๏ธ ๐Ÿ”ข* & ๐Ÿ”— ๐Ÿต `async def`, โฎ๏ธ ๐Ÿ˜ `def`,: - -```Python hl_lines="2" -# Something goes here -def read_users(skip: int = 0, limit: int = 100): - # Something goes here -``` - -## ๐Ÿ”ฌ ๐Ÿ’ โฎ๏ธ ๐Ÿ” - -๐Ÿ‘‰ ๐Ÿ–ผ ๐Ÿ”Œ โž• *โžก ๐Ÿ› ๏ธ* ๐Ÿ‘ˆ ๐Ÿ”ฌ ๐Ÿ“ ๐Ÿญ ๐Ÿ“จ โฎ๏ธ `time.sleep(sleep_time)`. - -โšซ๏ธ ๐Ÿ”œ โœ”๏ธ ๐Ÿ’ฝ ๐Ÿ”— ๐Ÿ“‚ โ–ถ๏ธ & ๐Ÿ”œ โŒ› ๐Ÿฅˆ โญ ๐Ÿ™‡ ๐Ÿ”™. & ๐Ÿ”  ๐Ÿ†• ๐Ÿ“จ ๐Ÿ”œ โŒ› ๐Ÿ• ๐Ÿฅˆ ๐ŸŒ˜. - -๐Ÿ‘‰ ๐Ÿ”œ ๐Ÿ’ช โžก๏ธ ๐Ÿ‘† ๐Ÿ’ฏ ๐Ÿ‘ˆ ๐Ÿ‘† ๐Ÿ“ฑ โฎ๏ธ ๐Ÿ’ & FastAPI ๐ŸŽญ โ˜‘ โฎ๏ธ ๐ŸŒ ๐Ÿ’ฉ ๐Ÿ”ƒ ๐Ÿงต. - -๐Ÿšฅ ๐Ÿ‘† ๐Ÿ’š โœ… โ” ๐Ÿ’ ๐Ÿ”œ ๐Ÿ’” ๐Ÿ‘† ๐Ÿ“ฑ ๐Ÿšฅ โš™๏ธ ๐Ÿต ๐Ÿ› ๏ธ, ๐Ÿšถ `sql_app/database.py` ๐Ÿ“ & ๐Ÿค โธ: - -```Python -# db._state = PeeweeConnectionState() -``` - -& ๐Ÿ“ `sql_app/main.py` ๐Ÿ“, ๐Ÿค ๐Ÿ’ช `async` ๐Ÿ”— `reset_db_state()` & โŽ โšซ๏ธ โฎ๏ธ `pass`: - -```Python -async def reset_db_state(): -# database.db._state._state.set(db_state_default.copy()) -# database.db._state.reset() - pass -``` - -โคด๏ธ ๐Ÿƒ ๐Ÿ‘† ๐Ÿ“ฑ โฎ๏ธ Uvicorn: - -
- -```console -$ uvicorn sql_app.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 & โœ ๐Ÿ‘ฉโ€โคโ€๐Ÿ‘จ ๐Ÿ‘ฉโ€๐Ÿ’ป. - -โคด๏ธ ๐Ÿ“‚ 1๏ธโƒฃ0๏ธโƒฃ ๐Ÿ“‘ http://127.0.0.1:8000/docs#/default/read_๐ŸŒ_๐Ÿ‘ฉโ€๐Ÿ’ป_slowusers_ = ๐ŸŽ ๐Ÿ•ฐ. - -๐Ÿšถ *โžก ๐Ÿ› ๏ธ* "๐Ÿคš `/slowusers/`" ๐ŸŒ ๐Ÿ“‘. โš™๏ธ "๐Ÿ”„ โšซ๏ธ ๐Ÿ‘…" ๐Ÿ”ผ & ๐Ÿ› ๏ธ ๐Ÿ“จ ๐Ÿ”  ๐Ÿ“‘, 1๏ธโƒฃ โ–ถ๏ธ๏ธ โฎ๏ธ ๐ŸŽ. - -๐Ÿ“‘ ๐Ÿ”œ โŒ› ๐Ÿ– & โคด๏ธ ๐Ÿ‘ซ ๐Ÿ”œ ๐ŸŽฆ `Internal Server Error`. - -### โšซ๏ธโ” ๐Ÿ”จ - -๐Ÿฅ‡ ๐Ÿ“‘ ๐Ÿ”œ โš’ ๐Ÿ‘† ๐Ÿ“ฑ โœ ๐Ÿ”— ๐Ÿ’ฝ & โŒ› ๐Ÿฅˆ โญ ๐Ÿ™‡ ๐Ÿ”™ & ๐Ÿ“ช ๐Ÿ’ฝ ๐Ÿ”—. - -โคด๏ธ, ๐Ÿ“จ โญ ๐Ÿ“‘, ๐Ÿ‘† ๐Ÿ“ฑ ๐Ÿ”œ โŒ› ๐Ÿ• ๐Ÿฅˆ ๐ŸŒ˜, & ๐Ÿ”›. - -๐Ÿ‘‰ โ›“ ๐Ÿ‘ˆ โšซ๏ธ ๐Ÿ”œ ๐Ÿ”š ๐Ÿ†™ ๐Ÿ ๐Ÿ ๐Ÿ“‘' ๐Ÿ“จ โช ๐ŸŒ˜ โฎ๏ธ ๐Ÿ•. - -โคด๏ธ 1๏ธโƒฃ ๐Ÿ ๐Ÿ“จ ๐Ÿ‘ˆ โŒ› ๐ŸŒ˜ ๐Ÿฅˆ ๐Ÿ”œ ๐Ÿ”„ ๐Ÿ“‚ ๐Ÿ’ฝ ๐Ÿ”—, โœ‹๏ธ 1๏ธโƒฃ ๐Ÿ“š โฎ๏ธ ๐Ÿ“จ ๐ŸŽ ๐Ÿ“‘ ๐Ÿ”œ ๐ŸŽฒ ๐Ÿต ๐ŸŽ ๐Ÿงต ๐Ÿฅ‡ ๐Ÿ•, โšซ๏ธ ๐Ÿ”œ โœ”๏ธ ๐ŸŽ ๐Ÿ’ฝ ๐Ÿ”— ๐Ÿ‘ˆ โช ๐Ÿ“‚, & ๐Ÿ’ ๐Ÿ”œ ๐Ÿšฎ โŒ & ๐Ÿ‘† ๐Ÿ”œ ๐Ÿ‘€ โšซ๏ธ ๐Ÿ“ถ, & ๐Ÿ“จ ๐Ÿ”œ โœ”๏ธ `Internal Server Error`. - -๐Ÿ‘‰ ๐Ÿ”œ ๐ŸŽฒ ๐Ÿ”จ ๐ŸŒ… ๐ŸŒ˜ 1๏ธโƒฃ ๐Ÿ“š ๐Ÿ“‘. - -๐Ÿšฅ ๐Ÿ‘† โœ”๏ธ ๐Ÿ’— ๐Ÿ‘ฉโ€๐Ÿ’ป ๐Ÿ’ฌ ๐Ÿ‘† ๐Ÿ“ฑ โšซ๏ธโ” ๐ŸŽ ๐Ÿ•ฐ, ๐Ÿ‘‰ โšซ๏ธโ” ๐Ÿ’ช ๐Ÿ”จ. - -& ๐Ÿ‘† ๐Ÿ“ฑ โ–ถ๏ธ ๐Ÿต ๐ŸŒ… & ๐ŸŒ– ๐Ÿ‘ฉโ€๐Ÿ’ป ๐ŸŽ ๐Ÿ•ฐ, โŒ› ๐Ÿ•ฐ ๐Ÿ‘ ๐Ÿ“จ ๐Ÿ’ช ๐Ÿ“ & ๐Ÿ“ โฒ โŒ. - -### ๐Ÿ”ง ๐Ÿ’ โฎ๏ธ FastAPI - -๐Ÿ”œ ๐Ÿšถ ๐Ÿ”™ ๐Ÿ“ `sql_app/database.py`, & โœ โธ: - -```Python -db._state = PeeweeConnectionState() -``` - -& ๐Ÿ“ `sql_app/main.py` ๐Ÿ“, โœ ๐Ÿ’ช `async` ๐Ÿ”— `reset_db_state()`: - -```Python -async def reset_db_state(): - database.db._state._state.set(db_state_default.copy()) - database.db._state.reset() -``` - -โŽ ๐Ÿ‘† ๐Ÿƒโ€โ™‚ ๐Ÿ“ฑ & โ–ถ๏ธ โšซ๏ธ ๐Ÿ”„. - -๐Ÿ” ๐ŸŽ ๐Ÿ› ๏ธ โฎ๏ธ 1๏ธโƒฃ0๏ธโƒฃ ๐Ÿ“‘. ๐Ÿ‘‰ ๐Ÿ•ฐ ๐ŸŒ ๐Ÿ‘ซ ๐Ÿ”œ โŒ› & ๐Ÿ‘† ๐Ÿ”œ ๐Ÿคš ๐ŸŒ ๐Ÿ ๐Ÿต โŒ. - -...๐Ÿ‘† ๐Ÿ”ง โšซ๏ธ โ— - -## ๐Ÿ“„ ๐ŸŒ ๐Ÿ“ - - ๐Ÿ’ญ ๐Ÿ‘† ๐Ÿ”œ โœ”๏ธ ๐Ÿ“ ๐Ÿ“› `my_super_project` (โš–๏ธ ๐Ÿ‘ ๐Ÿ‘† ๐Ÿ’š) ๐Ÿ‘ˆ ๐Ÿ”Œ ๐ŸŽง-๐Ÿ“ ๐Ÿค™ `sql_app`. - -`sql_app` ๐Ÿ”œ โœ”๏ธ ๐Ÿ“„ ๐Ÿ“: - -* `sql_app/__init__.py`: ๐Ÿ› ๐Ÿ“. - -* `sql_app/database.py`: - -```Python -{!../../docs_src/sql_databases_peewee/sql_app/database.py!} -``` - -* `sql_app/models.py`: - -```Python -{!../../docs_src/sql_databases_peewee/sql_app/models.py!} -``` - -* `sql_app/schemas.py`: - -```Python -{!../../docs_src/sql_databases_peewee/sql_app/schemas.py!} -``` - -* `sql_app/crud.py`: - -```Python -{!../../docs_src/sql_databases_peewee/sql_app/crud.py!} -``` - -* `sql_app/main.py`: - -```Python -{!../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -## ๐Ÿ“ก โ„น - -/// warning - -๐Ÿ‘‰ ๐Ÿ“ถ ๐Ÿ“ก โ„น ๐Ÿ‘ˆ ๐Ÿ‘† ๐ŸŽฒ ๐Ÿšซ ๐Ÿ’ช. - -/// - -### โš  - -๐Ÿ’ โš™๏ธ `threading.local` ๐Ÿ”ข ๐Ÿช โšซ๏ธ ๐Ÿ’ฝ "๐Ÿ‡ต๐Ÿ‡ธ" ๐Ÿ’ฝ (๐Ÿ”—, ๐Ÿ’ต, โ™’๏ธ). - -`threading.local` โœ ๐Ÿ’ฒ ๐ŸŒŸ โฎ๏ธ ๐Ÿงต, โœ‹๏ธ ๐Ÿ” ๐Ÿ› ๏ธ ๐Ÿ”œ ๐Ÿƒ ๐ŸŒ ๐Ÿ“Ÿ (โœ… ๐Ÿ”  ๐Ÿ“จ) ๐ŸŽ ๐Ÿงต, & ๐ŸŽฒ ๐Ÿšซ โœ”. - -๐Ÿ”› ๐Ÿ” ๐Ÿ‘ˆ, ๐Ÿ” ๐Ÿ› ๏ธ ๐Ÿ’ช ๐Ÿƒ ๐Ÿ” ๐Ÿ“Ÿ ๐Ÿงต (โš™๏ธ `asyncio.run_in_executor`), โœ‹๏ธ ๐Ÿ”— ๐ŸŽ ๐Ÿ“จ. - -๐Ÿ‘‰ โ›“ ๐Ÿ‘ˆ, โฎ๏ธ ๐Ÿ’ โฎ๏ธ ๐Ÿ› ๏ธ, ๐Ÿ’— ๐Ÿ“‹ ๐Ÿ’ช โš™๏ธ ๐ŸŽ `threading.local` ๐Ÿ”ข & ๐Ÿ”š ๐Ÿ†™ ๐Ÿค ๐ŸŽ ๐Ÿ”— & ๐Ÿ’ฝ (๐Ÿ‘ˆ ๐Ÿ‘ซ ๐Ÿšซ๐Ÿ”œ ๐Ÿšซ), & ๐ŸŽ ๐Ÿ•ฐ, ๐Ÿšฅ ๐Ÿ‘ซ ๐Ÿ› ๏ธ ๐Ÿ” ๐Ÿ‘ค/๐Ÿ…พ-๐Ÿšง ๐Ÿ“Ÿ ๐Ÿงต (โฎ๏ธ ๐Ÿ˜ `def` ๐Ÿ”ข FastAPI, *โžก ๐Ÿ› ๏ธ* & ๐Ÿ”—), ๐Ÿ‘ˆ ๐Ÿ“Ÿ ๐Ÿ† ๐Ÿšซ โœ”๏ธ ๐Ÿ” ๐Ÿ’ฝ ๐Ÿ‡ต๐Ÿ‡ธ ๐Ÿ”ข, โช โšซ๏ธ ๐Ÿ• ๐ŸŽ ๐Ÿ“จ & โšซ๏ธ ๐Ÿ”œ ๐Ÿ’ช ๐Ÿคš ๐Ÿ” ๐ŸŽ ๐Ÿ’ฝ ๐Ÿ‡ต๐Ÿ‡ธ. - -### ๐Ÿ”‘ ๐Ÿ”ข - -๐Ÿ 3๏ธโƒฃ.7๏ธโƒฃ โœ”๏ธ `contextvars` ๐Ÿ‘ˆ ๐Ÿ’ช โœ ๐Ÿ‡ง๐Ÿ‡ฟ ๐Ÿ”ข ๐Ÿ“ถ ๐ŸŽ `threading.local`, โœ‹๏ธ ๐Ÿ”— ๐Ÿ‘ซ ๐Ÿ” โš’. - -๐Ÿ“ค ๐Ÿ“š ๐Ÿ‘œ โœ”๏ธ ๐Ÿคฏ. - -`ContextVar` โœ”๏ธ โœ ๐Ÿ” ๐Ÿ•น, ๐Ÿ’–: - -```Python -some_var = ContextVar("some_var", default="default value") -``` - -โš’ ๐Ÿ’ฒ โš™๏ธ โฎ๏ธ "๐Ÿ”‘" (โœ… โฎ๏ธ ๐Ÿ“จ) โš™๏ธ: - -```Python -some_var.set("new value") -``` - -๐Ÿคš ๐Ÿ’ฒ ๐Ÿ™† ๐Ÿ”˜ ๐Ÿ”‘ (โœ… ๐Ÿ™† ๐Ÿ• ๐Ÿšš โฎ๏ธ ๐Ÿ“จ) โš™๏ธ: - -```Python -some_var.get() -``` - -### โš’ ๐Ÿ”‘ ๐Ÿ”ข `async` ๐Ÿ”— `reset_db_state()` - -๐Ÿšฅ ๐Ÿ• ๐Ÿ” ๐Ÿ“Ÿ โš’ ๐Ÿ’ฒ โฎ๏ธ `some_var.set("updated in function")` (โœ… ๐Ÿ’– `async` ๐Ÿ”—), ๐ŸŽ‚ ๐Ÿ“Ÿ โšซ๏ธ & ๐Ÿ“Ÿ ๐Ÿ‘ˆ ๐Ÿšถ โฎ๏ธ (โœ… ๐Ÿ“Ÿ ๐Ÿ”˜ `async` ๐Ÿ”ข ๐Ÿค™ โฎ๏ธ `await`) ๐Ÿ”œ ๐Ÿ‘€ ๐Ÿ‘ˆ ๐Ÿ†• ๐Ÿ’ฒ. - -, ๐Ÿ‘† ๐Ÿ’ผ, ๐Ÿšฅ ๐Ÿ‘ฅ โš’ ๐Ÿ’ ๐Ÿ‡ต๐Ÿ‡ธ ๐Ÿ”ข (โฎ๏ธ ๐Ÿ”ข `dict`) `async` ๐Ÿ”—, ๐ŸŒ ๐ŸŽ‚ ๐Ÿ”— ๐Ÿ“Ÿ ๐Ÿ‘† ๐Ÿ“ฑ ๐Ÿ”œ ๐Ÿ‘€ ๐Ÿ‘‰ ๐Ÿ’ฒ & ๐Ÿ”œ ๐Ÿ’ช โ™ป โšซ๏ธ ๐ŸŽ‚ ๐Ÿ“จ. - -& ๐Ÿ”‘ ๐Ÿ”ข ๐Ÿ”œ โš’ ๐Ÿ”„ โญ ๐Ÿ“จ, ๐Ÿšฅ ๐Ÿ‘ซ ๐Ÿ› ๏ธ. - -### โš’ ๐Ÿ’ฝ ๐Ÿ‡ต๐Ÿ‡ธ ๐Ÿ”— `get_db()` - -`get_db()` ๐Ÿ˜ `def` ๐Ÿ”ข, **FastAPI** ๐Ÿ”œ โš’ โšซ๏ธ ๐Ÿƒ ๐Ÿงต, โฎ๏ธ *๐Ÿ“* "๐Ÿ”‘", ๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘ ๐ŸŽ ๐Ÿ’ฒ ๐Ÿ”‘ ๐Ÿ”ข ( `dict` โฎ๏ธ โฒ ๐Ÿ’ฝ ๐Ÿ‡ต๐Ÿ‡ธ). โคด๏ธ โšซ๏ธ ๐Ÿ’ช ๐Ÿšฎ ๐Ÿ’ฝ ๐Ÿ‡ต๐Ÿ‡ธ ๐Ÿ‘ˆ `dict`, ๐Ÿ’– ๐Ÿ”—, โ™’๏ธ. - -โœ‹๏ธ ๐Ÿšฅ ๐Ÿ’ฒ ๐Ÿ”‘ ๐Ÿ”ข (๐Ÿ”ข `dict`) โš’ ๐Ÿ‘ˆ ๐Ÿ˜ `def` ๐Ÿ”ข, โšซ๏ธ ๐Ÿ”œ โœ ๐Ÿ†• ๐Ÿ’ฒ ๐Ÿ‘ˆ ๐Ÿ”œ ๐Ÿšง ๐Ÿ•ด ๐Ÿ‘ˆ ๐Ÿงต ๐Ÿงต, & ๐ŸŽ‚ ๐Ÿ“Ÿ (๐Ÿ’– *โžก ๐Ÿ› ๏ธ ๐Ÿ”ข*) ๐Ÿšซ๐Ÿ”œ โœ”๏ธ ๐Ÿ” โšซ๏ธ. `get_db()` ๐Ÿ‘ฅ ๐Ÿ’ช ๐Ÿ•ด โš’ ๐Ÿ’ฒ `dict`, โœ‹๏ธ ๐Ÿšซ ๐ŸŽ‚ `dict` โšซ๏ธ. - -, ๐Ÿ‘ฅ ๐Ÿ’ช โœ”๏ธ `async` ๐Ÿ”— `reset_db_state()` โš’ `dict` ๐Ÿ”‘ ๐Ÿ”ข. ๐Ÿ‘ˆ ๐ŸŒŒ, ๐ŸŒ ๐Ÿ“Ÿ โœ”๏ธ ๐Ÿ” ๐ŸŽ `dict` ๐Ÿ’ฝ ๐Ÿ‡ต๐Ÿ‡ธ ๐Ÿ‘ ๐Ÿ“จ. - -### ๐Ÿ”— & ๐Ÿ”Œ ๐Ÿ”— `get_db()` - -โคด๏ธ โญ โ” ๐Ÿ”œ, โšซ๏ธโ” ๐Ÿšซ ๐Ÿ”— & ๐Ÿ”Œ ๐Ÿ’ฝ `async` ๐Ÿ”— โšซ๏ธ, โ†ฉ๏ธ `get_db()`โ“ - -`async` ๐Ÿ”— โœ”๏ธ `async` ๐Ÿ”‘ ๐Ÿ”ข ๐Ÿ›ก ๐ŸŽ‚ ๐Ÿ“จ, โœ‹๏ธ ๐Ÿ— & ๐Ÿ“ช ๐Ÿ’ฝ ๐Ÿ”— โš  ๐Ÿšง, โšซ๏ธ ๐Ÿ’ช ๐Ÿ“‰ ๐ŸŽญ ๐Ÿšฅ โšซ๏ธ ๐Ÿ“ค. - -๐Ÿ‘ฅ ๐Ÿ’ช ๐Ÿ˜ `def` ๐Ÿ”— `get_db()`. diff --git a/docs/en/docs/advanced/testing-database.md b/docs/en/docs/advanced/testing-database.md deleted file mode 100644 index 2b704f229..000000000 --- a/docs/en/docs/advanced/testing-database.md +++ /dev/null @@ -1,111 +0,0 @@ -# Testing a Database - -/// info - -These docs are about to be updated. ๐ŸŽ‰ - -The current version assumes Pydantic v1, and SQLAlchemy versions less than 2.0. - -The new docs will include Pydantic v2 and will use SQLModel (which is also based on SQLAlchemy) once it is updated to use Pydantic v2 as well. - -/// - -You can use the same dependency overrides from [Testing Dependencies with Overrides](testing-dependencies.md){.internal-link target=_blank} to alter a database for testing. - -You could want to set up a different database for testing, rollback the data after the tests, pre-fill it with some testing data, etc. - -The main idea is exactly the same you saw in that previous chapter. - -## Add tests for the SQL app - -Let's update the example from [SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank} to use a testing database. - -All the app code is the same, you can go back to that chapter check how it was. - -The only changes here are in the new testing file. - -Your normal dependency `get_db()` would return a database session. - -In the test, you could use a dependency override to return your *custom* database session instead of the one that would be used normally. - -In this example we'll create a temporary database only for the tests. - -## File structure - -We create a new file at `sql_app/tests/test_sql_app.py`. - -So the new file structure looks like: - -``` hl_lines="9-11" -. -โ””โ”€โ”€ sql_app - โ”œโ”€โ”€ __init__.py - โ”œโ”€โ”€ crud.py - โ”œโ”€โ”€ database.py - โ”œโ”€โ”€ main.py - โ”œโ”€โ”€ models.py - โ”œโ”€โ”€ schemas.py - โ””โ”€โ”€ tests - โ”œโ”€โ”€ __init__.py - โ””โ”€โ”€ test_sql_app.py -``` - -## Create the new database session - -First, we create a new database session with the new database. - -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. - -```Python hl_lines="8-13" -{!../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} -``` - -/// tip - -You could reduce duplication in that code by putting it in a function and using it from both `database.py` and `tests/test_sql_app.py`. - -For simplicity and to focus on the specific testing code, we are just copying it. - -/// - -## Create the database - -Because now we are going to use a new database in a new file, we need to make sure we create the database with: - -```Python -Base.metadata.create_all(bind=engine) -``` - -That is normally called in `main.py`, but the line in `main.py` uses the database file `sql_app.db`, and we need to make sure we create `test.db` for the tests. - -So we add that line here, with the new file. - -```Python hl_lines="16" -{!../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} -``` - -## Dependency override - -Now we create the dependency override and add it to the overrides for our app. - -```Python hl_lines="19-24 27" -{!../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} -``` - -/// tip - -The code for `override_get_db()` is almost exactly the same as for `get_db()`, but in `override_get_db()` we use the `TestingSessionLocal` for the testing database instead. - -/// - -## Test the app - -Then we can just test the app as normally. - -```Python hl_lines="32-47" -{!../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} -``` - -And all the modifications we made in the database during the tests will be in the `test.db` database instead of the main `sql_app.db`. diff --git a/docs/en/docs/how-to/async-sql-encode-databases.md b/docs/en/docs/how-to/async-sql-encode-databases.md deleted file mode 100644 index a72316c4d..000000000 --- a/docs/en/docs/how-to/async-sql-encode-databases.md +++ /dev/null @@ -1,201 +0,0 @@ -# ~~Async SQL (Relational) Databases with Encode/Databases~~ (deprecated) - -/// info - -These docs are about to be updated. ๐ŸŽ‰ - -The current version assumes Pydantic v1. - -The new docs will include Pydantic v2 and will use SQLModel once it is updated to use Pydantic v2 as well. - -/// - -/// warning | "Deprecated" - -This tutorial is deprecated and will be removed in a future version. - -/// - -You can also use `encode/databases` with **FastAPI** to connect to databases using `async` and `await`. - -It is compatible with: - -* PostgreSQL -* MySQL -* SQLite - -In this example, we'll use **SQLite**, because it uses a single file and Python has integrated support. So, you can copy this example and run it as is. - -Later, for your production application, you might want to use a database server like **PostgreSQL**. - -/// tip - -You could adopt ideas from the section about SQLAlchemy ORM ([SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank}), like using utility functions to perform operations in the database, independent of your **FastAPI** code. - -This section doesn't apply those ideas, to be equivalent to the counterpart in Starlette. - -/// - -## Import and set up `SQLAlchemy` - -* Import `SQLAlchemy`. -* Create a `metadata` object. -* Create a table `notes` using the `metadata` object. - -```Python hl_lines="4 14 16-22" -{!../../docs_src/async_sql_databases/tutorial001.py!} -``` - -/// tip - -Notice that all this code is pure SQLAlchemy Core. - -`databases` is not doing anything here yet. - -/// - -## Import and set up `databases` - -* Import `databases`. -* Create a `DATABASE_URL`. -* Create a `database` object. - -```Python hl_lines="3 9 12" -{!../../docs_src/async_sql_databases/tutorial001.py!} -``` - -/// tip - -If you were connecting to a different database (e.g. PostgreSQL), you would need to change the `DATABASE_URL`. - -/// - -## Create the tables - -In this case, we are creating the tables in the same Python file, but in production, you would probably want to create them with Alembic, integrated with migrations, etc. - -Here, this section would run directly, right before starting your **FastAPI** application. - -* Create an `engine`. -* Create all the tables from the `metadata` object. - -```Python hl_lines="25-28" -{!../../docs_src/async_sql_databases/tutorial001.py!} -``` - -## Create models - -Create Pydantic models for: - -* Notes to be created (`NoteIn`). -* Notes to be returned (`Note`). - -```Python hl_lines="31-33 36-39" -{!../../docs_src/async_sql_databases/tutorial001.py!} -``` - -By creating these Pydantic models, the input data will be validated, serialized (converted), and annotated (documented). - -So, you will be able to see it all in the interactive API docs. - -## Connect and disconnect - -* Create your `FastAPI` application. -* Create event handlers to connect and disconnect from the database. - -```Python hl_lines="42 45-47 50-52" -{!../../docs_src/async_sql_databases/tutorial001.py!} -``` - -## Read notes - -Create the *path operation function* to read notes: - -```Python hl_lines="55-58" -{!../../docs_src/async_sql_databases/tutorial001.py!} -``` - -/// note - -Notice that as we communicate with the database using `await`, the *path operation function* is declared with `async`. - -/// - -### Notice the `response_model=List[Note]` - -It uses `typing.List`. - -That documents (and validates, serializes, filters) the output data, as a `list` of `Note`s. - -## Create notes - -Create the *path operation function* to create notes: - -```Python hl_lines="61-65" -{!../../docs_src/async_sql_databases/tutorial001.py!} -``` - -/// info - -In Pydantic v1 the method was called `.dict()`, it was deprecated (but still supported) in Pydantic v2, and renamed to `.model_dump()`. - -The examples here use `.dict()` for compatibility with Pydantic v1, but you should use `.model_dump()` instead if you can use Pydantic v2. - -/// - -/// note - -Notice that as we communicate with the database using `await`, the *path operation function* is declared with `async`. - -/// - -### About `{**note.dict(), "id": last_record_id}` - -`note` is a Pydantic `Note` object. - -`note.dict()` returns a `dict` with its data, something like: - -```Python -{ - "text": "Some note", - "completed": False, -} -``` - -but it doesn't have the `id` field. - -So we create a new `dict`, that contains the key-value pairs from `note.dict()` with: - -```Python -{**note.dict()} -``` - -`**note.dict()` "unpacks" the key value pairs directly, so, `{**note.dict()}` would be, more or less, a copy of `note.dict()`. - -And then, we extend that copy `dict`, adding another key-value pair: `"id": last_record_id`: - -```Python -{**note.dict(), "id": last_record_id} -``` - -So, the final result returned would be something like: - -```Python -{ - "id": 1, - "text": "Some note", - "completed": False, -} -``` - -## Check it - -You can copy this code as is, and see the docs at http://127.0.0.1:8000/docs. - -There you can see all your API documented and interact with it: - - - -## More info - -You can read more about `encode/databases` at its GitHub page. diff --git a/docs/en/docs/how-to/nosql-databases-couchbase.md b/docs/en/docs/how-to/nosql-databases-couchbase.md deleted file mode 100644 index feb4025dd..000000000 --- a/docs/en/docs/how-to/nosql-databases-couchbase.md +++ /dev/null @@ -1,178 +0,0 @@ -# ~~NoSQL (Distributed / Big Data) Databases with Couchbase~~ (deprecated) - -/// info - -These docs are about to be updated. ๐ŸŽ‰ - -The current version assumes Pydantic v1. - -The new docs will hopefully use Pydantic v2 and will use ODMantic with MongoDB. - -/// - -/// warning | "Deprecated" - -This tutorial is deprecated and will be removed in a future version. - -/// - -**FastAPI** can also be integrated with any NoSQL. - -Here we'll see an example using **Couchbase**, a document based NoSQL database. - -You can adapt it to any other NoSQL database like: - -* **MongoDB** -* **Cassandra** -* **CouchDB** -* **ArangoDB** -* **ElasticSearch**, etc. - -/// tip - -There is an official project generator with **FastAPI** and **Couchbase**, all based on **Docker**, including a frontend and more tools: https://github.com/tiangolo/full-stack-fastapi-couchbase - -/// - -## Import Couchbase components - -For now, don't pay attention to the rest, only the imports: - -```Python hl_lines="3-5" -{!../../docs_src/nosql_databases/tutorial001.py!} -``` - -## Define a constant to use as a "document type" - -We will use it later as a fixed field `type` in our documents. - -This is not required by Couchbase, but is a good practice that will help you afterwards. - -```Python hl_lines="9" -{!../../docs_src/nosql_databases/tutorial001.py!} -``` - -## Add a function to get a `Bucket` - -In **Couchbase**, a bucket is a set of documents, that can be of different types. - -They are generally all related to the same application. - -The analogy in the relational database world would be a "database" (a specific database, not the database server). - -The analogy in **MongoDB** would be a "collection". - -In the code, a `Bucket` represents the main entrypoint of communication with the database. - -This utility function will: - -* Connect to a **Couchbase** cluster (that might be a single machine). - * Set defaults for timeouts. -* Authenticate in the cluster. -* Get a `Bucket` instance. - * Set defaults for timeouts. -* Return it. - -```Python hl_lines="12-21" -{!../../docs_src/nosql_databases/tutorial001.py!} -``` - -## Create Pydantic models - -As **Couchbase** "documents" are actually just "JSON objects", we can model them with Pydantic. - -### `User` model - -First, let's create a `User` model: - -```Python hl_lines="24-28" -{!../../docs_src/nosql_databases/tutorial001.py!} -``` - -We will use this model in our *path operation function*, so, we don't include in it the `hashed_password`. - -### `UserInDB` model - -Now, let's create a `UserInDB` model. - -This will have the data that is actually stored in the database. - -We don't create it as a subclass of Pydantic's `BaseModel` but as a subclass of our own `User`, because it will have all the attributes in `User` plus a couple more: - -```Python hl_lines="31-33" -{!../../docs_src/nosql_databases/tutorial001.py!} -``` - -/// note - -Notice that we have a `hashed_password` and a `type` field that will be stored in the database. - -But it is not part of the general `User` model (the one we will return in the *path operation*). - -/// - -## Get the user - -Now create a function that will: - -* Take a username. -* Generate a document ID from it. -* Get the document with that ID. -* Put the contents of the document in a `UserInDB` model. - -By creating a function that is only dedicated to getting your user from a `username` (or any other parameter) independent of your *path operation function*, you can more easily reuse it in multiple parts and also add unit tests for it: - -```Python hl_lines="36-42" -{!../../docs_src/nosql_databases/tutorial001.py!} -``` - -### f-strings - -If you are not familiar with the `f"userprofile::{username}"`, it is a Python "f-string". - -Any variable that is put inside of `{}` in an f-string will be expanded / injected in the string. - -### `dict` unpacking - -If you are not familiar with the `UserInDB(**result.value)`, it is using `dict` "unpacking". - -It will take the `dict` at `result.value`, and take each of its keys and values and pass them as key-values to `UserInDB` as keyword arguments. - -So, if the `dict` contains: - -```Python -{ - "username": "johndoe", - "hashed_password": "some_hash", -} -``` - -It will be passed to `UserInDB` as: - -```Python -UserInDB(username="johndoe", hashed_password="some_hash") -``` - -## Create your **FastAPI** code - -### Create the `FastAPI` app - -```Python hl_lines="46" -{!../../docs_src/nosql_databases/tutorial001.py!} -``` - -### Create the *path operation function* - -As our code is calling Couchbase and we are not using the experimental Python await support, we should declare our function with normal `def` instead of `async def`. - -Also, Couchbase recommends not using a single `Bucket` object in multiple "threads", so, we can just get the bucket directly and pass it to our utility functions: - -```Python hl_lines="49-53" -{!../../docs_src/nosql_databases/tutorial001.py!} -``` - -## Recap - -You can integrate any third party NoSQL database, just using their standard packages. - -The same applies to any other external tool, system or API. diff --git a/docs/en/docs/how-to/sql-databases-peewee.md b/docs/en/docs/how-to/sql-databases-peewee.md deleted file mode 100644 index e73ca369b..000000000 --- a/docs/en/docs/how-to/sql-databases-peewee.md +++ /dev/null @@ -1,594 +0,0 @@ -# ~~SQL (Relational) Databases with Peewee~~ (deprecated) - -/// warning | "Deprecated" - -This tutorial is deprecated and will be removed in a future version. - -/// - -/// warning - -If you are just starting, the tutorial [SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank} that uses SQLAlchemy should be enough. - -Feel free to skip this. - -Peewee is not recommended with FastAPI as it doesn't play well with anything async Python. There are several better alternatives. - -/// - -/// info - -These docs assume Pydantic v1. - -Because Pewee doesn't play well with anything async and there are better alternatives, I won't update these docs for Pydantic v2, they are kept for now only for historical purposes. - -The examples here are no longer tested in CI (as they were before). - -/// - -If you are starting a project from scratch, you are probably better off with SQLAlchemy ORM ([SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank}), or any other async ORM. - -If you already have a code base that uses Peewee ORM, you can check here how to use it with **FastAPI**. - -/// warning | "Python 3.7+ required" - -You will need Python 3.7 or above to safely use Peewee with FastAPI. - -/// - -## Peewee for async - -Peewee was not designed for async frameworks, or with them in mind. - -Peewee has some heavy assumptions about its defaults and about how it should be used. - -If you are developing an application with an older non-async framework, and can work with all its defaults, **it can be a great tool**. - -But if you need to change some of the defaults, support more than one predefined database, work with an async framework (like FastAPI), etc, you will need to add quite some complex extra code to override those defaults. - -Nevertheless, it's possible to do it, and here you'll see exactly what code you have to add to be able to use Peewee with FastAPI. - -/// note | "Technical Details" - -You can read more about Peewee's stand about async in Python in the docs, an issue, a PR. - -/// - -## The same app - -We are going to create the same application as in the SQLAlchemy tutorial ([SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank}). - -Most of the code is actually the same. - -So, we are going to focus only on the differences. - -## File structure - -Let's say you have a directory named `my_super_project` that contains a sub-directory called `sql_app` with a structure like this: - -``` -. -โ””โ”€โ”€ sql_app - โ”œโ”€โ”€ __init__.py - โ”œโ”€โ”€ crud.py - โ”œโ”€โ”€ database.py - โ”œโ”€โ”€ main.py - โ””โ”€โ”€ schemas.py -``` - -This is almost the same structure as we had for the SQLAlchemy tutorial. - -Now let's see what each file/module does. - -## Create the Peewee parts - -Let's refer to the file `sql_app/database.py`. - -### The standard Peewee code - -Let's first check all the normal Peewee code, create a Peewee database: - -```Python hl_lines="3 5 22" -{!../../docs_src/sql_databases_peewee/sql_app/database.py!} -``` - -/// tip - -Keep in mind that if you wanted to use a different database, like PostgreSQL, you couldn't just change the string. You would need to use a different Peewee database class. - -/// - -#### Note - -The argument: - -```Python -check_same_thread=False -``` - -is equivalent to the one in the SQLAlchemy tutorial: - -```Python -connect_args={"check_same_thread": False} -``` - -...it is needed only for `SQLite`. - -/// info | "Technical Details" - -Exactly the same technical details as in [SQL (Relational) Databases](../tutorial/sql-databases.md#note){.internal-link target=_blank} apply. - -/// - -### Make Peewee async-compatible `PeeweeConnectionState` - -The main issue with Peewee and FastAPI is that Peewee relies heavily on Python's `threading.local`, and it doesn't have a direct way to override it or let you handle connections/sessions directly (as is done in the SQLAlchemy tutorial). - -And `threading.local` is not compatible with the new async features of modern Python. - -/// note | "Technical Details" - -`threading.local` is used to have a "magic" variable that has a different value for each thread. - -This was useful in older frameworks designed to have one single thread per request, no more, no less. - -Using this, each request would have its own database connection/session, which is the actual final goal. - -But FastAPI, using the new async features, could handle more than one request on the same thread. And at the same time, for a single request, it could run multiple things in different threads (in a threadpool), depending on if you use `async def` or normal `def`. This is what gives all the performance improvements to FastAPI. - -/// - -But Python 3.7 and above provide a more advanced alternative to `threading.local`, that can also be used in the places where `threading.local` would be used, but is compatible with the new async features. - -We are going to use that. It's called `contextvars`. - -We are going to override the internal parts of Peewee that use `threading.local` and replace them with `contextvars`, with the corresponding updates. - -This might seem a bit complex (and it actually is), you don't really need to completely understand how it works to use it. - -We will create a `PeeweeConnectionState`: - -```Python hl_lines="10-19" -{!../../docs_src/sql_databases_peewee/sql_app/database.py!} -``` - -This class inherits from a special internal class used by Peewee. - -It has all the logic to make Peewee use `contextvars` instead of `threading.local`. - -`contextvars` works a bit differently than `threading.local`. But the rest of Peewee's internal code assumes that this class works with `threading.local`. - -So, we need to do some extra tricks to make it work as if it was just using `threading.local`. The `__init__`, `__setattr__`, and `__getattr__` implement all the required tricks for this to be used by Peewee without knowing that it is now compatible with FastAPI. - -/// tip - -This will just make Peewee behave correctly when used with FastAPI. Not randomly opening or closing connections that are being used, creating errors, etc. - -But it doesn't give Peewee async super-powers. You should still use normal `def` functions and not `async def`. - -/// - -### Use the custom `PeeweeConnectionState` class - -Now, overwrite the `._state` internal attribute in the Peewee database `db` object using the new `PeeweeConnectionState`: - -```Python hl_lines="24" -{!../../docs_src/sql_databases_peewee/sql_app/database.py!} -``` - -/// tip - -Make sure you overwrite `db._state` *after* creating `db`. - -/// - -/// tip - -You would do the same for any other Peewee database, including `PostgresqlDatabase`, `MySQLDatabase`, etc. - -/// - -## Create the database models - -Let's now see the file `sql_app/models.py`. - -### Create Peewee models for our data - -Now create the Peewee models (classes) for `User` and `Item`. - -This is the same you would do if you followed the Peewee tutorial and updated the models to have the same data as in the SQLAlchemy tutorial. - -/// tip - -Peewee also uses the term "**model**" to refer to these classes and instances that interact with the database. - -But Pydantic also uses the term "**model**" to refer to something different, the data validation, conversion, and documentation classes and instances. - -/// - -Import `db` from `database` (the file `database.py` from above) and use it here. - -```Python hl_lines="3 6-12 15-21" -{!../../docs_src/sql_databases_peewee/sql_app/models.py!} -``` - -/// tip - -Peewee creates several magic attributes. - -It will automatically add an `id` attribute as an integer to be the primary key. - -It will chose the name of the tables based on the class names. - -For the `Item`, it will create an attribute `owner_id` with the integer ID of the `User`. But we don't declare it anywhere. - -/// - -## Create the Pydantic models - -Now let's check the file `sql_app/schemas.py`. - -/// tip - -To avoid confusion between the Peewee *models* and the Pydantic *models*, we will have the file `models.py` with the Peewee models, and the file `schemas.py` with the Pydantic models. - -These Pydantic models define more or less a "schema" (a valid data shape). - -So this will help us avoiding confusion while using both. - -/// - -### Create the Pydantic *models* / schemas - -Create all the same Pydantic models as in the SQLAlchemy tutorial: - -```Python hl_lines="16-18 21-22 25-30 34-35 38-39 42-48" -{!../../docs_src/sql_databases_peewee/sql_app/schemas.py!} -``` - -/// tip - -Here we are creating the models with an `id`. - -We didn't explicitly specify an `id` attribute in the Peewee models, but Peewee adds one automatically. - -We are also adding the magic `owner_id` attribute to `Item`. - -/// - -### Create a `PeeweeGetterDict` for the Pydantic *models* / schemas - -When you access a relationship in a Peewee object, like in `some_user.items`, Peewee doesn't provide a `list` of `Item`. - -It provides a special custom object of class `ModelSelect`. - -It's possible to create a `list` of its items with `list(some_user.items)`. - -But the object itself is not a `list`. And it's also not an actual Python generator. Because of this, Pydantic doesn't know by default how to convert it to a `list` of Pydantic *models* / schemas. - -But recent versions of Pydantic allow providing a custom class that inherits from `pydantic.utils.GetterDict`, to provide the functionality used when using the `orm_mode = True` to retrieve the values for ORM model attributes. - -We are going to create a custom `PeeweeGetterDict` class and use it in all the same Pydantic *models* / schemas that use `orm_mode`: - -```Python hl_lines="3 8-13 31 49" -{!../../docs_src/sql_databases_peewee/sql_app/schemas.py!} -``` - -Here we are checking if the attribute that is being accessed (e.g. `.items` in `some_user.items`) is an instance of `peewee.ModelSelect`. - -And if that's the case, just return a `list` with it. - -And then we use it in the Pydantic *models* / schemas that use `orm_mode = True`, with the configuration variable `getter_dict = PeeweeGetterDict`. - -/// tip - -We only need to create one `PeeweeGetterDict` class, and we can use it in all the Pydantic *models* / schemas. - -/// - -## CRUD utils - -Now let's see the file `sql_app/crud.py`. - -### Create all the CRUD utils - -Create all the same CRUD utils as in the SQLAlchemy tutorial, all the code is very similar: - -```Python hl_lines="1 4-5 8-9 12-13 16-20 23-24 27-30" -{!../../docs_src/sql_databases_peewee/sql_app/crud.py!} -``` - -There are some differences with the code for the SQLAlchemy tutorial. - -We don't pass a `db` attribute around. Instead we use the models directly. This is because the `db` object is a global object, that includes all the connection logic. That's why we had to do all the `contextvars` updates above. - -Aso, when returning several objects, like in `get_users`, we directly call `list`, like in: - -```Python -list(models.User.select()) -``` - -This is for the same reason that we had to create a custom `PeeweeGetterDict`. But by returning something that is already a `list` instead of the `peewee.ModelSelect` the `response_model` in the *path operation* with `List[models.User]` (that we'll see later) will work correctly. - -## Main **FastAPI** app - -And now in the file `sql_app/main.py` let's integrate and use all the other parts we created before. - -### Create the database tables - -In a very simplistic way create the database tables: - -```Python hl_lines="9-11" -{!../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -### Create a dependency - -Create a dependency that will connect the database right at the beginning of a request and disconnect it at the end: - -```Python hl_lines="23-29" -{!../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -Here we have an empty `yield` because we are actually not using the database object directly. - -It is connecting to the database and storing the connection data in an internal variable that is independent for each request (using the `contextvars` tricks from above). - -Because the database connection is potentially I/O blocking, this dependency is created with a normal `def` function. - -And then, in each *path operation function* that needs to access the database we add it as a dependency. - -But we are not using the value given by this dependency (it actually doesn't give any value, as it has an empty `yield`). So, we don't add it to the *path operation function* but to the *path operation decorator* in the `dependencies` parameter: - -```Python hl_lines="32 40 47 59 65 72" -{!../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -### Context variable sub-dependency - -For all the `contextvars` parts to work, we need to make sure we have an independent value in the `ContextVar` for each request that uses the database, and that value will be used as the database state (connection, transactions, etc) for the whole request. - -For that, we need to create another `async` dependency `reset_db_state()` that is used as a sub-dependency in `get_db()`. It will set the value for the context variable (with just a default `dict`) that will be used as the database state for the whole request. And then the dependency `get_db()` will store in it the database state (connection, transactions, etc). - -```Python hl_lines="18-20" -{!../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -For the **next request**, as we will reset that context variable again in the `async` dependency `reset_db_state()` and then create a new connection in the `get_db()` dependency, that new request will have its own database state (connection, transactions, etc). - -/// tip - -As FastAPI is an async framework, one request could start being processed, and before finishing, another request could be received and start processing as well, and it all could be processed in the same thread. - -But context variables are aware of these async features, so, a Peewee database state set in the `async` dependency `reset_db_state()` will keep its own data throughout the entire request. - -And at the same time, the other concurrent request will have its own database state that will be independent for the whole request. - -/// - -#### Peewee Proxy - -If you are using a Peewee Proxy, the actual database is at `db.obj`. - -So, you would reset it with: - -```Python hl_lines="3-4" -async def reset_db_state(): - database.db.obj._state._state.set(db_state_default.copy()) - database.db.obj._state.reset() -``` - -### Create your **FastAPI** *path operations* - -Now, finally, here's the standard **FastAPI** *path operations* code. - -```Python hl_lines="32-37 40-43 46-53 56-62 65-68 71-79" -{!../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -### About `def` vs `async def` - -The same as with SQLAlchemy, we are not doing something like: - -```Python -user = await models.User.select().first() -``` - -...but instead we are using: - -```Python -user = models.User.select().first() -``` - -So, again, we should declare the *path operation functions* and the dependency without `async def`, just with a normal `def`, as: - -```Python hl_lines="2" -# Something goes here -def read_users(skip: int = 0, limit: int = 100): - # Something goes here -``` - -## Testing Peewee with async - -This example includes an extra *path operation* that simulates a long processing request with `time.sleep(sleep_time)`. - -It will have the database connection open at the beginning and will just wait some seconds before replying back. And each new request will wait one second less. - -This will easily let you test that your app with Peewee and FastAPI is behaving correctly with all the stuff about threads. - -If you want to check how Peewee would break your app if used without modification, go the `sql_app/database.py` file and comment the line: - -```Python -# db._state = PeeweeConnectionState() -``` - -And in the file `sql_app/main.py` file, comment the body of the `async` dependency `reset_db_state()` and replace it with a `pass`: - -```Python -async def reset_db_state(): -# database.db._state._state.set(db_state_default.copy()) -# database.db._state.reset() - pass -``` - -Then run your app with Uvicorn: - -
- -```console -$ uvicorn sql_app.main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -Open your browser at http://127.0.0.1:8000/docs and create a couple of users. - -Then open 10 tabs at http://127.0.0.1:8000/docs#/default/read_slow_users_slowusers__get at the same time. - -Go to the *path operation* "Get `/slowusers/`" in all of the tabs. Use the "Try it out" button and execute the request in each tab, one right after the other. - -The tabs will wait for a bit and then some of them will show `Internal Server Error`. - -### What happens - -The first tab will make your app create a connection to the database and wait for some seconds before replying back and closing the database connection. - -Then, for the request in the next tab, your app will wait for one second less, and so on. - -This means that it will end up finishing some of the last tabs' requests earlier than some of the previous ones. - -Then one the last requests that wait less seconds will try to open a database connection, but as one of those previous requests for the other tabs will probably be handled in the same thread as the first one, it will have the same database connection that is already open, and Peewee will throw an error and you will see it in the terminal, and the response will have an `Internal Server Error`. - -This will probably happen for more than one of those tabs. - -If you had multiple clients talking to your app exactly at the same time, this is what could happen. - -And as your app starts to handle more and more clients at the same time, the waiting time in a single request needs to be shorter and shorter to trigger the error. - -### Fix Peewee with FastAPI - -Now go back to the file `sql_app/database.py`, and uncomment the line: - -```Python -db._state = PeeweeConnectionState() -``` - -And in the file `sql_app/main.py` file, uncomment the body of the `async` dependency `reset_db_state()`: - -```Python -async def reset_db_state(): - database.db._state._state.set(db_state_default.copy()) - database.db._state.reset() -``` - -Terminate your running app and start it again. - -Repeat the same process with the 10 tabs. This time all of them will wait and you will get all the results without errors. - -...You fixed it! - -## Review all the files - - Remember you should have a directory named `my_super_project` (or however you want) that contains a sub-directory called `sql_app`. - -`sql_app` should have the following files: - -* `sql_app/__init__.py`: is an empty file. - -* `sql_app/database.py`: - -```Python -{!../../docs_src/sql_databases_peewee/sql_app/database.py!} -``` - -* `sql_app/models.py`: - -```Python -{!../../docs_src/sql_databases_peewee/sql_app/models.py!} -``` - -* `sql_app/schemas.py`: - -```Python -{!../../docs_src/sql_databases_peewee/sql_app/schemas.py!} -``` - -* `sql_app/crud.py`: - -```Python -{!../../docs_src/sql_databases_peewee/sql_app/crud.py!} -``` - -* `sql_app/main.py`: - -```Python -{!../../docs_src/sql_databases_peewee/sql_app/main.py!} -``` - -## Technical Details - -/// warning - -These are very technical details that you probably don't need. - -/// - -### The problem - -Peewee uses `threading.local` by default to store it's database "state" data (connection, transactions, etc). - -`threading.local` creates a value exclusive to the current thread, but an async framework would run all the code (e.g. for each request) in the same thread, and possibly not in order. - -On top of that, an async framework could run some sync code in a threadpool (using `asyncio.run_in_executor`), but belonging to the same request. - -This means that, with Peewee's current implementation, multiple tasks could be using the same `threading.local` variable and end up sharing the same connection and data (that they shouldn't), and at the same time, if they execute sync I/O-blocking code in a threadpool (as with normal `def` functions in FastAPI, in *path operations* and dependencies), that code won't have access to the database state variables, even while it's part of the same request and it should be able to get access to the same database state. - -### Context variables - -Python 3.7 has `contextvars` that can create a local variable very similar to `threading.local`, but also supporting these async features. - -There are several things to keep in mind. - -The `ContextVar` has to be created at the top of the module, like: - -```Python -some_var = ContextVar("some_var", default="default value") -``` - -To set a value used in the current "context" (e.g. for the current request) use: - -```Python -some_var.set("new value") -``` - -To get a value anywhere inside of the context (e.g. in any part handling the current request) use: - -```Python -some_var.get() -``` - -### Set context variables in the `async` dependency `reset_db_state()` - -If some part of the async code sets the value with `some_var.set("updated in function")` (e.g. like the `async` dependency), the rest of the code in it and the code that goes after (including code inside of `async` functions called with `await`) will see that new value. - -So, in our case, if we set the Peewee state variable (with a default `dict`) in the `async` dependency, all the rest of the internal code in our app will see this value and will be able to reuse it for the whole request. - -And the context variable would be set again for the next request, even if they are concurrent. - -### Set database state in the dependency `get_db()` - -As `get_db()` is a normal `def` function, **FastAPI** will make it run in a threadpool, with a *copy* of the "context", holding the same value for the context variable (the `dict` with the reset database state). Then it can add database state to that `dict`, like the connection, etc. - -But if the value of the context variable (the default `dict`) was set in that normal `def` function, it would create a new value that would stay only in that thread of the threadpool, and the rest of the code (like the *path operation functions*) wouldn't have access to it. In `get_db()` we can only set values in the `dict`, but not the entire `dict` itself. - -So, we need to have the `async` dependency `reset_db_state()` to set the `dict` in the context variable. That way, all the code has access to the same `dict` for the database state for a single request. - -### Connect and disconnect in the dependency `get_db()` - -Then the next question would be, why not just connect and disconnect the database in the `async` dependency itself, instead of in `get_db()`? - -The `async` dependency has to be `async` for the context variable to be preserved for the rest of the request, but creating and closing the database connection is potentially blocking, so it could degrade performance if it was there. - -So we also need the normal `def` dependency `get_db()`. diff --git a/docs/en/docs/how-to/testing-database.md b/docs/en/docs/how-to/testing-database.md new file mode 100644 index 000000000..d0ed15bca --- /dev/null +++ b/docs/en/docs/how-to/testing-database.md @@ -0,0 +1,7 @@ +# Testing a Database + +You can study about databases, SQL, and SQLModel in the SQLModel docs. ๐Ÿค“ + +There's a mini tutorial on using SQLModel with FastAPI. โœจ + +That tutorial includes a section about testing SQL databases. ๐Ÿ˜Ž diff --git a/docs/en/docs/img/tutorial/sql-databases/image01.png b/docs/en/docs/img/tutorial/sql-databases/image01.png index 8e575abd65aaf37a33fed5bdae4029955dffb090..bfcdb57a0743c5fdcd453007987a19d878af1f1a 100644 GIT binary patch literal 69755 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 literal 78949 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

Y^^(*eiD{sbfJ%(&*CDpE(Ud6wW` zV&t7#?fkN^Ywaq&CLthG#y?qJx{>fr`>X(}>0!Md+r|g8N;jRn-bv39QvC4J9B7-# zKnQ{Nv}EUdgT-tr%B}8RQ0{IywNFvt&EoI7bK1Lt5eAGRE|{RwyQAom>I(@T%M{4) zTfgemWI9zg*XE73zQSgu!E1T7 z!5XleBW;v}8#Y~d+Dcna=q)#gNU+aZWNtAM~29lKhEiKK0n`Q-5CZ2Q0(X8jAZWek4C#k_x30Z_DY4U&R_ePo_nW)^4o8)*zQCtmgutVWKKk3nF@LsL!!Q(= zDtgl|!?9@(_q+GR^5RXg?(_B7YA>ke;A0m_O|__a1s}85bS6j9#lPRbLstWeWaF;t zH0hJVh$6iPxmUvHluC)xI&Gqg!O^ZlEf9;TqbJNnV87V|J3+o+)ulM7h7fnImdH3a z`aQEJ6p49~+w;oluZb*TPaa%qv0*gmjmE@IYfEk4B|vuKty9SzMmb$;?>DGpS-^mi zBn8c)Umes~R&~;vbt+3$ z@9d6B%Hz&92CE;PzhbKE{oLOO0%Um=x=$|6Xxxx#92qklrThnl zF*T=k*i1f?P@U(xJVa^Hm5Xl|sx++c8ZPswMMTIXt>^t6b~Q;Dz0X{3Z;QoxyK~c& z4nJ%p|73R(?Ae#+d>`na757obiW8$;w9>S~fgXq#8gT3QyBv|>NVR9Fn!pD_P}%G% zQCQ{J{Pq}O%VU71fmrstWuQu3qmhHf9cuh=Gl0j=_N{lzT%L4>nh%0(Uq=-&Cap>B zX;kb>OU`|a(X4y+aOEr|hih!PXZtU19>!oqdL^$KY;A9{A#U&3D3WSdtpN(g?9z|l zC<)EazW5bNEr@#946Qw)dp^wnG)|acmtN^rPoU}_QSN`4v_jrKm1vQt6VwFZMNL%v zSUaygThL_s$L3-XTk*7Ufl@aaLVTS21LrWYEjMfp-uC_xDk#5&W<#m839BizH~PYD z@1MuAm~d~oYyH*NRJYsZ2-8>_c~X1?dO1q{#ZSHVMwf7fe!M?JvXO2tZ23ZVfUsdX z7Z z64zchWcCYUFw)LPkRahv48K}e)*!K0Pse;JelKI^c(hsf&9{kdqEqt@@WG68)lE@C z0Og~;a}CN(C?Rrc@>{X&r!8Y2P5G|u-;vh80#xb2n`f~^pg}wC^7KECZC_*?p`3~Z zt}@La(_QSzzb8m@a&(7c!>WZZqp?^A56h(lv-9!7)X*3ww=1WqUs}~<0t!g9wzhV7 zY>b?TC)lPnOMPYTCmD$Ywm^%MGU~RibNiH~72$m10}fQrxuiY{p7$;@6iKg;gb2Rnt;Q!JoKi)_ zfl6G-BcV)uK(vDB{k&}p==LQv>b_ajTtGGH6{vurLtN)rrcc=CUh$esPDuI-tCDbX za4$$-*j~TnR8nh2Rjt7tY4lO4ZO>RH4~-B@ZlCy>PhKe*WxSx0+-&@jU;jl_ER|K! zWZ58qQ+BVPH0JQmqon((ZtwfJCEV|})H`CwW*)$28^^tJef1ZMyEYwg?^d<3 zB_{V)E`a#rvR7WtzkRLuo|;w|Sv4qCq|SUy#cW#Bl*3O0z2z8H??QuF@?B-c-NhD|LtU-@fCu zjbc^Cb5??yt^q+_t5WeT-ZGN~2Z%JsiKZI5-@y^)73@lM=zrsroGiF0Yfc2My|Cb= z0Ig?Kl22MchDd=o!icZjx70xeDq2x2vFKlj^PeUS4ZA0}n|gEOQo5EPdqy?*y)>Ke_HK=g# zIACAZAV#*!cv}9vA>Xm-*!b}Q>4gimAk!4bw5i_cvS$7wn=jSh6Xa&z_c6PCL*75K zG|2iP{Trm`w}B5!+W@wA1uVa|Fplm-+LM*3%|2BFPvT_ww>)(@nn;$8rSrpmxQ+Yl zXh8^boeP|0aa*;NMRwm?uu#wjX4Gv_9x5fG~-#Q{$)O}PCb&2 za5$jDww9kVA`gpffwus6&Q^TpDHI>RmA<_Ida}4|=FDP!^;UVH^GnRg>Z)3#(a&fr zBd!09+B?~ z0)Fj3k=LKd;<$Y+FGCsbw9>yg29`B#e*!WF;(nP=lNmg0ySAV@G|n*?PBfQ!@_{wA zl55%-Cyu5lKENKXVEe|c;uV0MfdxHwf9swR!>8JRaF3N-#wR5LY>0F(HPB$;1-!cQ#xAABn3!}Uf!T!OLo>9aXw20m#i#ezVZHqcX5Blo4! zRXn$j&`@F;9d{~xs5dQhxx4J|M5mq~ViW{X)N^vrK{Te`P28Tyq9dShS+e{in`xyB zWk=>^C>ZpUE|!=>0nMJNQSKj79B7&$6?yEOK>m=*Q3YVh;XNlA58F% z6dmK+x=-%dPRB5<2)!=uDn2LRo=rY0LC7DUziw|kSEy9DWSDJd2ANA$N%i{#4jL=w zhxaI@k{aejF~0K%++N0oq8gDSS$BHz!*Q0So%dRUCYjw{*7G}=0cVj$>ko;UqHcx{ zu06M`wmTlAZRu0(R`ycWPeDb$wO3SnY<`u#2tWn{aV`3x7c#BNk}%$0v)*btEPzllz@ zzqsrnaiKagwISzZJLCFHL!iT-1wdBrYO_K-X&1O)8N;w~e8i)#R?5Y*b2-3{)bvgh zQJ=Z5P`uFWyfQ_pc{ui!2dCbzGz-MA{l!A066>YYxN*zF%>F=rMzZGcZApJ5&lXMu z*6DYVwE-tQ&TzTjvOX9ly=^K@9$&WQe6)=8DD7-#lMH@4M-G2!fdTsR6w`NOZl3Pz zyN7UnjvHUGT*MHmDhqmZ;JYN@`xX$7YtWmBEI$gPEJN(^w!1azIv!%W0MI8H)DY2E z3prW6y^ROztz};BGA4z{vURa#4aN@NH}(1qXho9uRO5ceQz;CHlhKH~tYc7wbCh{Y zmFZ=Ul&9Sxh6aO7Gv9=ffh3rH&ke!FyTw|8PUu@|I>C&2Kgw~=-<~ z9!Riyw?mvSq!#+vfKe#$3*2Z%-}W{BTryjPHy(A;!Ksn z3aFkLOcyUz&KTvzCt7{{4Y@n9AVtoz(KC@gQh%;CHjL}X7A$bWT;aBUv~$15`MdB# zzLpFqenWby$0asaB*2hD=XC9xNA?tm-;pRI_qdp6+c}%Y$1tP9(P2O>JL|~*#W2e4 z`9_$3>X8QT*)4v(Z0XEYZQGskcXl3l5$ZkRs_d6wgt{^%oS%)T_5ulSzk6*2fb+G* z{15$YCm&d|Q)O&CbHj%v$PSq1K7eC(|6%{ROv}XZxiq;@7w6Xxr-xa0GjDxwizg|q z`ufQI^$q9qAUF$5*1QBx49E|x*IGnd-_q%gSG_UOdeOtNoDc<*yv>b3VZ zNGhs@6?Efry}y`999_}*bO$v6b_Mwz9Cy-W*dL&Cdhlko|K^&{XphTAv3TC`=qd3u zKqYwaDy?lq-r2`MqWj>Nk&7WGuW`z*kXeh?QC`6NCawTMZOiF=S$d&hN(0@67zE5( z^*b>*zUXYyTz%``?GJAXV;-kl%-8X9Z$N;;r|*s+{VFr_o5cW#nXhsyn(92!H7x_|~Zw*cWe&!&!|z zJ7K+k(|yI`lLIBXq6pF9uwU_*Mvx)`gGDR27*WxIYLqEBG!VPMH@Dun-m7IjmCn18 zVQ(zXO(WP(t+1E_o;fvl$S0Q}bf%TDA3vAe;e7=BER<}uOCyzD1jL|V@Tzb`J8UvotB%COXDqqrRVt~wb3RF5wUVbN3 z=+p`a@>Wj2d^iVFzH4jiPWSp4On|cT)N9qAXFWvc0Bp~wvS6tW&Q5PS}6b7p>FbXkz)F2A=xl{MeV^A zfQAD^(ZX&mpB7n4pvx9PW7&H~BnLheC7hgh4r@zqj|3ni(`~lOv!nL; zi{PViFSHPV!4&Ygu2v~r+-E@lMObOpIypKRWlLz!y{dzGiL#IWqe5)H%21$I0l7Q< z*>w-*GiYKe-goqpp$m5G{Cn^t(M!$oZIn!B78jG&cwwi9lq}lm`pdxp8UM zcBjWY5IrLDg6AmX6BCqdYzf=j7%e9*CeE4>(LqDn*Zb4TpYQRPy%4|dy66zIgO@mj zgp=M~)uTXz_m*^DlEJzo?2g|b^J-236V^bpMY!~nw22vXACK=T!x3Raffv2$U7gO} zD-9azE8Li>s@Mbs1XZ=3=O|cG5;ARP#z%AIzK$(UG`@diKoIT(CjRtx^@Ed*I-(rC zzNM2&$I5)(Va%193x;LKllrUjatRH#wqaFp`hnYNLSFu2XT!fVC`Tb?9RFxCFkM1r z>&hVO@(Xnrup+?@yrX^dN_7JhqruGMYX}CFgDau?vXnMA6`!ZuLw2u4>G1?FIRS0; zac-Oe``E1)`BIE`{bHbHF0;QWwmZ&Z@mtc%7ltLJ)c>chtBh)^i?T?I6nBT>h2oS# zaVdqM#hqfsHMmPDw73>0Ere1i?h>T9Q{0;bcL-VtGU5AX&6-&=lOONLy?J)-%iU-1 zeO@jH*(PEo0{)_IoQ7ujUp|bx2#G?`WW(8xMXB=P2g?B-c?a(}eSJ@Gd1IWHeR9|$ z8zFt9ksL^UEqa6uzU#vD73NL%^B;7Kpd>8L{;-p5Pj|UQ_#nTdo<~->LQCzrj5*R}>V1Uu(24d|>HUv`dhpR&YzQW9 z6^Hul+q#U$ykbgj%1F%5dF?(6CF6E6XD`tkp+L^#gv4L( zS+KBu>)mDab0bq>?rmy*afhSaJ3$g2>np;!5)0DEc%h0rmOVQ*opOX{{$!{~at7 zGF8kj(x%Q9d9a1tIsJR*wHoT2h;lEs#Oka_$n+W52iH~eJnpTuCzL}B1(J6R4!CZ- zWc~ycQx!*tj&k*PP18tbiW=*F%UtNIp9;kJD;egZMI+{e%$%vz<>Wn9$iL+d&-aur z?0_ojL#_xt$Fki5%ZNl6$0~feNjzph3o%YO6V(?QtpUL)- z(ie2TRB=uu`qUO9RP-NJE5^dTY-EauMZZ^fJG9eEBpk7NeMc!tSDaJqzgd8(dmETR z!hzDmT{?nWSy-QbE`i3)-86f9J_OY|KgZ7Hp4zyb+uqCxq(yiz?{pf)m8#okyC*Rq z+NWK#2&g1W^k<{jQ6)Kk7eV@_z=EKRU-&=v@MW(L^@3g^p^x4T-svnk{&!yJ^#{F9 zh_0=tj-TVW@{h>T)#SFsOs5Ul!*;>cc4T1$ac?-bs}J(+yD0Sj>_vD-JtC+BFm98I z5apJD2I%xU>VCdXXZ08(cU_6ke}5W;yN59Zz!x|M`O7Lh1i=FQ^dS`S8451kaVb zV8`syXdoFB>{jjUTr{Lp9!3L|c02XhDivdZcBObL0iHp>2p*k%kFaFnb*BX7t8~{Y z5Sjbky$VPhU^$7t3i9S4SmWzobm#eWbwi>-S*Efz>ESGyrTmRU{YT*139LY`B!jr8 z05qoX@k?AtGf9Q-D|1weEPdfCHOs||b$s=+{D;NsT4cYAb8KCAM~bF~gdbd8`tU~V z5Z-Fk%t@PpwL*Pws}y5}!Q~bGqlx^{iCMF;+yQGZ5vq8EcgIG%x}Y z$tyZVA;W-7_=Dy#i_3h4-x?~J8VGC>A+X(TaFE4a=I#`?a+^Xc^m+|1RP$tW1zOwNOrzNp0JD5u8?tY5BQX>8VIeE7r>2f^D*Gfow)@xH8imVh z=6y+{1JHN>@Ia_OioQSiDUa3MJ+FVwpmp{Y1uuzhrODNYYu>mvpNW^-!`~~dH-b)o ziQ0W$0u5#-O1$lGVdpsoLcRDh9a1St7CIg|1yc6+KiA|L7aMKycQVqS$Y6|*fp1hK zW-+6ok);7%%jq6Pw-r*UbGLS%2ag0_;=K%P=YHHrYUu zDd{OaRm5ohWfo~Re}?di#LH?jUGCm3#7H^Xq7!H7vT8bNfzORa;!$R{keM4s`cL#%E@-2{`X(lXvz_@0&1l2tCPf@9xr+ z9M6d#wibsZ0K&1Gv}^FzuIGku@h^!f_sf#dn^u&XswlZty9-=x%wF9#E@K994kEa0 zEWWuL$;_w5mA`d(g6=Sg8+IBzE0qqm!8E-+_6QS**Mz4C z1$_JTSv-&28fw1-c`voyY1>DfymPCK%gOg{g;sn*dUi8;CYS!)8A_}S6C%mMX|bQ* z2?cQ(&l8erE4L^}G+DPosQtHmEWkkCg(nG*xeOLbPR`JljL3kFFr74(F%J1DytfTW z&ke`PsO+{QpQ3UU;we-a2aw45hB7GgCx^_gm)o22)!rY5g^cB(Jw?1r3Fxx@ka}6k z1m`OcE}9Hl_Tz!P440E4?VJX$z#0((Gj*6@WE6wMWSd#=(3bSp?7uW9Up?OzmYTP{ zTzV}f6-f2;=h&&*-C4JcH_`DeP_iYD!DE5^NGK&SiqLwMfJZ7elABg*`E|1Y)|0Rz059I;qE*ybQVjA+4}>UPql(U z!NQ~56vciUj?tY^S&K{yAK`j%)3qKU0k%SN^3Rs3lTL)|YOJ4i#oSrh6 zs@X){s~8?`jE6203b{e~q-s!qTcZXBy6?$VWXDYIv|Un*RPnn(WRkOjxvlI>pSq7b z2rciOY&OC1gOQl#fg?0a@Rfb#>pgU_m12r}W0Zvx*Piy`%#RIK$#Bp9AYn>zRdI%C zO}Rj3YDx02MScmg=BqMyRIxFyB@EL@eEDBA&l97n2xk-kAAee`co=k9s7n26B^?n=J9RLatMzqj zG*8vt_@#z}jIck1bKyhl#MjH=x^9)5E3!vQePjU})Sr*)iedwahn3Fle%9j6HxxFOakzG>TxiH<-&fs{&;ThjNNuAGpmC797@^Vay+74!7oKp&}=E04kAnJbUHi z8iW4yYN-UWC!Q;s-MH@H`9uq;FYLdIRd8H*{m%J0mMxDhnW2uC6>1K?ViNu@2l9d* z&GH*3)8A-YJG<1ZEC3MrWW`6KwxwKANq(I#z-yGfA5(Ic+0 z$_mkd`0}-EQi(P7sEWjC{xnpb##@;(+#8d*IhUX5Gx0he-rm1{{X&;$#SYc-khfY5 z<~M0{z0H{_QoZ_#swrB}|6rUYE@@OwOsEI?N|@wxMGKG&vK$?28fhSsVF{dnj_)7( zKfq8UkcGa+;qLO4qU8iL-$$w~7nrEibey}f}}X~HvI6GbhPZv)<#kx7p<0x1;$04`pxx=Z@)T7kl zBhQeItt%~U@sDOpjnokYohf6{hF07(3X?dDCBQq~^!AHB=|`e5LFi zlKx(re<}iy0+v=)SFgvJ9Yo{Vxv^(F-6}Ih(i8CwfDgb2nw-g#p+OPnM9{9f-+(1t zkZXm@3kS*JqqfBRkZYsa%HRY&=rhD4m6Yjm_DIO)oOqszM)(&m;~HpC*dC?SaTb%3 z@5$Rf3L-Y3fLi}NJf9l12s=#}dme+?U`EX<71Ykd_-Mdobm$0pyZdKj$_gyB`xCkm zJ^Vdo$$TueL#AzFCBMhHF9Y9NRtZyL*a_)nU9vtq*KBvS_d>lRE8~#iuq_9*BD)RR zmucS}xbM}~xH0Y*CFQbv3%aM%&C&Hlw{g5hc+K7uP4|sZxS;CaRyT8%5U0m(Hx=>2 zo1yUaZM79uqEib;-t>uQ;cH7POpwB&<0tLG_dpIk$UwQMRF8fJJedsu@?62>aPNll z+XM~sF(P-2ea>=k?^NwBblXowJ-N!3xfG6XhazM}C_G1+JdBJ((~7j}5`nymp0NbBgpQCIQ04lyg{+C^OW(oPT~LFfvptkK zqTV=C{1?1c;KDm?U{Jer;On<0Y(6TBWdWed%w-02YIW(w%TfiFI)hJ2NWS^XFiKMF-8}S2A&>3 z(e!c2E`=o)ulVL%is9m3b9LaQixnEehNsj&t zI8eeMR;3+LSY=B@= zhTF##ra+NUt(Ot#ona-O%|3(jlo^}9Z}0Sn00ryAqE*g_xu(Fw9bPM_w%=puRR+^Z zWn<{)0z;^DyUTcUq}#GT-Th5^EHyrO+-U6b2ohhZ$*@0|!p3#zJ$ZBCR93zdnP#cR zkf#yHW6RST#ZDHT+!RXH;cqR0#577X_oon58|5Ib02i>n>H19%#33GL82qvBDqhG8 z>?2R2{*acdCba!Ez&G>4o*XAR@QR1eu^d)zW*LCB2}ESr3bG&f-=^!-6A9S=VQKdn zRK{YM?-S;~4Ii4O=eXa&;2XxrC`mJK3-ec`d3~92deMMYv#Hio&o{=W6vloII4*Fy z`ZJoUDXGC{GQ|6y*nb0R7z(Am+ZiN1G!u_?Gk-T0yL8#PU%hqaw}_qa{^F)N*S=`m zs^VEhOt~gv-nQ~F>R#{aJ*qBn*6mXAr9N$ER*#HpMzp9)w(okEVhXmDTJ)Z9t!JbQ z0Z)&f5%~(Sy5W2TxHtJdqcOu?(9P=YZN6IP+-J`O;a1yT(A!4~wv~eqp(B_bi(?aS z8NWrySRcZ^k#9X(IQu3C+s@KjB=FL8TkG9woN5V_kTAiVj?a|^I8gTb10c)# z19b)D+v1&0n&s9CCPY%+gUjA#xPW|T%qs9Heb_&9V(87D9uc(ro%GGGH6~EsoOG&F zn6OXf>gF0!IR%WT&6DPC)#`gj+0t$4xYK3=tP)qt8i4z+YpI=o7p>XBllv% zuIrn+^E{o!WCZ;i!ZDUKDKx@^?=?kRJ#*!iC9I@jvc7l(N>&15;lBX~7j3tUFB>_J zIWyF)yH=aQQ5p$Was@OB11dP3aNAw+fXl?$sFLEy7n9vzN;7is|Ryjr>o=6KFgShXt{P$Eot$d1w$XmM%aV^BNZ4WQRvCn*tNm zx%T*m*r{M|$wZ_CBW92bOAOZYe{tiK55bP(%oy-nU3ztkfoYF8XicWop zj7V(mFFE_48*RMlx-<1;YD{Rd&&Z!nFTYmx#mBN0;A!@~kGwIhky@}z*ZInhx`N=l zB7EzM8g@07@O5T|fng2K&p1eP{|~;40XTZj@PkDJ&rsG#4L|n$JT=&F*GA}5Rp1(> zBWBWXb5;MQXoo9>!9(#*2@$hLh2Ck=5?OkZ_*V6h;IDbYMi~g*119loMO7ty?UPJt zU((MXR5J7EzomNB^r?aCN^_wGzC!H+iT>}}BBpOw$Thgx5PpuTXaP5Zf9vSczpSbm zoV;-L-$Y@Sf36;CG4dMn&{1}PwxlwZoBs{(ocJg!HLrCZ|d@J3Y_u5gY-%mJ0%hNSr+zqZM373ktw)65T zc9fxB>0iw7c{`Iou$@(#m0|d+GXF1uNp>r>fa{amXrJR~s@ZD6Wfl8%!yyjB0^60= z{e$?qHaSC(F|71wMT}ygmF|8y8Z6ZJKx`}i;_Qq#{>>}k`ZHBga7{q?rAVsA#`8JT zrQ*w8W%fW_##ga-*~l*mtG9u!>tmv3Mg|Wfkv^K`MsjVxM%QsTg$%h`uC=RQDXR3U z3Gj(LJ9{DjjrbR=naTOp@+7bisR0Vv`FxEfOQ_GV?+c9}sSMq#dMEdCg1I4oUpI z>~=bTO61WWsT<3Jf;7BrPu3!_nJ>brmG8`zPiD88FX7(%JZ`nmP|q$3kz!;faxvbp zVWD2`jxuPRF(yWx*X%eKgSWS2zItX=ytRcV>R+T^HF)z&AZh{-jc%C2c22gUh2)*x zSq46-Be!cwDfU?6KipibLz{NnY&=rpiD3imQn4iCU}6(A5C{KAMZt5}+kTDwrrSV> zES|Ssv>0i;5jh}bnxFnh1sLoV^Kn}t50y?ldi3e{+v*%FRc1iSb=LR303R2qyty26 z30Dal8;5b4bxUi@r#eNkw`Kqx>$b;&XsW2&h>0e4?wo2~Rj+!88UTgVs4Xg34_mf# zR&&gMeyF>mt`K-sV(>w-64Px@Fe>W*bki|W0W~Q?2s*W8QZlzA2~fK%gx>t9TFN+f zLi=)^oUB;L6M_2XzD&rovkQ?M+OVRK^~Jr4S{&5jYwrJ&Fwdj$^k&r<2{*4{Kc_a( zmRC{9evC?8CYb;LfVC+A*Z0Qa@%vg^<0K+PfqV&5mb5k2l-)=bF>3!w8uv%2XaZ8a zEFZz_U#RfdGCy1tz=JUieQ}XRU;d$wd`=!sJ${rkpV~kO4qT_JA~@gGAIY~=s| diff --git a/docs/en/docs/img/tutorial/sql-databases/image02.png b/docs/en/docs/img/tutorial/sql-databases/image02.png index ee59fc9398a4607872d7aae1d439fdb71c0a706c..7bcad83783af805276cb25bde4d3a4b384f92600 100644 GIT binary patch literal 69197 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 literal 83482 zcmY&)&-3%Q~0m>FG&d#Qe#sSl?001#SQdCIAef2!k&0S>xvwNyyP201I zl;kcsXgb|5C(JK`lDhaw<@^Qx>p;W}dihi|q?n=#isBbY6;JqBfTZ6S)Y*iI2gkAT zSL1}(T3FaGLYr@$uRUguljEsQlbNY3ERTAn>Q#CGKNR5~5isy@j5yfIVBr3O#3(Rd zJ|9mM#1ZjxCpBE_RUPk*!nrvXY2bi}JZ9*JUopRoG(!x31d05oDKW}BlK}$ENIdD% z{3;#hlJC|>ETrFfq)#~P3_{;KsOIew5YdpxZ*D|Pab1}TXn5z>F>WPw;k+`3L!rJhLk1wbo(-7cK3_LtX9vS5Ju zP$NVP8$!JQ=fCD|K|t#XS>=bFBsG9)Qx8Gf!j~6@9hFRhOxFRR9>|mU8_`b!8`K3W zDUSIo%B(qYfB)}a#w0C7fNe@yc{we^2WKN!lsG}(G!|t=h#@(J!~-G=WC;d9;o`4zgfy#D9j35&nuhaxLB0MwIW=7}w)j_kJSm=1S5j%9M_6OG(I1y!K@Sh+KQ#koU(DD zTa=0c>=-KX7l7a#dY`A|VOc&IO$;XVZv?uzZ$TQ7{_8QHjsh?NHVo{>eT4!<6j~-9 zC0ZrvO)$CaA+IPJH+aTfk@H8GaYiejutH(X8F|gVvoQ3)NYrbnbQG?$n<^*;MZ2mI zzMGwdwn1*R3~*^+{!}ZP`!2~0m9~QcmnoFLQyh}5>squy27}s*K5^47UN)0G6K_Jt zp)IO_4c>P*j&og}i8qh1u0MH_&Qh00XZQ&hCGC#|hnLeICcr_3%I`a*>^W z109K5T&~q-FVU%8^3-NNh%0&X8V{|Wfz;~fAV-lHydfuj6D3WAEtstXx~hQ8OUvg` zItGnRST1FkL62UJrro-Ro7o=TCvA!jL-X^P!U%LJaeuQ?P9m%mT;kEo;?dD;aNTL- zeH06Cg$??^LJ@8-0}ggahK1Ruj0I~4A)<>FILj_tHMBjRBOx%d>#1{X)w4AY&7z72 zomA(G+r}jq6lh+|=f%7=fy-rV!aaA!f0dSoO= zpE97Dp%HALhM5;Hg*f>~FK- zL)JE}mR>TwAHoKP`h`=$2-4f7XYfwiNBMfKQ$)diuCWPES0NPI#(92}esxwm;i z2S1gC1~CPXm9`D|m#Hwpg^{2fHnHSEOK(I5SjW#;10W`4X%$S+6Z+n>he{R9PFX2o zuk&~})3@0Eb}C+{M%<)gU1bdS$P)!K3|Oj$aLDoFgs<0k~PtWG^Bn&cRVDn8RM^95GtS26T!m zI>R!d%?999+X0<6&v-JivAutf2#yc}rLQOBlLl}-Hhv-NVDuL5Igajr4plo`rk^WUQ$&z-n zUEhEzEtg7P)Rd~KmYQ`(h#G3O7%1V@d*M%r1G+;m-E88M$KOCfR#)TER4Z7(K1&1u zd?ug)@%f7<=q8flFqFxAY}8uzz>aTFX^IxCnc*M|Ul`lTh-_Ew(RsFB1 z$zWHSM~!H%!m!&?^N;R97?Ph<3z`5P^*XlStsI30_OH_X8s?ZEv!)md*J-X#MY|x}AI$1;^8rt55 zt=rmlz#1I|IT)h*wxeU6>unf$lH1qBPOdbP%-EYq>n?G}FVrrhE6 z*yJxY6 zx$F&pWOn>oL?H=%6MHkBOt`+7Dv=qVyS9I#ekTteJtwdLJW5dhV=1#z{pky4@acq6ifG4~P&-_=fU_VMc0;jLiyy7_UEd7`rr z1CQmUE^gf>(|i>|!k2#+xumCCd-RLLhjJS|J}IJecM*E%PL6SAGGdQs(dTwTuWVBg zh<`eN2>zyHsX{{uh%}L^P*oN@ff9InFaW54)_OzLF=k3JcU7%q%BMZmH2!+-L79Zq zB-VN3B9h-m`Y*#gN%7tqZit`P0GGxs1XTx*o zcz<{rP!WM?1y{>{QPXc~_7FW2*s;Gom^9|YSHXJ2Ivo9PC#46?#MoX*VEH^RkKv7T z+P?U15}G^y)Vir+hyZs78_F)CD5n{W8jWB1{hnZly(Pe=BhgZN;|p>g2|?8|$$wK( z>mklv#9=i#?5dQLhS|+9Wn)$Lr8A&iS<~$?{=w6l3eEq-$8obaOK1qSeAHktG=)Uc18)lO=&nV={`g7stz zcY+C$>%X`bmyr0CM=N>Zkx!06K%lCn)LR?5Rj;n^ty=fRdg+~XtoE}MY{eOa2CKJ~ zIz>d+O#bapiV!>?v^<)vAPHUx%}d}KI|g>)UFYJyz~-3EvRe?zF*XoCYNsK@K3}Ev zIl;}hOdK68D>aTd%rW1IG0(kX=8qYmx?m8Tbwf*yZnB?H{>PT0<#^C1ra{IME;r=L>2Bjf4BgrJCl#Y@Tg6#FRu2Bu;P(Ot| zdLtp1J)HE<)$`XXsh9=H1+pS*QC+~1i@^xFsEqgi2p&swX zmY&ugIMf$uYZnQEztEcYHGEKyYRZlWlLSSluMFZPf^r-d!tvsp?2m<_Gp9m&W!Nhb zKJ5WiWEI**zlBJ+2;#qSn*O~@r!ea;5sN8j96)M+ztr|LD#PQ|K!1O;tr))=Iwi(}${RJ8ixaQDB5#f`Nebj}50+Oe{GK*C!5m6_(pG13-v( z7tcTS&wyV&86=UA%7nx5b`q`VxwXGv5V%lC9r)5}J1G^+aA_g1_g z%cBqwh}H}aU^m(H5aSG;SljeCnYd`2rnv-Ks!h?OQQWOO?R1`>e@l!6myYF-VHXCIoQ&TDkvDb4lR~cqdut#%9_r6n) z&-&tD;C3g2xAAf7txYg31BjX_-e(l=Oj`n3ZN42UkI4gMSt1RG(cb&dIStTTN9uB?~O*fUGjciS~#k{^7heZc@4T^8g!$;O(d&-Lj%Mjms8ER*MK8upG0ljUGa) z0N$o`GLTnKtSeHFT8e(9t|P2eI=Ix=$6@nmD9ZS#!(BpNc?;}%><0ckP3SR0R^UwE z>bhVtP@H(Ci>w4*v6%-@|mo&h%Tw{Hfofx!kS(3i4z6VDwpj!G6R&gFqMUPzml! zx5VRJE*N97PASRUs;*FBDl~IDmEf7~b$>;6omPjO>G4OEdC?=B^of;@lhj&Xr+C&w zy*+GT07T=kJd!|G?Y!mo(tx~=k4~y%U;dO##F^{F)4c3h467nb^8Yw))1y341t?Zv z50a)Mt)ek-r=hmdaIe(-*ljnJaQIvNin%pI<9z%#i$-8ltUQAD5CtYPC=z9;CZ4px zd<{!DY>x|vL_c(a*#?v+JNM0G#cov)C3b1u1R^R-&_^3F^cyWc;4}TdogsBi#=_M8TE-kK0c9Z^y6L?X4rymP zQW%orzs)|ie#WH?SHWc2FszA}9^0+BoZMmAw1GXo^mEO_*(`?DMMnv{;bT4*M7BgO zPWGpNV3Z1X-!Z&2Y(8;2tul`FVgGyznrteGU{#-=p1JllQyLR_4Ybrs6aw#~Mc9+wQPbL;*h8&o#U^b2lZUs^mB`$w$U34UcFv zO&xsk6plO3B5XdbR2#HbM5M==s{aj{O+hLtz?cpalC4-hNW3UFF35NdMI#wkx|hwz zQ%+fR8Ufgu<+q;9VwxF^vm_>DxaNPaUCP!YP8J+DbYRm^TfK!sFrcvPXrf+eF@H<1 zXr(c6E(keQ+&T$ykMF3R`llpjou8gfLuf(ZL?L&dsRuvNT8zwU-e=9*81ere+bk2X;VfT~r)}i>oQTQ&C(WXAu7d_q*Lj(?`Ee zhbK*MexO4%8iYq4tN*m6LLQ~j_!D^2M%&{H9~GaspWQgIq*6XonWPrrZ`;wtqv=@v z4^$>hya4>4RY?_U5rtrZDjr&9xC7GYbjr@Z!k`k8@WK576WO{!HMGCvpw}wov9iEb zwDk@vi17Tqc8fUlm(L9VPJCvaFHd2yo%zs^7puoRtUv3@-?CbqY*hCg?dLFI@(UBh z5R@Ak%B-Yh>>qbT94PraUyfF#R3a2b ze;%J7R#McISt0y_cQ3l|DXX?T0|_AI$Yedz;HH~zCO&h&^j$U{oTgzvHG9W__iTZU z=M8hi{7a9!Nmk>ovQTjwU0sus1|t}wfbSxfHP|F+;N1b;!4khMy5kp&f)AVa=d1SX z-EFa+JVRM#ch$}&S!EJl%b>)YeHebzHrGQy7u_W4)O(xb4AGe6O8z8~tgX_|Wz2av zDav&-w!l?Z@kT-q25TUxo>xJIWbpL1lQiI})J*KNU^_a3m9^515V0)mRDv=7V$W{g zjSav~E?~4O41Q3rPAz3h=CjQmvU$|okUipkKT}V4++flnk6gm|}^RxF4)nbE?xCo05*2t&VZQWwh|DF99V}Wcbf&CD;&b(0S+b{N04&m6xi(m3U%AW8EORH4GLkK6_>?Fa zM7&qO+wL69HQchya;16t3{^^em+4@=S7u|ad>BQvJTuc^G#e5AJUFumIkx|OQrwwP z)n)uUd~kL|pb5hspMf0*d8``}^4J{?u*9M7qm9~S&W>BEfFOdANr?h|KYx#(RO#!K z&gOjHt&-eZsZ23S85}{sTGzE(O0yS=1WpNZeQYcZDr0#;lq>3~v$IQ1QKe=QrjF97 zKPYo!_r+qf>0y~`6D_)jef9w=#_=)+FI+FQseibxLUf-7$kt0ArVfg%Vczl+pdwpX z@X-hcXcKI(^e3n2@5h-gYFeWK{M1TS&}G#LxKv0e#IczvklwEs1ZeRk#scuU9E^y_ zf)9E{TI8Y+f(J2cQeN5BXK1(FleL`NND)T6e^YL@44v+)fN4QVX_d4E?46RS9J37x zbUwDaonpl$rWx=pRl|`1#rAUUR0#Lc+T2ter64%9Gxze>%CZ+jiqRKBy6cB#R8X}r zZPc)!yzL$1C$&v%aQ|ecP_UM`Kq4wB*6=cFE?GMtwl=+&HiG5P(wr>P{JJQTlT0mX z-Bd?-w>(KM6?eH(fJ`sN4Wu>n)(8s$FR_1h!*kWL1jgX(DS2$g(4FDEf#fH2d%GLb z`cg-}K(&=`ZYz4DX?9=U%q=w6TvQX|hO%SEJHik+@+J$(07Vg}mGtO6cF)JVX9YSz z_naBFvIVmAtSM2_t+qcnn^DxtA zGXieS5fT)bX$!M#x%QovgC!>3F$Hu$vQ=%TkF{>FTA#;R06zKsp!sHMBv->`ju90c zkK_BQrJ09!-?^oTn$Ogqbc8HMan>To^OyI>nv%Ufs44G)t)FqqG@2-Ii&ay?aCGqJ zB^_D1cFo@iPSR?4oKh!zZjKE!182=d8zex9$zL0my5SBSnrwA5LJwpT zhOtO1q>L;@#-}vlM09&eQGFF(Pq95^+Z#AiY9ge(w%VJ8dW=x<)45*a&>puUV}QHI zY`Gq&Dq2d}U9Wk1?=b&{z?qtGgflC_@zgo6L7UfG2P&XY2Ock;PM+c++Hhs^i!O=2 zhu{&c6V8Q=)6)(=^E|3um&1-NXIB9L3X!uba-e$D1BLR@*|cufH5t?#bmyK*P{Vf# zsJkrCtc0>o{o-=(t&(-Hu+2)1*$@UYGAy?9;~2=!!QCQ>`LWKI&a$Fb zs=;Ll**98}5F>;CXduR}+JlP^-Vfksgnm(F;G}9Ck!uAnzmV78O}NzctbJ8bG}%TLKslS%q}HeCaZv-Eqk zn~v~~551B=9{+VF>qRS7UoQ?<^`|vmi}iW4ER$%g4>cbM;8BuebKCr_4@=)^?Etm$ zBE-&mv3+$12T6v8Brur=mgAil7Z?~xpPt#2H_Lp{6Bw#Ro@n}snIo87W(KyBf2yoOPXD7X6hGq)jiz zFj^mPOl(GOC{?Lx!KS#!q%uane!Uio&p+&6lojx*k)Opo&d;XzDe=0Q-{C_OY0z4+ z;diOUrP8!${f@oIuI{*sE-%4zQSq6$t4UF~*$Tubu74pxyQHHL6{9{lz+}mf9Y5JcD$ILUnZ_t zHKuALK@63v-z`HJ_Q+=NY4IFQ+~GqFi1HlK)zBGZd9@|#V$5RoMRh-lE{7j2mD+*=jknlOD z?S#t~b~!2n57kxkamWFr5Pk}Yz`~x!rO}7gmw*+!QXGyx%d@#GOMxnEmdHX&BQfxu z55oPLHr;PGvH?o}B3G)mRpHWZ+`tvk`xx=3N)l;B8j@6RS!ztGT!2g!%oeOsdTV6O z9rQ7z-w_b<6{zP&SvWYxY|Sg+nWP#d`v9mFm@z*bJdHQXJgP~T2?D|x2L)#ks#Gjg zI_rPf!`oJoj$kXJPcTX~C`~9*X#vI%{{X z))S|hdFYjyogk@B$0&c=>*7f&IoN!;YOE(S)p0b&9G|E{I3My_$j}aA;TiOu5k1ZnOx#8M${vo0qVwMDQZT9E}eQP^z?R z^~4U9euc>t;1pawu@@5+$BqiYOrOLf=Qz>&)lgoqlCL<&x}a&Pi?pOm{>a#{hIRBL zJ^s_RgulKxtpE}bgwjG0Do8xfh9OlUzH(#j)rgj3iZ}a^T0Z$_j!pi@wG>&^T{cDj z{1>XTu0NcC;nrSA1AjdsuH*?@J+f?Irv*d%t!Zc?%ocFfHcDG5)pD5)ta~c21Q4Zs z9$tcuuaW64yiZL=X7u?%RPu)&X=-OKR=!&Mcvw<3xCCXES-#dT0zkJzjNU;LaTbm7 zm71;5Iw)T@Q#|cI9WS|w+?llt_*!t0I`m!fPrSkv^Q=`H9`4|dT%csJTWX9dtK?Ed zeH)RB&!2#FQJzNdY>ow&!>W`ws5z($zk1)sYvOd?9Q>YTRER#EYOV@{d)Hm%Rm>Da zJU@>hONNHciYy~A6b&W_2?>c(szNO}Ik|NsD<=oAwFtiTP!}@w8N|)%#PStqR1zf8 zGR(D-w4OU56uRzH^8not;~Z)Wo}pU#itN(bOEa;+e=#pRyF%Bvoi|5h z@TG&gEVcSg&BrR#AJ%x_7h!b z7q6Bw=Ry8=T>BM4LPFX9cKQ+fzvMp*>F_7E`_Je>zCU6E%Y1xoF4tp_rG1h_94+jdOJ=?@JC1x!WfLVQ zCkr`a==b5+iGgktOG`t?1zwmZ*+ehljeIA3{(Gq@U%|I=4N3Sa@8*Vld3}6UNxrpv zYG)f6GvY<1oyWQp9^3;gFla&mDz3;3}a{M7VN)i3skX|IgC&$p4(K zta}=#P~&CN(R{=z2#+v#U*O9IdB)gHdy{-IHY)0RnVblh@lG9dS5A+E+$s_xyv~4M zh)QGUx4(a}Yvg6|45%a-EO|Nz6$qY}D0DT(IXrapZBKPa{AaoBVdpx7u}2dVlld9* zFNcr6=7L*(z>D-kF^t|gO@(UIUoU8)K~W404&0}^AMv!6&b0g}j5v;qoB1S~mQauY zfc57s0T!DdRl~#0L3btcAT7mgHx`fmbu#0?z&_a?CgV-snRiMVzWvp*-g3OoHD|Lb zJZjL%Yo_B?iE=!)V9{nOcKY2k<`ynoBjMX!&56j%pP-?;@#go`s~8|ykO0vY*6b%+ z)G7E$xZFi7XWhEagIt_Xs)sj`Z691KVK<)3u-17iH=drZU$zXai@OmLRSl$#_X8W9#icoAWLDOMf;#HdDs7e1AH}Zd;0Ba_=l6zrQ%$J_vC5 zI6EQCy$fUDaL9!_{e0h9N^6Z&oRKecWf3WqkFY7EZZTN7n8P|pYIC{9vhMQotT#U3 zYwBoJ-+o0Nn@_x2QzeA}RBqJqsh4xc9=wiaG(0vl8@v08g{u0S+8K+&E~(?GzHz#( zikvl)t@XHi!NdwM5zlyYxPa>#q2gz(w6;=j z?1S5RS$7x7eoXeMtK5Cj?z4!Ese0e>a~%h&2eYH;FW;V|^L)G9_AVwtk9GSd>MYoOm0Ji&~1BPl}B{9s;=x~hULLR3e#M! z_X)WK0B&k9uF8#VHJ-O#L&^X-Q?s)bIMRzheLe2nE5)8LQJRp6Lr!3{IgI6a@3;x=O}~;84OOq? zMrA!2aTqjXLMj~{p~AXNj+8A&?ysh-<)cPnGgxkBb+;XpdzIZo2DM0Vm|iy1eQdP% zIm|6Xq&ERyHaNZ&?;+D|dv4yJ(5Q#bQ-~IIW&yXf^zS^L6gm|=??UMH7oOocn&ZP>!(w#LahsK>SgZr7|~t7ogk#HcQ-6SA(PNBa0>tgfUO2 zQ4Wmk>0FVy@-t-)pl2m3LMC&oZtA$&+Fah<;ekV+sB`b@+Nf%9ILju%pBr`g?b#zx z^AokVNJ~9^1~j$Fcyp(G=ffJ3^?tO#Kx(%{BoY!`bvaFd_>4Ii9U3U8sz z_oMhFvG$Mw!$lm9{eF^RtBa|SL|=*kb-fXa@{`g>z%~X%=s^5;Z;lZkmFL^Tp$KCG zmiP5a##6W;QQTxvmsWZmsXE#mXx^6{-?lp18k1U-3cj>0xK2K19Dj0E-yhO2vdg1t zKnj`f#}gacR^dn|i*#Q=?#--!d)_AqU&Y1rJX~I!q&q(ZeTTX4PW)e3SV*IG_Ge$Qu(nfeKIB@}Yta9N0l`NUoSUfTftuIUf} znrhu%k&8r|ycA;FL)O`5+Vf(C)89T8<~-G`h__llSQhah7kB$BZf}bYIN=3}o z7K_&?1j|;(&VBl2>^mO|i`L2HWW!Uq$@1?_KIbaX(_(y&MRjZI?@Qml;os2}q}Lth z6LKHhD?>*l3#vqNzaA)fvYE~{BURzwy|o1gGOjnki&qmQ`t_X1-HrgqVb{vg9Z$<= z*?5&8``;l-$eo_$Uax2RjHwJVk(c{Xt};tFk2=pIAb4~B^X99x&fM}nXIq=MyAw<9 z(i+82f#KTw+wpJno+{V+gkovdNfqg+l+3vs; zm_ Uu6R6Yjtnw&vkYgyr50<&Zn~J2%fAGox6$r1Pb|%*7xRzaH27kV<-lVx|?vB zu}ArdEdG-hu3vR`CnCTg)~X=a^LF*4KN+4M`Ye@g)-&d7zjz7MrN{4`Dw&$wVOcIa zg|B=&0xukw%(pr{qdd>>kT*H+LsCDZyR>W4oVp^l^Q^3~Go?ZAd@e3P0Xd7EL_sa4 z{WfbRc%%Adi&HsxellM>ZJq$o_6Oi^7TUhafaq~uCZt`W&7r>D+V2l zd984)`ss(n_eU7}@Ui~_8l9JEQON)J_$M@T4EU-3{hxgblY(JF_Fvy3)={fAJvb1B z|KHZN{;0fi{|7g9f0HED`X`Q|z@#AVXZ?Q|PO}Hx;Qw#_{DtdxKj;6B$NYcZ{(q2` zG=`)6|7shX+XL|bPdS75e<1095X8R}OTEE=p6)D@CV$-hgCK2LyC}?$vb+PKO-J*l z|N0+?2{!+D+LDf7a&6H;Dw;dHkH`!9dgu{-ywDx8cr?7!+ph@&z`t(76Lw zAV9D{r8sf)Z#7E7jG*VWG_+BPFG`WLRJ<~<`DcUAi=|L`d8ra5hIpV*iOLNxGJrm1 z?7(qh{)-iqON73%#8o&SP3*wS%H+a6!78PLDP9Hr3iAW#d%n2_%)EbvrBu4*i<# zN`!-f1d^*)G8!v!0T1)EfM(e>)+=wfic4t1`xYludSm*q386F zR%#XMp4w1h-jLXL!s|}?I%@@LpVVrTAjjlgvYoN!(#fn_R`n*%yMp-YeQDs{a8A!4 z@E2$Kws#j=ydc^c!-gL;#4B+!ImVDytOcs7gx%90J8>T)R>5DrJIjKf#{Ss)-Pdps z9$6e|-snlc?RO-s&x2)^GdtThIk&|d+Ub3kAX`KqG`h;?ca8t%c@o-%q1PyDdj!SR ze9h#pP*~Mt^)x-WKu-LWJQJG z*W+~4#6gtB`^U{h!`(pTv0l;%tBU8ceVX}KTNVzw9W$h56baZ@NnL2!*87r}CUoFr zGWAghkNKpwGHuhCYfA0Nsw-Xf%1`=G5!5fFl0V{k>D66K55mzPKiT4)wX7TCwmLZK zg2l06l@C zz#ekC@nB+LsVAqb(PENH9-f|d03Y7o*RHzkR@-e}k&=|Cq-QrfUfmE&y4zkLN?}xE8v`iRic=fF8_HN>I4tE2Lp^?q5HhnFwa@+HR4;sKb%juT= zZD+jR_DVCOD#=uUK`W+a~5wG7zi8bCs&=RgClP|?4XVNKJ z#$`-Uczvh|EaaOv<5;g&+~+?d{Eq+Z_uJcq@(J;SE+FQJ-gwo|*F7_{xnlSJj#5+- zW#OaQ!bC(|GQFL?rX0a!R(WwfW?sX7^)!xMM90P6sF?blPUP)tU9vpmg)L%YB$Z+h zF?TK92?3D>?|$LjD+tNh+{C}iBf51}a*@^8e%21EAb{xVG6pztakON*v--+MQ@W>n zTUfhdkwiL(i3KZ_s8FKg4?Ww~fAS(^WMtvt;V>j!S35n}ZAiY1PG-lgs*m?-3wULh zMRGu%k|izEQ!w!&mv%Z<{O~Xy&5a^>ntJxUFO?}Y$DAXvrR!z@LGC62Ak^T-?NlXUu~@O zQo50~=RI=`ZQ4S11s2oD-&3& zri3fp8_3EVB^uL5KXM<^_C~urV>_eT$!B?|o!7Cd*a!*B9$YqRN>hiCa+m&&Sv2c+ zOX^Bk^z+&OYX<^YY{UC&SkEne$>2la%1GjxT5^-=%B;kY4Xs9-Lyw@#t5dGCNcP)x ziW**JR~H2zk(0q$h(zJ~W8ifuw4QstKK2>qRma^q%j2bbN?I<(Xha6PXtH)at*17SPxBklni7W~>LO2H68ehmSw@pV^he zc(TulIL{ZpyZGAp)=TvJ9U-&p3;lseV1Y72!sUKQOO5HOF+0OzQRx!!9-0jV2N;~a zPz*ZDhp|J7iQcC%9tDCHp(J?)HZZ^bVS_p`x_n-NC%)8kHr4Vn31TAUH+?!E@Kwpw zJWQ0Y@U#$)Sq}3XzJ##zGQ1dRO+VewC)$ji;p1f;N1vesy_hdyKC5Bywdf1*I|xeI z+ly*A4hAhfgjc9P2!G)sXiBWM8v!v~#5DUZgj8jBS-+_6QRw;}-Ht6)`B1+9jfcj( zxB>V%KP70YBgx8!!NNqq$Pzl=ix6FvS}MfYpDqos3>6N29>+f5bG%k$E_CYi^5|#s z`v^Y%nT};t){x4U{CK}99Y`y#JGc+Kb5oFtDwR8@%ic4;p0yAjoY5i)z*-{F5{fhijNi>XDR0(Dq1R+ z<-oF2%r@BeX95_E=qY;L4bON(J0jupX&PZ@KP4z=s#xVBgzKX1_JM;V zcq$7Tn8?T^`j@bzH?UH?{@pCkD9@+(MJMdNbVfZgHNhR*L2>rie_grYq4f-}&022% z=(T?h$oM#1Vl|XXr&e3nO8Ph)*P*!rL?Yz+t*v2FQk8XHNlQIG!qxuR9e2K0V1`_Y zXZBm#5t(QuJ*k6%hoNabm}fyrs$kZD@e34GSU~ZNg$lK*sp&z@=e3^0!{ik8#N=c} z^!iBqQv>&kPhI<|j6Vr%c&^rpqUzZ_B)XpUz;$~&2)g$10AE0H=Ti;dCQmf7={u`- z<+rSv)XO7)D%G&EFj|DePdM|&j9yEJLXm_svy@y&!!i;tGCZw7%IS`EZE8WD2%|62L0 z-1U4hvlIlZ)c}H1Or`YUT)AAcRJL3TIzwsTv{h{uFg+bfHn77u8m0$d57Ps2|v zM`rwYs6gZ&uX7NM+-@%y{>#&4n_Mo9Au}G%2XA=Lu2F~xzvRb%KMPf!TkECh;^neI zSwcXMuPtz;+fNSR&(2h=l_Dl&gRT--s&IV=o-FH#v}+BNzFc&<35s3CAVy`qh`D6$ z<>r0p;e6$?j;hGa1DpbDi;ft}NNE+UPI7`_-g1b1MZTbSN~1 z$vdzP8qgjrAmDa8yk8u){RF1I4%)Iw;%|_8%_6}5eckJ`bwH+W(aDwUXM zCYx$1qLWG9aD5a0k*vM3AuA!{>Ettbk#N;kx72EPb==GLE=&J(ym*GG66O2QQ1w~@ zuinAvJ1N6|7y}8x99lGk=X%kwohT^9-{7rYV*hi?2mTWC#|XvaWd$5Acl$;9@X0pQ zEg=Euf{#Y+(xd#*KrWs6dUF8nn{eVLngG4e&-aY(%{44jdjKF}hvDs}R|7+#o+%Bw zo9R=x#QvRAlqTt?=Uz$5rOvYV77v<>OFkW!(QbkpKjALd&BE?Llbp+YwdssK!L%Vx z)1>d&yFG+z#}TL~tL-+fzdVT}6y3|~DsZdj!_#G=Av~j?S`=rZUT+y$pzNM&C%5}GhI$ay+jG>XA&mKY9)nHD~@NGAyh@$oZ zO`BimZT=6$;e8kHqI$TEFR%ARg}}hL_rq;U-Z{@Ea5q@{5bC4F$P=3m{c6Qg!F$cO zD+A?AL#TS^+wQt{!-#gaoHZ~)E7kMpEuA0#qvys2W6mu2dBL6&07zgw3uHJVWf3e9 zHo^w1pQaMGsDduFo%Qodf6f7?1U$T%K(D7fyV#>u#y{|~I-C?As@3^H@}4;g$yZ^> zd0<*qdyt=zgq}VqmQ<2onY@39jGT;MY>bH4`A1)BsFS0kBT@`LQ~S(8A$Bild zTU}urEN7l%q(+<&fL&Q5BV4iaji&b-xE8QcP=Yp$>=UGwS`?8=}s;zx&3QQCpjt^UYT_x`Zn2Zx7a2Q&KHw;ci%Dl;s1J%al;m zV8`9oTN-Q(nUGRnv4>n{m8`T6QAU01)@b8LkC*UeoKwC8+nkN6N1lBjAvq1>0UF&_;3yw8hEeNtw)?u}DO}X%+*#EFU(st)y?govvDJ8qPT% zIo!WMlW)8`Ez`;SHZyKISv=>7Ey>k^+IPaiWNrPhYV|rCPG8sQraEqQn7Qv(dY|hK zkDK1y^-%-MmIOF0A6C2Zvb?N)I5NxT@v*Pwoa?}*2AW+beSe(18L!;T8p4~IMzU?i zGoOydG;Ot+{4O=DZ3}lxYJ3{>Et(3{cM}722O+h4zt7Wqx?PT@WVOXYU~?Bg&ii)p ze`oefCo;*(1ia|^8 z`7F_R`*@XZrRr|!eO(VGB7UE!|LT--R!FnJ z#`8X^iuVliy>!^()zj>_sQRAP6LWWzcB%IB0nCs2?gC5Rjz8H>C@}EkDK}vc3_x6g zyjo*5c-cWl`F(MzLDc19RaQ4U18$=EMG62o-jerydZqDMPhyZep8_{9P_}hH&%&u; z?GF2@%jeOz>3MY|5xRjQnCdC_dtZ8!qV@PF8}n*1*%nt*SufoAdr0K)xlK-v1g@ZV z(!@5mi^=(xlAdH3oK)uHSbuoykhT_FdJF-}92Lm(q_|MP((_*4P>pDeK_dS<7zDw* z^?Y$$4J!hkgP5DuaQ~yezQ9qPwNBZlh9b**)Wj`CI;G#c{ z^XO3q6T;8uDdX8Kav#h71h2%&f4+|Z~h_`I!L)cYGH zny;lnjf66Gh_DFWfjcSSrX+I#+xew61n*arD+?H4z>zvBuQIu|qJ6XsRz4bWA0Su; z=d>!fpwW>lB^HQVy|f2LFFMhXR=iVQ8t#lZw>ZMN1Zx06K=%m?W)C zM?Zg|3Qd$2>(cQ=Dl&Qvc?dshIcI$8rscRrVqFdR#xCuC=3W4R*?^%M*Yt;exH=Ud zTd`NZYCVvYiJJ1>za1pqXB=MCV)2g_Adc9uMe@k~8c+LEH9*{I6;aF4)wG?iG&c-u z=>jUL9ovTe4)vlVybd}o>vH0t{czuL=YwSY#`1^T>1EcfX;OWXAe#Yq3uVl(1r>s^@b|uLi5Ep$m5tL6sfzMxk`w@{ttAro%^=6tSQ{vA2bp#L?$f-?4a7 zqnLvIVSO4*A7_;~pSiyP00mS|{@hRuPWgR9-H@LCONGn+86MUILOvh_QPvTW==uHZ z>{5$g6-dr86wEt z+HS#KntVp20<&K@jakYI|H#CL`Z*j`2i?)}cTfKixr06SW}!ITZDe|O>n)8d`We?NeHKa!MdBS~=ai?0AJn98LUU*Z2RJ zddsl5f^A#0@q{43T@oO;yF-GzySux)6Wrb1-5r8!Hay8#1G7zE zpZiWP|8*at6pa1hH2%HmD>B@z;@eJ9%R7crY*>w*akF0Hdv>d{;<=XmF5W;W@p4lj zt-e{e@wwU=gZHv3r#wdx-uhAeZQbC^4`Q5^d(+ovCcGy)v-vL-t#U-chR!~Bn>m#g zcjMcjFwtkSqT0Fqv3f?OgQs18@n{Hh+Cb#p_L2|37V$3NWI0_J*wbR8E7+F6NX45^ zJ`w15&V0K=?c;G*oEJ(ynaXQ`=fjGFp&UWaUasQDxafiqf8x zJpNtHxBgOB*vkN+si+>7wSgc6^KpV@_2u%WL|^Jv{NTaUgan5H;P->JfxGKr1cm#e zg8kJV;cu23FpFrT*=}T;cFwlV_(9sR0|)2(jYbv89Hq+b79vZuo!mP#X?!8|P-Msp zzj-<)^vvEh+hmZ6<$U6b@nb+p{UQf?F-0&u4mw6MuB-s|LA|HBbt`B>W()H*mC@#I zJtQQj(;7TPx#+r#AK4ZepELMX^Lu zgv1ioZw4vj5iqiwf{q0bem4?;a&X#)0Ed)a{2Gm2Sd$={ z#m6N5ywVpaJbSead#z!O9mh>wk$S=1`Gb?iLYeCwHW2eruPLs*)~Le0MvOX6^*5oo zOPJX2P6{?=H~^O8LIhro8?ogc$X3ON({(<61gECz3AoQObXn^!-F28gPd4tedO^sq zvHmuv;y&{4>yK80$rWzbNC>$D@;L)O2tY1{u-xTWS+6ED6`qZKrt5Q7?$*yQ7i_*} zcG?AfWXJ@l4?{2S!vmDwcyt2SLN3vh8SMNdZh~F7LY$R_zlTuZ9tdqRJ6DoC{Vy&! zLVpyEAFQ@Hn%djj>*?tU3k$c{YygP4xjSlWYr)7+41bGpA+j7rI!$Qazsrx|P4!UW z8E1&M39HL`Nmthr7O_%d1FV3mYY61^2K*xCZSS1bWomDzSTJ*j#ELyZ0%bu}+P0CL8(xv$B!#njp-JyzlgSFf!{ z9B}*X`xrHpfV~f!wC_#VOmT&B@dgz@RZrXJk?Kw#_n4U&SC;JKxH#ESlx^Ne zH%3DGQuF#fRbl(>xSO^;Ce>y+>Ba20*CEZp%U*LOrY}t{5YZ2(<&Is;>v-!azuvJW z(JSP>Sw^dQ`)%*vKi?YvBdwYm`pLpuqIFNnvR0gN!Ncy_+%%wfiSWl>0lA`&qy8mj z7y!_OEbriA0c13t{CQYqYyC@>XN|?oOv}4q51fi(M~h$5A9Gq|czwcH9&Gtte-t*{!;%_vsu`I$0q%ckz`t?o8 zqG5%dlO_!$@7Z6LiHJNRD6w&<72tMr68-$o$f5dUh&oAyAwXV`5MFgPM}!VEn|9Au?Hc+vI3pnc`YvoFTh;d+m=9E&$s>16G5H_17u` ze_BdN9Vb2o6+OAY)xwXlG4|nUfRa9_!&O+zS=!p2Fpv%??WL+E8-|M&+g`cdZ{sCx zDD6Hptbl+Ci!Hj+E2N~5nw6FC#zn3zzb2(Q@WPq?^GkewS)uDCsRAV+7TW&stgZ3R zDS#N-OHj;CuIhyf?{uWjOx(u70dp`JMJWQ^klHMxbUu|Mgqaf?aD}nItPT@w44Rk? zU@Fe6{$d;Jf(sbhsr4_XsK`BVTb7+BR$)del?}v}9aWRfRxD`IXWTh5ztW_SK$rJ# zPy_G~JNTnQDa-4cu04!(lI1>%&wcDoCFgBf-Y0()Yh_2~ha|Vh-dnfmfWD}lR?Gep z_GXBYU4JxP=cukiA|C8rAU4Y#BvqC{EmFvr`lsA9Y@Szy$j7b?m^ zgw3~7aH3E@RRP5ix#rrW-hOk{H2bTl&R(e@m^O(~%zS&0K+&wl%rposha`4xdNFoS zPxr#2jvo1Ojs;BADu|xCt5o?p1Qww|h|9C+$w@x&r`RG=lA(^qk%$6v!6r;aMMad9 zlvo1^V?y@El+L zz2rCp*G}j7o&|yx1Ia8!H(Z*z$@Vfg`p;XOn>JJDcDS&xcGoXUSk}YQ3FCgG2?h`fukcRq6 zp*rPjCM4?01{h_G9R$fZj1diLkM683<1*dE@OXu`iAo(`&_W4Vrc&&%{=+1QxnSmP zf6$p*F@7lCLxb{I*jgTvb|`0678Dap_egn_kU)VZONyWUX-4iMXkm~Xa;)9$gVCC0L`0yo zdKjFUl$ou#?C-L5X+~MBTAoLD{J~0PalpeX9*d7hjS~}+hd~Xy`-3g5$AOfDjGC9r z1pM)(o~yGuPZ0-&oTP$+y+Z4!QrrOmFeERxU1WSPcM#U)ScfTkiuNFsjLA%Ie0zBDs8#6DH+T9w3}P9!;5Y5E_K z#2zzdJm4P_rJ}Z6$G2flrW*|=hcR{AoWg^{iZCTLeqlw9=q~q!5t9ac{1{%R)O<4- zVHi{L;l@TM%!+EoT^hoeaacN8*{(O^)ireg;Of7@_D@)guPzW}p|4$r0q zh(>h3{33MqA3w&#U^P{9t#z|AUSBQ{v+IT5MGZT|Ozp#Jn&%09w4 zK3p`@@B_cDuS-BwSXe>PU0^HDOfPOeR9GLWt)%tcP^&t?-plm?1YT}H_XyEVS@P0V zjEI#q&_$2B=w|nhpLZ*6g+);)l^<&OAOttu3~(^8-Dpn&vvr$1ACst6`8@8#q@^cU zSHW}Gdo-R52M33Xi|faaALO#xZ-uE(i~O&fsfcTBY-+cEuta%u7EI@}c^^fm%oNtz z`L-0cgo$d_w0!jee|Tbir8HzCj?YU1p~T`B)YU|^39NS(m|Kdd{Mm3h>>nSI^l;kHxcQ+T!Wk>4g39ndwrEw!WmKV?5!&`BwAgk43A+hq`8~+&kLH70sCH^)sFU zItlJBU8Rj>O0( z#ZNUaFH6fArs&d^V6A;eEqrAp3vGk15=5eNrdzMkoz#@R|C}96jq3FKuu-AcuKo6} z&CEuDCsHnnM=R)xib~o4Lhs0(@nNgIWT3CG;O8-G3^OBJ3nJc-l`RyUQQ*fh>HtKdKnuV zqobg(b98(+IIhVpQI5Z&qZN*>p&wU}iyIf4@X3M(g+n6Ah*K9zsRxhseT$^7p&9YX ziAUtYcpobbo&lUIk)wLiC1>QMPb%-|D?nn6E@8VGQXiY4cZo^6|DsivN54_WmKGbA z7_=f|CA*-XCLU5$N*)&%7njOYGKQgvOjP=g4?_ll#4jDV<)NK4cQLt;^jley zHbWp}y0LqTjdSn5ND$~-GS!?{n+Y4_dHD?)FPG7=w8o*Y3DL3nfO`rbG+LDwxUj`o zul+}~{XB=KKE-S|H~E)?kPDNV>-}6^{Y>d{v&AN%JoUl97J~*Gf0HOXh{`G?3Qt6g zqCzzZ6v(`6B6*!Sd|y?XQKja~YO|S#gz2~IArwpIsxEw~g~$@NN`gZsgb)4=IG!(?%i?wo2n;+pIFOW(sBdUks?s#IvVsOMLH3ac zW~Dq3jxi9-Hw0%r4=b$|dX9638o27!uTeNs=@|RoOtjv;9?_eXZo>d&Dm5BQ)_E;z@!%NVx48YD?-}?#Npm_fEDt(I zK=is4R@VA1uMgH}XlUBn+DM^+`rs^ouqaq|u6npcJL|Z`xi8H}3pRApXfCHF>6ziR z-r^fr*lck%I;Bb7!W0>D#xW9+*#7OJEKzt5noN76$H!$G53@HRv?U$QAWqZ%{jZMAk;nMz5)H=t z%8~eY#{R|TQVUZpqg|5BuV>Uh)!<*I{2QXB&P{>E=630|<}x4i&M4YTnDR8;neqNv zF{mPv4XDvI+T3J}Es!bl?6$bwJ|cK1(*0PtYi9HFN}NBU+v?PQi&K3G^N$a%@c+zm zRV5I_poUL3jJWMYvHv}zbJTMH31Rq%xs8aRAg)DxK|^SzfCMd zhf8$cL7`Yb8d_ynhF7;aMo@;r-Cr%5KuOP4wv$QCL4Hz7!j(*_aks81RJUs%-#m0@ zJ70c6$RNM7I9Sl}5l`*v?QBau~98$5_S(WOeOD`Jd|j7mDC;4ot%4E>bTFg1mBktKl> zV^X8P%p&_RDN3nF?d{Bk1yy9~Q*-_McE8R$FYMd3%NLg{ErRJ9hZ*d)q^MtoXUtE` zp#^8ri;9cE%$EGTY8_E>^@49Eqh;oYT|O!4MHB^e=;ay5%!ueS-^0YjiFY%A|?psi`Wa`dF7S%Q5>&MNiJEN87YOBzO*)Z(1Pm%D<0I}ALxe+gUwBg>KYpP|* z*Y^>gyeU0guK>Wo;y7|m7QgjLX1C^TS~(lov&{tAad+_;&OE)CQ~K9MR8kIJ8*Tk( zI%Ir>)iJikN=Fd{QmV$VvuK?=vuY)N6TRYuJfk_@$)2q`^>!V*(HmU%?GL#&pLY>q zVHf~Dr$bQjNogsCzW@LL-hq;e7nc-42&lL`4DU@pm$>FkvE!|;KB2W7wKjfLT0!YJ zeY48x)mT&>-vouho^VYnpJ2f8!PkCa7+La}%6F4246e(4FjtMe;#S603~k1tW0pOA{*JbMKYPw~f0V)41O zT(hV&_$FO%zZ6td;}XEaMZygDN?1#SR|M-3Jj;^3wAdFX#TGSh_kv4~%KbO&xoq2^ z4IU;bEYaUKG_%Z1QWI6-w~pOht-A8^_UA8AzmOhQS63;^%CwN+hOg^y9B8E{@z`y; zz24g;OQL;i@JI6pg3uM_2GsnYw^Rxh6j=Z<qKwom97_O91Ii zVjSA-Wt_nOPnHy`pP+;9*!93F6=(0WgAAD6qt@V)P*qh`T>L9yaBO12>-mN(w0~w{ zL0MUOkb#Db>|k_nI0AD*xQirTkc2bmz1#7!yCWk*>1XxF&B2WqAFyv~wQnk&_;3C$ zD&ULRPRIRQctOcyQiSXkM}W(eAk^D1vEb*?{9ggml+LwfeR+^=rVC!I?ilAs8D`^omUK-|BE zMDzZ4&O> zhWi_0xB7V9crt?hcY~2|UxFghP>wtPfRooCIt`Sa+t;YPyjaw0B67~)wlp#LF9Wv1 zA|}OQZOZaGY^eVg$x1lr3TRGc2i-nA(iC%f&2h{FH7vM+*~xjbJy0$fm~9!|84*GCI^ThvOGP9*I`0+<9g(?M)q%K92bWVK9XgniW-3Q zIE)6jijV+5DK0M0-oij}bS6@0$ZItHpD6#4xLiwr6&@-|Rrb{O?&rpyi@}0{y=2iw zZTTr6nEw2b@%A>#K@G@F+r9yYKwg|l!S8B{gL6KcX6kx`8=o1Rnb>+^D`x=G;$BcI`j<4 zZn@;^<*SLPBYYUaty(XMJ>v5H%fRF9;rSY<^I(0&F{N6k>h0qEWaVGYU?UDQ|Jv5SJPfJKEbR>*&Bk+-y1VVc z^B-@m>VXAd3kN?sE@rac#d!f?ZPl&3jcI9Q_zF|UFxe1L`8ah2HFh))Ekx|5`tst2 zkgTZs+(7+vFIh?hf3*qj6xkMb0&GoT%Zf0I(rpXyO>nl zy#WUOgS5@1MOk%Tf%VRe&GY+Mm6QHkvwdH-OEHLRkxr1=KsSQl5<1J#Nnzjv64+KE zQ=PqDV|bbj9@8;8we~hxh@C$M!A9}B6!yK5&28bfnF z;c0|vUs6UCvZagN{^q-DzxBF0WP-{cENC+dM{3j<`fLvG)co^07HBH~=8J`ED|>>tbEHDANf7z<@)m$n5CjOv!r01_1y}56C3iQEV@t zs|r!W{Q>~`8!exv^$75L*VcsI>YqV~CH)yLNyGb9P^(Iq&2uuG&IVu$Od;3H&Ix>p z{ezu73zC1mV|DfCuFX}gd5mupyZ*^i0CMh~)7z>T!f2wSW2KFsHCp-^|jqMcu;9=YjKo z+S6M>*r;puUP#B+5|KoN+dW7)DztGWkIG>m%oyuxv)7rwsQ6k211;-!FsFeL&jlB8 zoIlKcBx=q3$wJwZNo#1Vv==oEp5=HW@+hxm*4==V@*oVN!Cueo;0eAaTL`7i< zj_c3NBZ9aaLaeN;u}XNtkz(juh`*+58=Dr&(|}?Oy?!qmz%U4}wf>BgH@L?H3T1Xf z^^sK~6sFf;VB!jQ=e^OG4ebR#Z5=2!N8Z85cI*yp8! zdS*d|FM$S9UbpaJyRVLe`GspsHRQ^0C#?Zt4eBK`U5hT7n}z_!?vxC+mbdHMfK|s@ zJ2FYjsPJ=3%ri7t3XCp{4Ctm88nS%H?2T5RBwE@@Z}1u@FySAXdKp<|XtgYGqKVyL z7+tdpVp$djqrg%%rDK5Q!$p%?x4k#*cEQzy%W#%8_l&gg8=$R@d*+gsENr}jghesD z(?!B~N)1lE#HP+_tJ$CW!Q&=~p9sv1swFPNG~Bt1i$c+HIf=4?{|5!eGN6-?qG>WJ z&3uiir|53t!2(x?@GczmR-d}z(*hxB&}-icq01(7k-&C%{+6z~4EIhQWrlC{wn*>~ zbxH>)Fr%_IT?*f_W*WM8TPp*2l0EmH!c3FTCxqc89PT>C>+G|fG_e0pcOgJSf`eDc z%L}l?E_b)qbU#vbh@-Tx6xnVVQAc}=m?}I;{Pl0tI=Q zLr|Pz1sSDkG$IP`_Kf+XKKuc9Cx_F~8T=oIjaoUiv?GSAaa|xsOq-lley$w7(ee{6 z_9lgAt1zT-`-xew61q7OX=O->?OXa0mR_*}L4SjAxWiMAwxg^Z3#pbU};o{AiA!S{zzLXFXFX7ZJ$as!CR{aCYd{ruN z*f62Uoea*Wjpwx9ChfJcgJ7!~fM2)wtJNB&;?Yn1Ki==@H6K>n*=jy69^T{~uAkjm zSy1BnW_!r~eR>0f+Q<0lkwP;R`0FjhOD#AKu|upgp~&(ra~uX%yMBab!Qk}!Zx7a; zQ&SHzOYHxq`QW_WnDf7B_rT}3CtKY=RIfwL?&GpOqQDi=H;(wbP*d4;iC2QCFo}*v zpG{B$&dButJ;LV0HUXdM^B~FBwu_{j@U>{jTHEtjf72K1m9@1TQ6&9-f41$l=kj&t zIFk3Bh6TgT3NvspL4LO32E?ZS+8?lG4*ge3ZBa@&WU#U_uewMzMP4$dhvHcRWAw2j zq=~4L)&B`GSf-n|!KJ^B9EJsu)qQb_x8{Z#45H`QG zd#n~C5uU@+wx>$J2bIT{)$P4TuHEnUXlO|Lp8ruc>XM#tO?7w}Ylcy{N- z*=dEU>lwRr7wwlKCM$Pq5)vp@2w37S3vM%nLwzlNt=JMj`WdEWr*&{*6 z$G4P6C?A3?k_C}A{%bPl`Ad5;Ad<}G#_Z;Vfa22fM4PePz(o+KJUoHEIs<$6$iO1E z#&C6jn}X+MV(lh|yc6A~;y3IR1$t8q&b%+5w@#bA076A3M|*qc#~a%vQ_$go zH%mmduKVw4z`>)+gOz?o3Dx!u43&z$>9{}a^(o057q|KN=$Az4t{2*#OhE|5;Md># z?sT`PV!04zCrx&SIte4>&;Uf@HXaRKJ=S|Z!4&c{?1g_?+PxSf7HTXxu9^~1({jnR zu3Da0e~i`r!^pOLe~_hyGEA9rFqsCTTZv< zg)>k`k<-**o1o4i?2E@DpP(W#&*%64IAF`egA2p#+#FN=;|1%blEcCIRCo$pvCBgv z7}Q~JyazSz`ZrR|yEPgbAnS@cYX9K|UNl5^qWI#5p>MW51l1C>8kv}p^Xv93YKKZ? zao2t=nJSO3K^B6B<517AIKjs5h~&zo=EE~7+Ax>2FpfIu)aFHWf+eUZg%{Gr&s-Q@ z+EmiqT3_XMk~{z)Wp$CL=_>z`R9-V2(hiW)z$!H*0$UthRNq}#VRd1CL;K>ldXx`Q zIC_+fWVJT`Z)AF)?R>ha#i8e*`-UCnie)-J|8dEXEo)5a&fI1kkUIzj60}cQHLyHv zF%P$h1gklgo*Djm`?FZoA-&?WKMnFvZ!v{#&i1ar56Yg*!U5K7jgd7 zE)VBe0C8@oD#hWh%#vWe8Z z#`-@4CjspiYPaNy^lnpPM-nAflLK&ZFYO8AtrygQ#?l4h~iC%yXw8Y`}DI zjEjhqxrIq?jp-bX+wXrVF^t(eRI57JKk@h^>^M;R~acHUD!D9Cq zv{LEaleSjC^;P#`#aj}7S2QEU_1?a5c41+z=`PKqZTH&;nKWOEnb_$s6WH|jpp&Tfm$xRSB`YksI;>4|O&!bkpQR=!mMY8FC9|MfbGgk67jT@3Mv{f7 zK^uhy1>W9sF*V5MCIr`27l{trcMz&ZbR>uR^bwsZ46DAyx!J#qRF_W9AODS2@tJ(% zzC+63uWRB$0?dY1{KMK@!fwBHn`oS-t9patzRzer8Oh#x0jh}5v}yWR%l;?&d9m~F zO_5jc<1vnrvsrEVcNki&t3f6t|M~g8kmSBQfF@ZofnTlXZJ$g6_GR91df7>vZ1ZC@ zVpRw4e3yF5q9v%9gjS1KnXwRKSbEw!i@pQ=FeTA>Lg@&L!n$~y4CIAcv@TXyt(6#h z7w|o!b-}fipo7gdCdBd;&}h zag$9sY<5%6Kv=uo7g;G&%{x2E6}a$tlEHk-G~HQR03TAASp^R~JMrDKVN8Yhht(T9 zP197b=SLq#^lHZi0Q@4LoSj`mHQ5s=*#lv}+-B&^Nhd%HD#46mPw8!A%ay%~IOV+b z7H%avPu>$Byi_N(6Fwtk1b~n-g_u zgPD7B3)A`sg`~G6&W+D+RYlG6RIdn1`3=ZdnY5)I>wEE zL|j3W>!9jbXZ@AC(Jsm+Ny+pu(Csw6Q+iHm;72(>6AR)!=im}DzQ@Jvq?Fnp-&t*ZY0=qoT6ANUFb@I#&EQV_6ia_Yta%}}w$18Dc!i0U5m?2IM6-mEy+&UAHmr|G?(vNE`XSlOns-mveo5Sd( zCdx$sKgu;$+e0uXZBz^-=}?GlprU2uw9;styfw@bG64YyscbGwQ&*OIOF)jeX=fIPqNbI}I|xIxaOaVx5-GFb#@k1U+JV<~+%6j=`Td$Q=LIfN#~ z?3Exmay?})De}#TF3sK2uLqd+u zI?s_nNT%!fue0*w)tRj&>BD{>=NGI8m&-AzwrWQty0eNfsA-Wfyjl|0N`bY$;r?J4 zI+ys<&`?rOr;D?i(7+O9{q?xo3|9v;M^=Q^Uz_LBRLE93Ue{Np+%XcpxeVML0Tl;l zdln@Dfl24GC#Im-as)kx$>NDipQf##FU1G%j<_U-p_*XX>yqbUwh{EX24^QFGQf&; zuEw2)--Ee6?Q-sgr_sl?vJ8pqc4X-dW$B9!{=3C;p*XD;o160&&y|dL3-7b!y!%n~ut!6|xbMWzCi6 z34~^aV_fOA&>eTo6?9-kN2rQhF?Lrycgmw>VDAn~YQ9Vxt9EiWyL}AJA;&XG;{4Qs z`JsX~*IR$iSN}XJ{+_{WOR!#{`WQ`zi=SJc!#5e*9f`!EE>FNAsg9XuB>h$IXNMpj zeF*6W4~nPV{Tg4b~2oRU>qe+w-xzu>6IYGrcb{U!5mttH2WFW@#R4GUQ2e8~*HB$DreW!DHFzzYCdvU!3fZfEH>VKv{ir3mnOX$(DA1IAYx$JE~dG;Dd&{T@=e#R1+5j_oe2BZb_LOm-xWB)^^=gn_xjWE=U5B5mdl-(;oC z_`doZyzYaBh0F~Gy z<_1+PD8279%`$>Tt#Mij2Au4=+yAU_GS0118_LZKe4_IEJVmm_T*-fqEn{d(|M|Mx za7!`W5AifM#^j{o^8>~L;phE7P#bm3x=)ro^l!c&dFNE5Ed9BA)77v4hFFk!Fy^~d zYo<0mAQ620mB8Svdd;X~N9Jk6Ugz~lLg3^I0I+K+t0OUo)}C?cZ_(1Xd=k7L<6#!~ z7FR~VajF)9a(xs-$Wu$s!{8w`^G&}E%#%yWv81`rdv=zN2=q5ny9|0%F>J?B@`=eNzB9ZM% zk9FqhkO=(@v3y#yzVk?J-H!Q3s}y_YaK9cl!KcE}6l7=6yKV8b8;%#_XfM^k>tJ;B z#uS_DaYaRFf2Syq&altJ?QlUQn|DMeJSF651;%ered}KW+YjX~`33p;?+A!l@~?#z zQ}_W3uq#zw84*kc?guhzzN8e}N72oEA~cjD$ST3L7ua z(y^-~pwW|GKd40Uu)qe0<>+>%4>j2{=tD-SyPR3I8$DhmabiJ3d@fVrHaXyR8!e6P zHz?$gRiT&HV@B2-Es)Pvc=jBtG%KnQq!Di0SVA5Vb|*33EDfE?PVwmykch9c8Xss! zzvkN%bJ(Bx@aVI9N|;G%;|sj$1O^v@hp?Ty76mOuqQJrc!^!yTbrEdeo=CHc5u{jz z)AMqMt*-qX04i>wskv^?x7-oIuejT#xCXYpRY&Y@m2XI^Ix?U=IlY!IrW)8o5ufWf z`r;wlYtrCHTCS3z2R0fipn&(2TIVs^2F)VPVM;sBZ)0YqZJ5d%H?e5YbDu7s4qVV? zB;T)hwj0lPBW`pLiuEX#Cna`uUK6Vl?{@KeviJm5CFIVcq?tgq)SMLGGddntQ0S96 zdzf{7{PphPuTQep3|?&pao;Vc_U@pWw7c7w8|vi>5P4_H!F%MaOHj(X-KC>8O{@0x zaE(O#r#`y7M8;|K@GMMCCm|i`YHt+hO2kXMaLVB2*F%6Nppic~yr~j0#6y<=rVJMQ zgKJ{3<}o?@V{Ak$#aYzf{PsZ+S6m7BKTl>4*byQ&4-wk8&EL4!ot@lbOAr86CY5)L zKZl-HZTb>6+Jmf)4|#1*%j`Zc4l$|j>P*eU3-I^xoP3q^cJ?aUjVyP$A*J{$EVO5m z^TScScbxOF@A%X`{3=p4OXW(c*J&>1^F^@x5A5?gOT}J(%)@Hw2*=6a96EC?NF7nT z&f^R%7EkcXq6xNSF`k;H!zN|fou^Y}Pyl5;eVi}Ln_`kytq_&^a-VMD)9wj~8oQ5L zZT9|(pCeEa+GN+=wLM=YontTn^faKI8`Alysnku6aYQkP5RO-!&bEWY{voYFqB8(; zH{Uz1GnXesM8%A^++ziA&e7ea#=G^cmqKMgEUl#Z6$M-sPw#U)1)E)3!a0&T$;G_w z4q=1ev5I;+ovBQ2`;Kll7Ok<31^~13V+h>cBveEBHC;?4yT)wSf<$aC=?57plTV_} zSrUDx@DYSaizbE-YwV>Ui$Y~v1-C53<;i3rm2(#(R z;RlX#5jHjz-Rpgi_Xta~{BLWvo0P{TMOVBeaQhL%vR^C03_V!ph9F2sK~j|-{y3To zd3)HQ54>KmFVQPb$I2;;CXu?tYVA)i`L$I>TLk3kZ)hw$Ui!Ca%p1X4%#wdv-|o61 z{Vz*q50?(|bgzbi3)hn$BXX9j^QipEcWi-1+^*-P9n>b1FK#(s)<9ZSZ!^UU@*ACeL+RF75-GitlA^`p<~t*i-GT(jY7 z?S;kUrUA2^t>3oii)nk(5*=z(+bgmS6w^=uJM$o)N5ujG=kmA*mnD?8Kd)?qmP$6RVGa#5=fM$*_t2ITyzmsadvEU>e*3pxZon2IE|yaqr*EDWn5hoPo#$$W`J6wWdWWQPl>T`-8h3oK zr+bvR_Zv}iD~$0Q*9Zh1R{c4R;X`5(wdk27{op}kCHLg{^z9re+#V%G1eo@Wz!O&2 z`jbrCvBYHW@f;u9j4BWU(C;;n>|ADuDRtV&s`~HyNLlHc%ltn05CiLf?W6RfH3aJ} z1d0pg!}W{S9#yL!CKHjtycx#HhCJ$ItmBxg{Q3qMNsqgWQKy-p)dr=B4D8TIP^;lc zrH;OE0ryG;gQN|4(o&vNaa|Lq=TY}y2JNfnw)x8MoCmtGtvrhm58jW#M)-@pX6vDO z|F~WG50Cw|hT-gl?Q29hHWucgsWZ){rNWFRRME^-Mb0~cqm5-x4$W~DT_k!Bdv@W8 z#1t(3p5Nsnm1_~?=Y^hc%u~`iwmheaTYs-5p+2*A3~-qW4;~mDUqup;Y)wo4(TID; z)exc23#qk)f)$yNAslALI{1N6)3g1lHVX zu2%J*`oD8?oL-H`UuG{if5_$Q91ndZTrfx0e)!l)qJ~k%`8lM->dLAt~%?vsYe;X-&7; zSICInUed4Uei{zJ($f^JK)M_Mfzwt8^kUHPXrb?^Ae_rnnEY+DN)H{B!r3K@fT+#< zw=_YM*<`N(L>&B|>cw7F5JcMh+1*f7!{(ui9;7leP`;uVHXP7v)|=ebSy~!JJ!Au{ z(i=Zrg`GsQ`9_2#G>z&XKzuR+>ZhKCCq;-WkNXq4;`!6#WEap>ZlSLE1xa5sIZ;}t z+hHK?cRZQWV*PdVoDl#pXV+WI`&L?dFDYs}Ndj$Q^5Yv+#tC<{2H!ak-G8|N950je z);qi{j7bw66V^Mn<+H{+FLz~eYsJV~iqW?R3wK)SX=95x69x|5j!0JWSsh-VcY-s6 zjRFhHmXV&dDHi>#5BDc(ppBO9V;xgLz)EcY;142SFggHioeTJrg*m^816U$-E4I_S z#|+NZ<`g7y)#HxheKxZ?Z}>9NE<$jItGonJVPJVXgT*AWG{*(lR)5g)X+FAH{Zx3! z^17SZPuE-%9%_!Lyd{nby<40m)K2l4P{L@xY;Bof*BkAV_PABG%2@W*`8t@Sxa4}z z{_GWoRJ>~b^45VOF9{1LZsepDn$h@PO$rx8d-_1HcYLpbMUq{&oihTVDPe?g&bo{p z&H9FbkT8uA!CYLaHtp)#jeZo7s6?w-QRhD<(OGYK5`2CBj#tEAsMA}pW47OOmjBFbK*1##M>Q!2IbF=5 z&1C4Fz=~=##OnDt&sNUsPmPt3{0ns>m}zu&IcNG&jx`IJ&(_w~c1N@tadde2p-)>* zfu*^U{yCy83`LSaevSM|#cYw8TIMH2a1f-*EBs4A4(o3>GoyTUpZyVXg|r2 z2~~*eZbK%Q`1rA`?_rk z*^8sh->;|f&m(mAF~mZD;C}V;{r~9t>!>!q?+X|XPH}f_aVf>!i@SSi@t`g4(Bkgy z?(Pnyc!AIeYgFJR1rq7b#z1D>}xr zkee32U1$zrcGif@)WqiA^l{Jhb*z1>NQb&|K^{5TY1H0Wg{YEup99K|M})L7pk05r z)-Vth0$_#dz?zKwr3PKLOQh`zlX>q;s!kSE_3x8P=}luvC_p)1W+((p2!F9ZZ=%*sX0AdNs*LsqMV4mQwAi z>8-D6Qk=x8xjojds}+t(6JMQExs5V{a5fgqF1EFj_-SZX6)lnYa?!4KZBLVy!D3*b zcw!IdkcnELaajWgA@4bh`{8oOHyMAaKvqHizwHx~uf$H%_0W=UHMdup{u`&OX@Le}jT6_xJ4 zQO4I()D7%}^4n|u2|nwt+%D>)zbq^G#oSB*=W_vJz}-B%n%Xn*Y#0;Hh-!{H@g0hL zNBD4jQCJvbz8=?moIcVpGjsBpNhNOXsL^%&n>5pR=?6NLm^jmm)0>Z}`2>wF@;EQY zVW0m|Gwhg0o)yMs9Lxc<$9p-0-0hS%`Dc=l0vmYk(?MJYZ-ZPPn^D`Y++-Fo{+RZ7 zTG`uvd3+h&mbM^7RM4bTz@d}D1NTb?*~)I4@&`F}-M(GxP*4|M4vSy3DFSQ&uwo@p zZ*h=qiZN$sA~Vz)J@~bE)jtUF@OP5=8m1WClKAyczNRCsU2v_0dB0Fi5OV?`^SN zeTCE@-zML*+i75`jrz;x=Y#CClk2#! z_69S3a-$ETI-)*xn_1LLo=Dn{6N7W!tilkY-1m8MLp*?gGQ_`4lpVsTr%U@* zFY4#3MKis<`0lQ43ORn0j;H44s`Q9t@wxFOhs+*88C|b<=Co6n8n4NF_rBrEAf))% zrdMPS#m{vR{Nu@S6PVW? zcvKD!_6}gx>HGV8``|q=*uwf?&*A_K2K(w3<8p{oOLtzMpbJA+N$r@&$IwzRvwi-q zH9Pw1siE`Ac**!JZIvv!05g#dpDfIs#2L-m2@~YMXiU?72R~(XSlrF9;Kfa$y7Q~+ z#4k15^4ScfRF4NFjE$7KUN_EY=)@;z@t!ts6DrI}(6L>{Z=<*S3Qp5=M96YQUz|{W zO8A1$&(9ATkE?2m5Ru1AeXOW>kYSVuk{w2lpu@B!??mEN;UC>WhX6e#b{!;A14|Y1 zzE`EGG92ny*@I0(@!ud>qc2}%raMlG5jYg9S`UU~+#qhBG~;9U>DlnUmIgc8zmv!RXfm04hSTv)5#$c{`qKnO*_f{>NJw=~V$NcJ@9?KTR_Hid zcWo=;x-(3d3-*0VRlplaS^}K+fxxVfJA0}rCM3J{l(>Ij*5lmbWMhE^@Pv$?f#Ul zz?GS0-j^FLV>vm;du_tL*tOP&oiJ%@D#{u9Q^}+TmsTGps~l4iFHH}{v#0C*-OO_xl8fVwoZ{XUJ|VfTXhi&`Kv$XV4@tB_wvo_HW|S4{{=9n-?0$-<61a<9rv@Sd3FYsT$DV zNtaue9gn{`bG?28M>krOUKYb=_y?NGrS{S8X^hUyChQ{AFnWPbwj2q@EKO~vx2mTB5h z*}>+xa2#ffV9p;R=nTjQ;%Cg4N*A_?1I0H_7w==R_qP|-ZU)}qr(gSUg;q+AJrB;sy24|yd=?uZp2IAa((*QZgJgdY z1ppEm?~wIPdbZU_>sAiTODAy?I5RxStHI(~d8m4}`Lhu*7A zk9xkn`rQ#xQs%&na$aa4PY}{p+E{IWnky2?!OF_tRF5-`3OQYo#-)?Q)nt}TfKihX z$RyEmnt?|{!%IGLKRD~y$_@ki)teml&u9_0{&acN-(@i5c(?qpfEO@dx<@wcdXBto zE(8LMS2_*QO+_iXGh6Br;{q2ZRIy@?m^=xaY)mC@4$-5$E4~55RAMH&yLCB)#eC(6 zNyz|7EjC13d+MZL6)*nr4C~JFbMssK$2sukA_gJ@#ug${ukow+>Ng6{^PZ@EtSR+m z7Kg8__mPb=(bH7nfQT{#gapF%^>$~Drd_%wU3MnIx+Y7tEw~L`G8$6FN}cCD+%B8q zsBmzc68#5VP5vKUE(BfnVJtjexk)@89nA27WmV3>C-pjArmi_!VGP0QXsJ|Z zS-j&pxPzha0Mo%g!;88PDk&!uXzHOa{EpZZLcqkXGugWH7Zl9%r2&N~YyfE*pS_os z$J8z%LbOfbQ!IFFPi$l=I6A}3z&%B;*>T$(69A~aXV#02?u5|g=okMPK48jhZ?icq zR0`7Cm_Dbg)03?@pBH7#^;RKo{QJ1m?HwEsPaQ_U9GFLCoYx_dhpqRaq=pU>a)7KV zEr)|B8IKne{ufL@s-|M1Q#ABIOBC07)|vMBx|JGLNVV%teltJ6Kts*x**YhfU=U1GY_)vw5>kM8_yF zhdpWy3-mlEe6GvwOOZLd;Yovh$UR5!fBU+^yN~TN8QMMI7LE46lIj^nrGd?cj-8!nbg1FB)BN4DeT{j zT#zK*JpTYRI33fDMSZq8+b+n1eWsC)oN9b{?iBxyam^ISSjVjur>UtbTGHZq z+=OO>x0tL>ItP-+t^h7b1!_vVolI!GB=gbFMk*D{U!sDjzi3c33o#Q?6Ombv#|bVS zkYgR%9G;F18ffb%8qIQiyshGl#M=$FU*y!cv!#RK$*2C^YZ|0R7Z4 zqxV0jiPeMmW8J!T5V%6c#`*Kz$tferhUY@o1cS zo7Ck^7e`Hn9y%2PNOV5fSDNw-3?ft8In1BVx$jAsEeev= zfXK`&4!}Pn{MNB;`SjdLOGwXWN@OCWG_yE^_WUKZLZ2uq8z<48YX>>16+_5+Tl{{STJ*E0bnQwpkr<#YN^DQ%)^tN{j;t3dlY8*P1lj%I- zs;`8J24JGMq?cDy+68DEMpK{7J5D}m95*3%u*g9j4Zg=NS-m)^`sSO2sXBU_9Vf$8 zaux}%+DXkO$-}b%03unv=BsBJ^qt`vx*#_!$x z27fa?a>h?*XG#Td{gl&nnzuhFW(;t#8G2e`@FQ+nLIhO3Ci%Hz<=m{kFlhRE)sgD-4`VQ^W0Dpgid;~(U)Jk`UQG`jFV9G) znvW6OvpQ0G+S;&Fgd40voKL-EcJPAA_+*+4)82kctA#}PZ|mT2f*)ZSrk!H}gBk`F zp*NO;^#H=RBb{7pOT8?i0Gl(otXH>Y!eYsH7y#OS`b{BEk%8rsREC`UeMwI&H8x## zO$i0bf^ku1TUcHFA?w*v57B-u*x;TL^ldF1Nelsh7~;B+&;wa(arDT(3+8mtMvpX8 zyxcfh`gKkGKt$TXQx8sY{u0A7;)0Mvfe5oU=9%D8`DsM@z3x&~!#)^ycMZBdwxuQg z*EvmYR3(;2*MBKu)n9+h(J~YDGJn;YNS$+&Wgz@d10x=X}`FHz6qU->TvkFM7`~dhEAL zYm(wdtY(fw?{f}(AhmTQ?QZ9_^HW`=yxI(i&BAQyd)s2>oAWdY@g zVqCP?_$J;%BF7!}YspD@-|DtEmRQIHu-BG{CO$ug5kxy3|4r;$6_C$p_Dp(QH6WxUgW7ykCB1|{V zvrIQpb+}32Mz=&govJ0?B+s|pQ3xHe;_&f3tiBTJS&dlXB^0|C4H%`~aTlU>WrUz< z_O{=Z)slJjPULo@5R`z#C@fS)CQ}+WDSiR#JXy+F@;?R=16y){>7f+ zCiS~N;u_JM;wMPn?U1Br{F8j2bD`5$D<;P4E=2R6TQQhlp2VoyY(LXox|s*t)q1w- zLZLT!9#wi2MLe}sAC2z$QmRUMa52V~S(+yvVC7^D)r$OidW@LmPpfs2M`^C8*YZn) z8SRrA5uc;aCUo}9>!a@jJz@F%bPU--(hUm@4Xm|>R*cH@b^$daHdbG2ZfXjvq`czm z(6gDKT*Re;hCgaW{T-bDvC20qzZA9HJ87=t#YM7Szk62?@6V$9W3I104y# z;HO-bECkIB&+9`;iGDjDpDt>hTm|}v^r@*^h86+@+CqvGrLVqj{Of^iuMHrna9Sc zRI>0XjP)>oROr3!yYePhCw_N(vsX8L#rkeTvnypPIDH2G`{HF(J3^-%P_Pjlzqi%Oy!Im@%I2S1Q!< z)>Tv(400)H&_%{Z&hqo*+Z;z3H3c~K{RobVh}P7`dwD;!RfUGkav8;RnFE^zUdAx6 z!&aoWaJlB$?sRuZdvW!~0YYvF-JdcGyK<(?qY8Ihi}P)Z(SyCo=yi{yn7FWz%EU`_ zt38i4R$tD2y@bXs0S3{qZGqUmz-@ulJ^k2EY-s`YY|^(ph#!o|=f84ev0AO{QP4lp zlD)o%dH+5B_+RbpAS&)Ignm-rgMDtEVmdp$F(=qeOcd;WpTi*EG7K_6IK~DF2E}x< z3klC;Lt7{Rjo6OqvIjQe)?n8w7;k^VHSB*)#{Fl;DP|P0C&O55+Hjb-_OFMd%4MKb z0{Tb1KWa)ibm64%G#g_#QB7W_i{9=kmnw6wj@AkSa{RYwqyBv~X(TQwDar1y3eeWJvZ~6+aK6&(v81dhXej^v|9j;X z-JRRp$%Xkqi_~02NnQ;|?-ZU(ESq8@867G2ZqI;E_MT>6tN*Ua+*Q>m7o)BC=Uwx# z@i|<*qEKe70Byq`2uj}Lv9MmV$M9r!kqnhtI0;=}-k!x@wqsDmjbrM*rhoYGAt^D5 zkbvO9w-P&~K!IBcldh zn|*9aKF+1k#8ZmW(Q_x>`hmSPW4KUEp&m1}&q<`_q6-yu%m*Of9>~bSRNqmvjRUpI zQ`-Dx+Q9fMyo^LWNj`jeKXHXUBje7ssok{uIoBCCW0;b+^ZlO%WQ4v0(YHs6YBpvF zrY?i#(BIweYQbY_dm|-&A=Z#eYM$jaui;d? zJx)^e5ei&VgSsz0&CSix(b3u2+3T3AfWVR6cZl$WgoJ*6eljvcFxt+Q<>h4YNj4az zXWhm>F8}O|!BNsyE8;#=*9@h{i2%Ll47Q9g+x_7) zx+eFS$wyul>HnUK}TwJ0M(X9E}c021=&fgGBz>2Nf(w30?fMhXfM;16jN zC7Km5=ullu%{3Xj>E4t&W z9o@K)_~KsMi$(^gW1_rRj*A-+kp1)!K5%so^&7I&@cdi03#bw{J>Gni0hfPTG=;3SK~t% zYrPX!lO>weD~?lIk)Em7;tX*W+R!4N-ia0Qd03pG8*mq_8(bLEk^(>l$jP63e}LVE ztt}NV*9&$X#4v%I8#8;h%{?>bmpU-_h7(Hvx zjd62%6Y=M$H5%-mjsOZehq4o}&-c>`=CLrq0TR6-aa7W`9MmK>v`jk=ukAgk^7kXb zJQ{13Wu^T2O-bhPFOZsy*>lBz6M?ww|q2FjBAf@LG#Z_`bI zR%_=8lYnUvS9{$LF^$%DzLAy6a;X0It=NRcw6Lr5i(sc~*pXa-7kGTMGCx<;+rcuK zB*1fJf1I`mr2oHK0NAdkpDD;g$j=Kcs_YG!nez5u%5K)@sKH1D?ZFdGtBW&p|J1pi zfx?epB_g6?>#z71v7Nq@rIvQqMi|wF5PyI*UEfdcV<97}q={K7`urpoMl;F~iM$0C zCi$Q~=80KPkO&SEy<_SVpHMLNn9=e&&zp6TzWw9SD$^*j{BXkh#NY65PJ4l?(V5zX zPKZCB=X58r_lv459=l1F6~Yoobqf{lcekiOtN90Vd`e#4hT5}#A7G_N%zuKC9g+#* znQVjG{BDF2{hAC9BV;-lNmGSA-lX4JW34(X@pzG_RQ7~O@}yEeSi!h*s=*2*#27&EL_P_OPVUN@ijYe z-Dq;rB7!yitC0Dc<_M$Qszssf9S@SSs5nxIwKaz)I!l^*)deh^5aWA!tl*fw4)^l3 zmfqCN*b<|kptw{~@ zCM*0M{8Svc4^+8hdnY~;w9@1zQY!)gf@J|oOsp)-QMaKljP!`|R$DWq8(GUFiaaqC zhA|s2Y&FYCkud_oBO`NTpY^H>3+%R=M8nyor4w&n`$Aqs*zW!u3zgnGq-~_=CjRg4 z)=ZBiOH(=N!ZxY*i~K2+@;zW8+AC<-2sxX~LIMF0Pmz(j1n#pU(V{KWW24yksBydH zlvk*frI5!4Il3+TdO1ruVL#fm(Nqc{FL#GrgICq!>+ElpW~SjQ2lUl)U@?y8GL#YCk_Cp$Fp1ZxvOFU%FD~=e>nTzKD?V~ z93CFNy1HVUfIaL(gM(qRd3?Zic_k&|sZ%HqW7r-lAWzQCEh*bS{@;qq!A?&2zT;xf zN#(N#hw~TlwO=#=P)7U5iPEv=#4Y0lpIjUBvoshuq(P&9#__A@eZvFeyB_ z%6R0lKZ8>3p@W$|4c6z?a6$8qCfVv$3fxAHH)kUT2x}}qpNDFoz{chsS*V@diO|-I%a^U;HZhEDmAc5f~ftwdbKETFmAf4#xF9n6OZho3t;R30$vpyBI z3ic)PeEu)v`S*107w0!5?okO;KtK0=u8_I9sF(yRhu)?S^vyw# zV$rftp`iaLZSm!C*ln|kHz*nyi0n3+6eSv${VwBEOY2$t&CKrjCxS>HT$rhL9K?a5 zSRW`Ta)v({YcQ6@FQhne;?e|9L`NwV-5s}HRvRe+RHpzEsKhR=XRm)+1HNP}sU*R} zc$Cj#&)SRh#O|r`WdSynX^S-?lZR5JOB{dx6-b+znGX&QqEDr#r(=-uTyoQvUxR@o zg2c{U`{mtl`L!wcw_m0{2(1!Ky1w5!AfMWc5}Z!e)~PtzdEVOo5c&<~;~>Hh+#F29 zennJ-EDgNvD`+J+fs6IP^9&LAITG|n#b}|hr*MP;F!mxi*!YJEa3+awh>w3?#B?47 ztZh}rB52rp2d7du1hwk55d|*mc4-x{i>>S*{eJ=#+k2Lcr}KB#jeO^>qmye9mA30S zCma?I>xWw>{6%+}UIsU4d+ND8YN*@b73KyfCu)6HCgrBP)8WEd4o-s?@Ty6HYSZ~y zx{c1IF?c7oZjrwJce8nb+|cELc*`ENpt%w>qlc9~Uhp=pmA&6|7(=LWjAxWP$r(FZL4K;1v- zwa~>{qe>AVG|~d+d;FX2u3$b-4Zx%$ZD+LBEhwnG)$3;M`J#H7zOMWZV9Y0LQrETf zFu72(7KK)Z#pKqt?;v`z&ywWdnrvT5*couQ4c;fObtK783uvM{zfhdP`7i6oGB`Ho z@9`Yxl{F=T5ite^GVPDL+`1^%UjY3)Jd~~vq9g&Yk;L8Lnrbc}f8l82; zxl)5xyPZk2LezX6E#U!qQ#URT1m6Mf$dQNwOvhk?2=)t80FF-_d53xbIs@j*69xs3 ztAvp-Ce9ul~iA-4G+Rf;t+Cs%!3qqoY}>%i-dCTo=}u@AiLn1K0(`TeO} zokX%?5wCG)@O)z}fPfY}TXZ?Mgh2ePt0r>`gx8nF%1WlChQSrm$ z^xADy`MSarh)Q`RJv zQ1PFibquH0GCr<0i7)u|lPj7op*PudOC;xk+=7`~!yP`BFq3Ko=|M*g1k-o%lg5xz+b>L*F0Vg&LE!Zk+{v?!vAaktM7{yR=YtQBR zdP&%jcR|aXystR-|5(TRSOQGq#Qae8YLvHYadBZ!hKd5@X2Hz!v$!-D%;^r8oN|o_-5cfCtans%M=P-C)W*G-9p{4gU0*hvQk$bzt zYNIdejRXYDYPp&O>I8&jY0UX!i}-PjZf@$je<#}QN!L7%TE1Ba3zFo!Uu>OlN0tbjI_0401 z54^bfECZMGA%#PXLRPE4o?^tg1KkV`jvfv&#G|K2je-_8#B%7EuL7+#@~L?Y=VYBH zBYcj;QLGuPyg|<-sD#1*8L9j)U5l$e947Bb3tb*F942B!gJ11pO}s#+FSA;AMEEDH zzIayvrS&~jxJsJ_8smn&o@*9fJ=hxaQ&bfo zkjl!%Qzw7Np$B~SZ6}l1p9`xawNzt8$fU!^Lc~0PX_Zxc6(NEGz*K}UGjQc^q1Q0HG1{u zu?=ZSVt0Cr`f9dsPS0f4Ix0U_@r1O}{Nsbfkx=qN|I1i!!B2RARs^2R!10eseo8;r zM}_%zRd+rHvNS5$OW+fA-_{?nHWF>!*GD)SQ_dh(h=tV7$muDCSRO~xSbJDlO{lU4 z##I=3RLVTBa*PVX)N{$2qrRZI6-g-I5_su6e;<&t{8tj1$6BD7x;+CAkb+avAlOEw zr5GHwN|bGW_5^+O;G)s=QMBgN zVMERjq-R43);pF~pwR>;fWZV(pF0W&9Vl!1FD%m|EzzYr^%EogKFVvtSOZoC4F>j4 z_;=+p4mDG%|NqtjToxdy54bS)AMGE}4N4=({PXj;jQE@F)*b6oQ?ey)8|0(G;~$Hy zE3r_Xz|z)q#df!A*TAGWzcqyqXNJ|3izYmF+DhndUOQbl-qx8pLb%I^c{I1+2U&(CcJn)o0Uz=fkI33rAy?lqjy}?GNV6{ zrD&jI38IpQceYB185b9b*6NvtWD+Nj(HBhx)19xs5Y zm!4Std1kW`gApPBC!jz;ruX|@!DSpU!!))-s7sLEU;?nC8&G}Z*RuplFr$$Ow>@bX zv=_FgSke2jTE9eOhV?ONBOe{THB#)(y z7K6cBl}((88W^7(4{dV_ZCp{h>VY&JfX)^g8j^yCCU9wlv_ zxxz#c%4m+lgY@l9?JfPFuDWpQyM0knxQ}0Xd z%#BanL^^o4aAG>#H_5oll$u|s((?o2BiJsvlGb4(z%0z9$m%{bovR|Lh%e`wc5S`P z&k`8^A4*Ml3J(0|cmA3(#?7a4+RT|k$YLkYcG5au02jFEY`?1S>}B~jH6|#6sg>5+ z6vvUVE(O!8&vE1~%_Hman$6VR?CROr&lBXxeS=PcQ0$ehJ`1Hf7G;OJ1E`XFIt|BM zoMFntY!eL>4g3VXG4ky8$wZj)MltKH(B0^vHOBidEC*HLm=2k)yu=!(BlTS)#5wvT zDZ^+(R(Ha6>s*VeY7#|5jZG-7#MMGPCv6MB{A#_fmHFY9p4;+icA?_=Xx0hSQEGkZ z_C4r@m8H@PbszQefI%Uxb8~V!Ik41X++5sh)FL`k{`-@m1T{z6FB&kbk23()hp2C0 zFb3OkhpgUO++~YPcUTH7@905ggTm1_bn0j%V3WNfV61J2{S*`U22OBX)oP$x| zu^K(ZNh@kBq~LPm2|2r8y~zlgiW@nGjdT363+H0c1Ya}R_iGRLgty$!ld+MO3`SIK zJ{mT9T>WDot1kRg`1(EUrvp1oJhH$JB<8-R$Le-w7=;X9n$y+6?w`2aRoC^~dJ=Ug z+Iq-aOAk|g4S06yXQGP5&3=YQHkp z?|Gi@Td&A^t*7TxcYYbX4`B<)S3x-wt!jLZ!#o)|i<>M=Zi%K4G0J2mI4)wIZi>=e zm?bh^7cAaYH7qYKu-*o5I-G^z_8cocbd-usJI@bj6%IwU)slAcYp~#Cb>5n6_Q4<& z8Kb)#vELs1wJScWp6rhL zC|&;M3lUJ4hhIves%k?_wBjU#A;|iK)b{A&gg-`@f|2le{=E55RgCnW&Hs!uBtH?~ zOc6Q-sWjE2&O5%=6!f~bSaUxUti3FatUvJ9{9M>vpTcY{7|Px|8QYTk`>!LBKAAre zkBVvc_g=fDT6!>W%^k^W)oggEHb!N{t+D5d%l&1H{|S$f+{J_)lRk4qNo6`BQpxL{ z!PZOxgGY&E%3#!u9E!QMHgr0zMllbqPl zn~h7X)*9o)b9(9-95Q%JX2B~=-Y$-f=`WoS@TplHmsxFTvOZ@+cF|{X-9f1fo?~zX zKD!Z*Tn#Lm@c;3^dt@c%ltm4$FnFzpjoWNEg;C9@gCzQ@l{VN@;IMN|@w`_-077%U zm=ry)oYC4@m=p1o`tCf`P*G+-CcsY(3ovTYXwIlwQB`qg`HiUROR`C^+H3N!GW#$fB zt+cT)*N^pDle9`6RCn^WjlWXU%1BKfu{vQ}u3B;Gk zx~JsS`Y(RVR63aUH|o#a1^edxhzEhg(@@0sc!nME-~Wfq=&E#iJT0z2)S>&^jI6qDfh;V%QFtW;oUm17YLKPYN2Q z|Mhj~a|G-wFxcQO`+op_g|G4Ax(u!@vfA50?0Xvd$w1k6|2yEnppp^gzxV%b{}0?> zk)V#|=iTAphL_wRlv|&=*Fsww3wl~C(ROW9slTW(oJ%3FsmtBsF(ywV{fqv8Bqe`H>gaJRg2wH=%x6x@W=_=lu$8PysX+7URXzx78@k(Cb@W3! zV$J`Zr`EIOT&0d+fC8#y$HWyw;U+S&p^;G$UOA!d9sZcSk_>Jo1B6nCT-iOu{XqL8L!!LQ4njhON>XxRT zW}{&LvKE~-QyPQ+#y+7m+3qmym1E3n_b%gLTIxV-U#NxW!o0LRKBM)``Clw5auo?| za*{4Ozc0Vu6wv|>aSCU^o(jdrxbsIub`3N)12eulU6xJt}iRrA9ldaiHSul z(!I$@>@pXnr)a86^F|wU&S5`RkI0=?-$o4hn;)w}1}6Ik8vq~VV+y#4sc1a6exgt< zfFL@-|CHwjjS)M@TfbEFP-Si8156iZWHa1d?jJ0!E z4uBw9=A?-k@rUFiyD}rEamix>3|lIi<(Z8w@I+q`D+IMSeq|{mG(ORV9 zkDqiGG)wV+mpmfW$-7o656I`k$D>I&gB9#IiMc}YFVPlp%x8-qsRe&u+t31P=;o7^ zJ~UmcHUR}WKa;|E&uk*n4{x7ku)=v*D9KpRJz3C%L`vW`vb*;DB$=i=xHQ5;~9nzJNFF9GZSLGTQ0GGkuQVjZN%~oRraHs$XAI)n?R)~@B@*iyFSBO;G z6N9vawh94IDqQ`~gyO*rfDI5R!YnNQgKflWLhJhXcdk@Ca2GS3TyAz9au&TBA(I{Y z^=|~gKPged)sl$8h|tGzGp+ z{XksAoNOm&7xi;z^bo^>|E&me>BVfSMk=tDVod-*QO$&DBH7y4@3V0#TSgrV5xfNg zKwVL4vYP`;-SqfWDS}1<4@my?lg0OMa#KcQoc-|Fhq^ezu5|b$7R}jG8WvIRWT(|( zIw6{9jm0I$N|_!4CT}kigHT_6W|Z3IEr0~GyDiY36xTz}Okj}HYc`64NMdN*UWT&q z=^-jgfeqa?3hR!+-(fc@@mDWS?j8VuxvwW}mx;7jCl@idVkX@5jEw$u1f zdhE-hsmelwj)PdS=zH;<_nHtu1-rZCS_E*wM%(9os%CcNn7l-&fr;s4G4)_yQ1QO6 z^k)TyG(4S7VOCZ+0R83nMZRf2a~D!#_!39Q;apoDnH$OzQV(r;L1Gr`B>Xs8CQRGf zgJNYZfRqf9oKlRTFTh~+1tZ2?RwTa6zB|2q?$6a}z0gTc#{q7#9KIF=e5t@c*96x- z`wPM0%t}yUclQ~+z=ebEkgtKFPzfPWeH35bMQTo;NTRrAZSwR)Sn}bxufy*6I=zsHT%C#A$()mrOrKPEli+ff6RYV~0g~{@_7+oJY*?ICU%>(qYc#`qf#e{)|`Q2ETYFN%R0C044jKeo=i_(qetx+NFRwr;yyn1xY3IrhjF%OIwB&%uzepba%BT5R`OzNi z*H3p=wz@M=Z0`qmG;^-uHLp~($+5kJAIvDvNpOK2i>G?TUqA||@OZadXWC{$wM`Q| zxb)Lg9RcDGUfHK%mT6Pnexo_fXn)7KWA7r{(l~NU9d$n5Y(`xme!*71gYGmx^!Jv z@=D<;Wj4<;WL+nu5{-nl6yMW^T@nzS7@owr?o$ep`SL;;>E!h%t~I$IgL|mI=yJ|& z9lfnLq|(Z#?2MO0JpW%U!0cla%#jU{?!PcKsWGfc|0DCfBHQ;;tFQUYx)SrYvXJSi z5*nrGH$r=p_0>`MtTY=in)lH*pZhSnxspB+({N51bnunyad@jPwfzJln0j}z@4Vlz z*{PD+V`aO)_uY*boaKjFkQhQnp7q7D3tj&6*U_zAjTytv8*iaE{_gXm-qaVm*_XE- z$i(ovG)}SAbyZDm={BcTMMDZ%{En`&<2CBz1|MBkZILqydj}MPL!2;h$)t&KgI^XxD;+Lnt@Hd9@99?_zB&eS%xVqKedmt zpOI%@Jc3VgOd^ZLS2*2%AEOU&sZ{9}dH+NF46CQZQt;{Zc#>ro zTaD~Tu5(zK@0(QYTC;og$6>8M{1mVjISi*X!XfrVCpj=|J+q0~Qxn`TW0Eco92y(B zz~_?eac|m=(o=D$ltvhRW>|^UTZY<)gykoC52H07uXS&3xW}yPw z^+ANY2WMTBxhkHKcv+H0Q&YCTQKTfO{JefnADguOCN+U<+r1k8Q;b6feB55#_qy>g zekUK#n!!ib{{1eyRTdsF>hEK-_$OopepW|W1R0YDtayQH7PQoQ+r9qPyM_i7pA2qi z+jR`6&fCz8L!sLnUgbo8WzLmK^mV#v1_nGLl{7S~4>qn{C$X*pEm2N$#N6&W0^=~$ z@@?mBr+!lQl-LbQINnS21Cu{OGjU#j)~@!q|XKGg!T!ub1plwUORI=jSKNjGEQ{n?K&@?N@GpSPq@ z2;|-e<<*bnXO$Fl^3cKP82mjdo_V*wxXZo~v51}*BH*LQEA54Zm&=H_a^jLlR>e0l$k z^s))Q?!y5N;4w?(#pkRfl+vGq5_-P4W2h&Kxk51t0E-xleZMa1%%Vb+>k$xpe{(p{ zW}k7C0P46cIBA_rtKIDGT|MsGE^-g}3WtiD>6a_T_x&I_<(+73UwAA=l&71&IklZk zWYnX_dGrCMhbU%D1ggJ}!06+y!1iA+lxKTtlTSPXn)6NiE0AX0t5-5ySnAs zm7FTtdyzGs&pUzgiCu5!wrVJOs|ZkJrA;;4>CJ_mzx@zQG(AyDfix8*&n9~Ld`tWP z(e)KTaRl4iK!gAR0t7HeXnlS zUvGoQ(G#q|}Fh@-rwKSF+7@^KuaR;x1j{ zeW0;A+ADo~YyW48zuj;q3(a+&k%y8>R=dk?8=qfx-bp9uyLfz-J>4;d2Z|<2&P>nf zMwQibe0`V;hnG!e&F4Jb+fTk-*+P6Dd*`y0#u1Uru&tmK@XOc zUgj^IxKj^!t$2soU6f@d?FKy%L>{K{=J~}$zld-%G&xr~uKG}f}R*k*VG=6U31wnxlDriNZtf2bUluuvP2 zS(jC1S(x#$FzC^z$D2p2XbyyN+G?aJpZma-kYm8C3K5WzLqj!Z3zeesu+>j`PGuk2c!Td@icLo6~i}fJnHoCdNdsCdUW`n>3rbClu>8 zI*N~#BhjFhT+8XOj`aB;AXL>vsxG3^gueYu5f8s?0LrxAza1HLbktotmd*$jq3I)U z_Z(+uQ|N6(d4Z+Gr~74#L&PWHt+1^5&R3CN7^qGQ$Y}mGEND=$ckp?aehtmPG)BSD>8V&Lp-%g%Y9mb`u17+8pvo*9z z#e4qs&Ua6q4$E&v2ye^;wZ8|3luPQ;uf9*h!)&b+#$iR02U&(NpON;d=nf)Fey`2$ zLZU9OiXPvbw(vw(3Q$xO3DrUKso9>KGh1!#T0$p+x?CSx#uC}51{lX1J=v3ndkP3{ znvrpdUi5kS7DftRGBNm)=d!|mauPInnWyt&lC3C?aLSC3Pe*@YX^?<0Qw;meOMG!w zKL9J5qdfPEKJ(U|aa*ETB)_d$daU}Z<3<*3#2hbVMb0~H57K}tw=b=KTOn2r$ZzrM zCW|J7K0JSwvoUNxvY!?vF5BM*F}#VoUAXRvtP!y;`>Q8u@Uv*tV|jgnP$f)T&`V8? zmW@OK%^wl&7g@VEou?ui$*1Yy3k*7p6L_D9S7Cq2&Z=Fde{CqR5Ley6D&NwmyUES% z^Pw6^KqLD%*-QeH=9YaMUJg(eoAbl}82^hB_7a_#vB25*dxT`0iL6p&8b3p*1HOV- zftBLj;X;?JL|bYDyFB@GDoY((NnzoaX8E6iNh~ zyYf(Z<9t%JGlOUI$|McoY<0S?{oI;|ml~gUE=Y;C|QkPTn)c6Jl zLQcGqgZ-lwZ*3~~cwF*%E3hLYp^z=$S!=uu4WFJs?>Tjf9i%D+%DyZTat*(lr zlJ}3eSg_q?dj|I@+nOP*4*xr7mFfj5VMwx1u32Fm4+1EE&iZG-LxpRQE`2;-Rg_xG0v6@(M&bSx5Tk4eJ0x6G-j=UKGKy5~xhqPTVQhMOzM5M&%GR;B}KE!3m z{C7ohqaZN%&%Emy@)KFaobq2rT`hGN+%a$%U^AMI7eU7Htkq$oTf=j-=bPVVf)I8t zj+t`$7$Z0#4y3ZuIvw|d}v^B-!w z3v#3=+~`q<;9_!PgjRi9IjmKi5Gkfu=;vdFUcCvxQA%Vb2GqBZ=}%G`d8H{qJrTD? z-cBg-6S1*1g)(g=FS%*(oYEo)E)_n#ve~kZ97tzkVO2mzU>oba<6yKTr`dXGzq&jZ zG3P9=f3!jpk=L;?45GJB#4)`8iXyzpZV3CvrF7$VPdcf9&BG4XxiR6UZl*+hE_2O> zp0v=rp~6dQ_R(c7m&D}lr-kycoACQ)7i0;i>6~>ISW`qF$ivvLVPTSSkHohwrIsC< z#6!dEw@NKeFiWq+Q^hXhGhCg_I;wZAxs;W`{YJ8_xj2-~! z;Kj1ib|l_`1>JfQ`oom9VulvnPgc!SE562qR_et$?oGw#;_u1dr|&(*iAxFncP5cB zu4qe3i%beTJuKcXTQTk?Y?$@#d{2v(mNqW)+N>kc;_KA$eso@to4I_xiepe)HDVzZ zU^TXwsW9I9UuVfbRScu@+2=Rene`6$7SC$0m);!SxZCx%9>cMhCIC4y@Dlo$EFK=|<;U4X4C0?bfPml34_Qcr6|26V|%uA0PocP`I zd$0eAJ3uD|60&FwoKI~ZPYBdDYS#ZA_xWdFqr$zty^SQ~c?stq&F6m3>vsC85)tTK zp7yCtG;|%--^1mwIXqWujdEXAR<^m+>b*Bx4HPvg*L#VG2xOmm?M|2T5*5A3e*y*}JkQ{|Y?+vto*|;qnbh8afN2E`%Tpi6eyOR} zXLf14uEzit>--P^&|Xth!yuPmYrTa09046YYF>0RS0-hDsWn|67w#ujp?+H++i1fh zL&uT-O_F1vi~Vj}8=J3yX=LA&oIJ-aP*UdcwNPO!wK##aeB{EM6rO7qn%PS%cZTp^ zqtR<`*YZ9?K0h~Cl|i6z7{RKrun>S^YYRfp+X98@nVOmk4L~--{}~z|A6WeAYTpxB z2T|67WSBsP)!M8a+!sT`SO~;(XOcciTJ)8KjEv{SJ|W3-7R>*;k+#=tYN}l(6l!g0 zc?V1P#uRLDKCm(~n`!m-*3(;Z+?m7>R8~>h880w2HV$|n@?UpkP_`44He0&VJ=@t{ zRqAv~2LzFomDPIFC;(pDc(EN8>K$Vk^zc8kI1?x_;v_;?NC7kha#HE;Afgan+f3uI z4Qxm_;C-wyTl)V5P#!(+qtEZ(sA723#!==+(JG@;GOGyaX$(Yw`ulDkDueAgKNn`} z)hLxOIyUdX3YABGTvk_(5Q1NjL0UL=K9zjSVD7kxUBlNtc-#LW>h>!oqH}Kn995{O zPHQoyXkgX3;m!Qr^}{s5*1P)eEKmdZRL3+)uQM%WT{P6|E-+?{-aH=$U?hed{pr?d@zJxFdN=v`X&}JN0)>=82dt|rMLeeH(so$!tC_ha zV&S_Bxu5AB@$CPiyy9`6(g3&R!NGmCr(HGXgG8d2o>yPJ!n>VR+_kDoznDZfU0!6&W(~)!)py?>++xr9 zV`HDaiHr!Aop<8D{PD>W={H08XD#J6!!bZ!A>&ZZPs7jI2Sx> zXz8a=;@-eOlzEW@tLFX6o2$#Yq?!mYDo3_mYv#kyqE45X?WsQ8!=8qnZG9b<+^aVq z)W0Dj?e8v549wXqD?YD zX5yr()!O1Te=v7YJ%@3sXu{PE)SGm{HoL~9fO225+H_@+k2DB*o}S^7=8jZELaZe> zgCi~)4p3yuIGl#G3G3Z>!YdRk7M-C34MFyP9Xj~a;}b^h!uQn>Zu8E8C0ppgwHV5h z8QNj(;hT>JncR!l(9)NBk{f3O{hhVP^W!~tQL`p@cy*_&s@hEiX`2SS9oWam1`q3F z*V%O*r!pyyu+jxjw=<5_h-cnB5ZP?6nry=xw)vl=pF@zW(a+Q#W@^SNU|@+m*PAYt z@j|wp*|0>$^xL^5-A}@F%kAaupBteIt{&bJLKEu}GL6J#$qw9{{3L6qxO)9Z z*RQvG6)zUUQIxP4X_zn798I;eXwd#acV#C?A=E4zh1u{Kyzp;H-oV_wm_%y zSzL@zg(8Eg_D=P74CeZOEQ~5>{W9Zm{=q%IH(x`l5dNI~enCs6Y8)pT|M}XG0Q!ZY^UdMnU>r<-Kah;sP%-UL9GrX#eJ#{)g{BCU?gc?*{hVL&Ev3 zmmm2W4TU<&$W97WJ_MboK<{MVe?>A7!tO2h79*o3!vR@ll7T4HI!sk&jncvFkC}BEn>E-h&HCzh4z|C}5-sE@~QNBnVRsTVVag zh`xHBNAgn(6Z32E*(ml%`14ssEtQqzH#y(LF^JsB2S23;`x{`DZ*x2RV_4G>oWCPkd{`yBy1~ z-V}sz7MM*oaLN==^sFO;49e3hZ65h@i9>?ef|THin7O;CQFy6bol&FSKgt~Vhj|2p zRw<5}`1EgYKji9cw>N1pj|S6!_9aO4I2^e~Tws;Dnwdf7OW}ZRxT$b^$Uv34N2nkq zGgv8)fbVd{W;+OYJ<&JQpTNgo(-#Bw{7f(K`(APA+IE-n1#CVVmYT zB;)XIZ^kz-c48`~5iieEv<#mmZp{OwfI_|Zxq~bstZTA!%=9DFL+#URpD68yzEque zGoj!=UL*DuLI{F3B}fVvx_dvo<)pHi{O!Db3+-8sKR!Km2uQ|-ip$9*(oVS09(Sgq z-%mr{A#F3`5Yh>QR>F(vxaUsZ=~Co+-~Z~{zPzj~pM_6TlmUr?h|x*S8Aw%fQe6wE z)D|m3(Zo`RC;BQ{$c~iDB;((>A2wI%pki@QiwPT`h7>{4BBFnQuAB;WEm7c=$V>U~ zO?Ow%ohdJR(soDO2fMm@c}N_#&*E4?j-MGRnaf!BRAl8u+k-h}7AE;k;;We16_tPY zRYX+(xWOY8M_HJ4Dsb`M@*j};Ts6#sjW^RU0H+m3zW(V71G#uIRF#^fl}QFr(tM^@_-Y$OZW7 zJ$EAdLld~yMh7huh4*Wod0TbJ)j%?E+-?^pq(vYiubIK7Lr=tIn zl@vd$GM{_~uD70Ai7P002W`^E`fiIfE;e!h4fFH)YE^Td-)r+3db75!9+d;Z)pRDa zP9-_(R!y?9t%Y9m;veLlWEKMV`wldu--!iIQfahMtu6H_NCLEvu02m1oWsSvu4Vts zd0D~{~Om1ho~p5;1P@$;t=C7UQzo7~(;2~H^p7hl`A zP^9H_I^gNZlPnOYHf^u5>}l)bD0`17_mbiMnX?7{J`EYj&xBOWnrL|5AYM`XsU1$s?Pu1xZF#gB z+JgA7K%nncX8XEFS6LE6%1i6yvZ*;)dv&4TZ!X%Rnq<5vO$9?lXP9$!_@3XOz0kSt zto@-(E?Qz{q&`qPdGU~T41G6Qcxb&d2NUB#dWhfs8mxV9qX@%Xj0)cSXb z%xY+V;r?tV<0lZ`QE!0G>jSz~#l^72l+n_0R1OXG%p)IIY<1(I5Uf?un6Z(=$3l%% z?yWsobxsd$pye`EK`FyFKBqI29%!zUaa4RUu6 zR1$Wz8@(@*xH$@37jrZRvpu}{&0YdK*#FX-!3+EsoR@#JU+{^{M*vs=Bg1bpDD$J& zrk|X$+4ewPfeZAhMRv4b0>R@aI4T7aL=42IkCMvW%M*2towdJ)g;o6pR}>zf0q*cj zC{i&;ki^STknfp_;A`7D`Q~WL9r{tJvAx9gC^< zQ7UI;$=$;Zj$UDxmY43E@vr;Ph={uRsMa8Z?*cQmxA_gff3oW;rhf2=KH*}}X;N?J zg6eWdtuSP&uss(Rr9vc7qoRfT0Fv}>@$9{Hb+>*&X&cDaNjSZ@ME>wP3r(O_f94$Q zDE5xP?kxs7!$M-?6x8D0aN#zQyrtfYt3BU@-gK{@+T-%f4-tyM)gSRcu>jlQG>WZ3 zLBd6LIxJFKYA?p9Cr z1u^s$LAjpyW~xktC7WVTe<^MW>-O+MR>Pgfd3uU~X19W_&Tx_1@MenF;H+N7GRTuw z%v21e`Qb*E7VVs2%-eb~#n_xx@8KGn@O?9t0a#!qHt6;=^}#p$Abt6X31lDhTn5&FU5ojif!~3j~JV2eV-7 zl(6)O7lXLLUHdOum>&C`vW|4S1Rd|g98?y^bMM%m{(-Hv>U`{1<8c5PcAy0(`c zlga~d(rn?yAcv$bdIvNd%u=Ji<^=in@8J`uN|;^{%9XO#Y<2CC(JgoG68W0kJZx`# zxEH_){6+U=@g9xTF?0=;S{hCMnrgz?a{P3h?S*>knkJWmN zX3WZ{`iN|I+t6~sR)7%z#)6_n8>Ly&RosY?!p9%VeA#}xDB}Uy+ufsO z-)p_qbl@k=%l|V-zeOzHu{b+8$@6olNr?pTm72MM@rY|n8tX#>GD6}{x+_D`b>q)H zF5;ZL){BdG`;^Uij?{%Gzg;==1l`0tSC*Ak+niZ)Il;!NcJZv%uFq=%DR2TeCMXx> zaoA1K9NPod{gPE_4Gm4VR#pYB9C(!oh&QYj4F(#+&&sCzmo@F@XB^JLAf$5~`F}TzO!Q6Fm~5P1)0TZ2g);8W7Hht6rlrMK!k0^DU#;Pn z7+8GO5ig-@f&<)n15(quww=?72t_NS8Phj+*kGNw+w!W>T`gUsp_GGnxIhCAnoQLr@>-Xul?=V@MRJ`h-5P-)4F{@=+t{1-KX|wj+)!@EdRaY8! zJl$$S>-$;f#p-O{=DWDsT;=K_{+rJmrPPt=c2(v_&baAk`^xkOR>sDLV6_(2^cOo* z()+VNL;WI3;`6+Ra;6M5&OawPiv_FSUIpCY-MGzZ%q7ppn;&-RsH~JSOrGmJ$PBu~ z^KMQT^GEb?&nC?!mW+d!9bfSK|8xi;>4I)F6ieC2dk31VJZn}Xfn8%h?r+XkUU7XJK=IV5K}2P+%+D`B2DN2aI4=B0Xo zQ8AXn?KNyjw?4aDqr+iS(#i0ChsLAc`+j$^B$GYux;YXa1H*LMVt-N3qrE-kb&37m z`Jh2mq&dEe@Z=Fpz_bo5x-e6IYOBhp^nrHDeQahGx1QcNmyVZHmk~Ap}cJ|y$N*>q%(TG~t)vkXp zCJ1C3usoMg#ZHO2sgAcc3+Jn1>0P_kH>fMMHLn~LjNWRy7ol3M*SEfO6-M4#=XRB2 z{I{2j4Jfk5!9JM8-}cfNa~iv_IUYf-(`Zl#m1sGUD4db-N3y+5vmWUC>}UTP)P5lj77 z#)M0bH!cEvkt-qMisrMw%}`lGu<+DjX5aN~^PJLE#(#` zU1~LDR4a^&q0#uT{hf1-dbVHc*lupi{sNuaMf@(iRbFU#%Uw8;DL_%xjxc*qHso0h zzJ$ZSYe!<8ehCAGrjkfzxyJsdG^XZ;kr7r457D+kg1x4p8E?}DJ1=Z-a&1xb>LXh#cbVc*sY|q?^0V0?M4TRf!nzkp4 z3~7HRSKsl~&C)+;x)m=i4JbJ?&f$#N{64a!N9D>49h8`&iI}#hs1Y*NaN0{0+0&?6 zuAb^f3A^Dz3E94M9hn1n)_g5#0>xH99Y;})#M>=aXxF=q`^YJ8<|r5Bs@XC zv@cLbej*_=#a*Hk5}g0teIP5*x1iV zsDHl6iz~;2@~$Bitx7fX7CS0n3 zGWjQ6F|IlTHUYA>SdpKoXtWly+7Gsh-VK4WvSf^z{U5hqs1Hm2TyBf(LtB?$(Cdn@ zCfloO1;|oUyDRuEd41=)M>W0mMsk94>wfjr|A+qmWRj5f5tzI_NaUET&+3^xfAifZ z{izwo9Ryq7cs-eSULAW~G!-;1O3FjT{3>&fN9FynM3?nOrM^7+S@%5=b%ZUzWw!=l zFwGVcfyCCOS~j}MSxwDc6x~&ndgy-f^>~Z9OK7?2p}b3pD34WN`*pd+W$!m_0qRcJ zzIIE&AzHCg6-FvoGjk+`0|QN9o;C?(tBz{3>milGy@wrYA9WJ7V9CnBqdI?!7Xb2h zc&J~wi;a}ksuT>>{B7pn!NLqNAv~2KDsOkMg_$+!9_l{e%m%zWi4auYMu< zBN&8ZFm?K?5++!o_p=%K<{kz4Wv>^+*j&loyHG@4-kK=3UN`C;@B%hxFrr^3M)I!RwN?!?W*|L8unhBreGPVN3BDNXi(jEHR*F1&yDpfNGS z?%u~mKNc?83ferLkS|DcT1&cnXK2pBzQV8Z!K1yLFY$#V&Lku24$>a?0K}dD%i&>w z8ymli;e<#XN~SP~#Bq{-nJY%UovG8?Kn!`#Qm0 zv_882!O>NW1mj3Qk)iqTJdf+!Yc_f(`U8?hyZzlhz1~L8I-`lP8}tcd%J6;FCDBjW zubAE`2#Y2}OH#9uNJx;~x2JNky?DpSu{4%@l=u+pwYR|n)W+Q|j^lR4uUzq}x)ZHOt- zBwvx#i9|N<5U%m$SS6A7OPPy`NQZNUGTEeS;wY@2E@d9$2N@BnL@^fDx2QF2Dv@y;S1)61S}tlOpe(1(zSH$SR>uw5wowXoEpxVD8;cbYJt-**%V zZ`_I0ve3JTUH;KsUtSldWz-GbH#HBml*lnWd^?0mH}RXCg~W4$U!sl$U{aYV7b27K zY$?e6Dw>UnWuUXxqOJac0^TZj9g6OL3dc>MS&$>(4hoo1;jCmQZGi69<(mskFfJ6$ zea+=_!An<&ealr}KU{7ZR$K4HV44|o&&+*-=Xnz!wTVZJKT=A>WtyBJ4NEd&JbPj3)9s^kovPEi^mp z2AhcTxTTXvn`T2PIx(FYIg(r5&T$&i*v&qNsM069nmQY;3Zd@h`m-+)u z;q{l%tU;;~d~8P=Cjm@BlAoQc@j3 zrZS-}FYoz^%}TeS)a&$HYMm+P(U!ed)u<)Da=APl!k7KcS!y{Z8cOg;IyX@D>5;?Z zVygkX3G;E+-&jhF@9a@Qm}>WCzq|1y8pRy;$L&`FV;NVUzZgg|hrZsHU24?NH$64i9$AI~0~4-$$w?8#n$g5;0{+Oz>1!Fk0W<6wT^f-r8!zv5Vm3tFQ=Q`drv{wec#k;C(p_a7{eZqBVGgBpNU$@_a&Je(6v_5 zbdPwE9c=f6vyZKfyLo9PJf{!qL#sn8E*`@kW}R21R0e)P@I!jP_uje}bGNOng8mCW z=i~9MNz2){6~VETa;haOO6>?7(MhZM&)|GkIGrwiT3fw;FPY7!Ei~TkOx-ZDi`=4y1fOzo8^Z6c!JZGeX}0h`Lm)#KE8RBWcY=NM?FWD z=z2|qInSlac=mh#r1!Z~$HnGDD<<~Mwr-u~8(XX2m#b*LHwdq#E<^~o6K^i~n8Ca? z6qvFFe5A~H+@T`aCo7AU>Zo&eX_Jbj(^|3hGC(x)-!`B!;?GUJJE&8*1<#J}uR?>c zC3cw$j$b0958gd$IF1Bm91=tIR_XCOFF6yBlOwigeEH;LlBG)mcQhpJd#N|rxajCX ztO;a);tns4aExF3cg*8vKJ*z2K1oiIhuKqH*VB&f+q9T*twZLwFHXIG&N*yV4(Ct> z6_YyjcMLO81t~&ogR!mOn(>qHf@Hcz+sKZ3=E|tdJu8b9#@0uy^#aJVb7_@%DaVJ| zS*xq6hZAjtvqMq5&KTI)WHMGHs76Lv%ByOaNBiq;tW~!ayINZ=SVyKSn=Cs&zAfB* zSF2aFM^l$1@x~5dBtUO;v{HYIe(&@lF0&o$w1 zSp?mfw}jXGSBCXrdCh6T%4QNFW|$~q?m8q&x$XWzEiTC$o68n-gLN9XOa|d&BU4zS zT8>6S-GBrEY;#N`L7_4D>Tb$LP2Jr(a=$n&m9Wf!(z)LP zAT1wA3*JzUkB?1FYy_X4R=pS^3Jes{+Dw+fH6$AzXBr+`2QE7b7m8Ugisj;Jg4ZQ= z5p+EH^ewct8v=4Ex)iZuRWSKhPkt2uHYtF$i1WTek{PP^ZL(toeP&?wz#b1 zyR9v$`V|YyjN$Lk9($EnWJdFJb!OvdqTz<9HMu+7=?n^gRxX~GryxndW~gA15%uax zjc!aK=v$1`mcv9k4L=oOG&CHOmR7`6^mp5soloT8wtbo#Kqe{Kn#Yy6$@b-@OMFd0 zip*dz*yH#Hc3@yI8a}{QsU)-UOa#E$FzaQl+ojgnQK6*Fi3yaBy>YQ&^DXUHUv+hG z_%5DCH(WWj9?G;io2MHb{qHF=4F(gNWZx4*e(k8g-}xG1PXWf=ZblD{lwqkJH)P>pf(}h#>NRm=el7m}rD3qAd*?s~LIcd1&WbnvwQb#sY|iAQuxx zkd4R1cw74S1ty^o{yW-dwv+t?U3tQR!rc?8!hORH;nmWAm_*5#nK{DHLy?iD*vh70 z!K~rZuiKW|LuHIQz+EKTu>f6%kiVpir8>49nHTxFP8|)6MCJZP-X&gorsDWhQ3qf~ zZ~M(5ztK|PGov~g>Q80^oO&)Yk?I?p^nL1|_InV7ZYlzWh3Nt?LL5p`>8k4H&HHYl zpXSD?PjEQv(?~GCrf|Fd*pOak&BpXq9uf~zKVlxF)D0hb6hHS_(I@-6*8&%dQS=)! zB6J#-m3hdIfsHM8>2y49DbpiOg@rvfOFw(9mz3;U(6Vk< z9}1GCE6_^t@@Lq(PBG`=IH6%{z1Fp-%%V-c?ih^O!&kPkXRa)(EW)kh35+>D_cg=- zYOQl3g#?o?YH(yrs36j6*^3=)H*@f+Va5F7{%MxuAB)J4w@MGhbURd4xu(H5Bjdjk zbRF?RvvPFA6~6oh=$*a4Z=+n5NH!CMyt2`s|ar1qHT2F?lu&*3QWBoDAB?Y#ipG>2@!XM zMSWHp9~M2n6FSR!D{AiYO#hzlSg8#O|MnTC|pXdOq&fEr`o!w zb4oXhu@rYD?nI^t1oBn5qA0m-77=FKC1n`Dzq>pej~u9$_PlW3~u?AU6{ z5_eHf30g5)MBSdM8nBR5^nV!?tzu6Lm{*7iF@!ql!4v1ywB$7D%|sbFC2RpNg9?#I z5Pv!_v9KUTg}-(?VJEL9yHKB)98CaA3`om7?NlwQ$4q7%rdz8dt!O9q_;i1N1eTUc z(p!B6|7d(xcf2*=Zr3>2avtC@fLu(l$EvbCg2Zd4)ITv%>9yo`Wc#&AvT&e-X~#jN zT+N_VO|L<^11=BkUVt7u-S%385Xxw$kUtcPCkam*XJ zxm3^pusxc-(xtx6*vu}bSf^>Xy561dpsdm&3+t-%dQV$^8>%A~UhcyP@y}L0X$Lt+ zZr#pd`=Pa3&Ne>$S`wIGra25q%~h5>9`En7tfE0(vR%DR{uFgcPnvSgn&e5%WEh@x zs?3EYkz>%L0_XE2yhR=mCRi&fD&--`)u-Q_vhFP{1ug}#>s{)R{1|kQX-20<3llGh za%VY+`F|>4%c(^nr?G3k2qef*QI)GI9vHSSoU`uSKygQ~Br4Hy=Z6G~-|=(C6*59g8|LuF2Ur?n{+i&9?kC&>|(S>P2Iqx`G3x_A?v~cV%WP@j+;qrGYzh>t! z897i2R_+2Q%d<_5K7c^{+xvT8AffR(hlJ>GPdlGH*_Npn3a*#J(qgkDB)#TgqB>G$ zkyek>uVL)M>LY~PyN{9YK^)^f=lFY_42jPqVqZp%E>x^3`!#vxff2ecr}I z{{a!kG0)ho7V+`J#hM`ivlFSs(4P{nw3a%#$mz5nbc7{+BC$%xJ0YHl7Aeq zD8eRN@52#*M@OfBoRGlq{oCgM&-ecvFa9wbX#anF|4%mpEh)7JlB}!(7w~X`Pa#bn zd0=Je`3rP7$=4X6k2n13uh2c#`WOCFi0^-#t)Kp#>)`&6cOS*qSw(;ly%i`hOxJ6| zF`fEZ$k!4J|MRU@t5;hQhxM_nvxCj%L=g-&7=o@0ko4NDOLStlk)BSaxg{v46*C!dtor zgDw5?9*rv?9&o)H%^~fQlTJ_I@&qoxqvEfnVWrOk4B+DAYn0@VFPh9>s2U5##_zl& zOJAxwC`k?$fA3-7bwJTF=yZOLopP?{MW0Lw$~@gcX=E?bTwLhw?Hn)lDB(w$geJ{b zf5t|{*P@ksNRxtgRrHhhEfGORihP6oAOTWmKKexrVn`7ObndQP)f-7%|_%oQ@{-YmKQPS@S(pN2lA8v@0T!qr!<3M_AKWB+ov z?d?NoZ#3sqC*0cyZPJ3@l5h+zOj&Yy@3ivN-i;_cj1n8-@H32g&d$>Bz-klH0I$KI zF&9Sp55uh4*Xr++QdmB{}eI!AdH1qK~d{Aw#oO4IdyP@wo zNRIcM%#}s&#gzMos7t*3CZwWXYfouL1qY9nJ2~19)tv_gKpDU45CG8jR6WeIHWg!G z-p?NX*vo!KLVU6mJ`1IdZR)4Mz=-+ORX-w!pBqKTNYQa}6E%iq5LmB;J|zA5KbGm6 zzY~J?v@8r1H0)V3B2+A~3Le@Zxno4gSpRFXJq0it$60Io zV!6pMkqRcZ!@X@l<-vw@q(>h?*ZJgu3X5jx-|4Myq!;~T7t&4?20uTpzHU+d zzMTJw1^8{2)ad4>iNd>ZpM1>vB2V37Xd^5u3g{9=dJJ0Nl;PYs&(C}l1{1Y(Fm^R@SME2-Mr z%IcqKd0e+0cGxY0%MnaD|Lq}G4Zg35f$A6XbX{n~hZl@4WrUG)mF~6PcWzWva6XD? zmP?QF^#7FHPa;`9|DQls0EF?A=q4AA*q)FF+`Z(u`;VGXPNZO3q#&SG|9;ZIa?lz8 zS^G35aa5b7FX7}XG8*xKxGwJhd?5$8BuVI?24wy{P8=$QVI5SspGHEDM*%8FNhjy( zM5rDmIeC|`SYqMDxH<)(iqk7~!Uu6Y_4>)o;pBV)Z=xu^JOAYP%sdinykJKUd1-Qv z1KR3#*U8kHs+9US=<@j?y|AcA-`IMHM2Oy@CZ7%;kQ0K1W%zXXBp|PA)KoN3+-tZG zFIT**aZ@|ya^5id{2S`T^i7ZC2nnu*ZrMgK#?&>6QTNZze>_YT-%Nfzp2Fm}sn)j0 ztGhI%rC*b(f*%YF431H~7O_1;EB}VZdthQ=*{;BO&~KU9iHX5hQsV*l$*9tLKrzDO zjw!z#_jeyG+>VP;u6WgH_u^&ix0D*7w0c%%rb`+JGoW~~(30`3w8LL&e*caYw)8hO zE>+wp;Bk|UF3655?aWtpQltH_DsQrJQzrcuIY6EY_(eiOf`fw-Ue4pZ&kCNFFHz0Q z%iG!9^r$mJGe!M1b)TBql&);cq(I#hBAF8kbB))`Q7urRiEr`sh4?9@o!nuWl0R;@ zaoinNZ4#bk_E)m5Sv?EAo>5C#;qim!c6Fdc33V42fMT(=-IYv5nsqxaLun9BUf()w z2qGmbQC?ntRG?iiF7?s}dL+?t;^Cpq+U3H>#}_Xd&!;0LwX>YnRdXJhZrg%|QQ89! zOuzg(j%VvwP-~s@i!0}C#(s4S^ug4+_k*T#@~sQggIR*G0TSLA?(?g9(y7J5Pz`&k zoKQsy1qB7koYBESSuwSX#b);cGqbI&EnvF=-tvI(4i*k5kOE_1uyVFD6;@#cM|DrB z;-(2Uy5u1d1Co0HU{(nUi6?~C5L??LASX&PV5M@<`EgbN-B0S!uP>#Yo6t`-t+pP| zPGmHSIpPDD;iCJa)bddoY67}SJ< zhSYPp>Z+Tg>g~devsEnoZz;6Yk{~`efj>D%H1gbe*SaCE3GJaS7QftmWdx>CDu^q1 zq31aiHc2Pa%WyA~tz)MtU zu!mTrP*6ZCm9n3JvDq=o2BpP+1x5sp3Sqs`(EfUo0cK&fzCGcxcFw`I_mOUS@j9L7 zO2wwS?5uEBzjr^;4g|un_i%%`O*HLUTFMlsYMVQz10p5QHBh9~+S(jAE_~JXTtOu# z-`_T{Tsm7?>o;8wx|!9$O;*R{LgP8MbM#S3Let7(*+D408N0J+?$n{q&dFm%C(*HS zq#3J(F&-><<6iSilVREt010ew2?(^CH798JP%!luT z;g;yu*5nJud52DT(=lAW2?;K~eDMO{ttWmQwjFCRMgZxGc#Ni6chlo z5*@Jj3142T)>c-NgF*t0w&L%pd;QN zRH-@|AAf6WUT}BbxBWgPnTvzw3;T5-y&TVpA9eduG8n8Ht#?7>e(N#*hVmr*K<^p8 znzOU*#<2i0*OTMFQMFURC<2dbNXL1_4m=j98E3aM`%Gb7cA^Togj*h|DEVSm9PZ&$AkfJaS$W5lzwvPdzJS<(!6(@PcA`{p0K_&mzy z(t#;n006=jeR5{=ffCu-@fVC2q6gach;xGDug_9Ac!%)u@Cc@BYc;}f1T*rfU_6f` ze~QUpnKqU?G^Qy_e>q5UHvKu%QSFde(1lh z=x!E4x#%!Q*OjnT*K>d!%LF)oAtc?8bqvBh$B`xw*oWzL0;pq{F)e9}?MJJ}b7JQ` z%o4J)+-|4AH8qhzL4b@&j*p+Hz`qMAc-thIp{#++RiC0JBPo~U+88?)rWzRdQp5bS zJK8S}39h8{|5x2xN452A@55A~r9gq=R@|W!_m%=J?rtsaDZvw@EfjZmic5eLcT&N% zSa4{uU)!u=?^^r;!XW10eQy-5i9*(l~!6CWc5+X>%uJVfw`n&&OY5im)>z! zxjcz2^-jd2k*QE^9UVc5bBK?^osk^JZiuh2#*V*SSZY0>h6x!191s7#``nnlGv~0x4)lQD z)MYb)RsPdLBXfYx-wc zB>2M6$n^!chTC_1(q5@{o^jEtwAxpV)ja#r^F^;d*bLYH7tVD1een>eA!os9y;Rc8 zJSaol?_g{0M>iGs_a=>^tctCqo3)%Pd9l@-pM!(LETu{tGI->y*JeC)`Zf41Gu`1F zV`@%ln~dbDOE*L;nDzr85%`=wf^`|Y7xq~{S5qUXmaou

```console -$ pip install sqlalchemy - +$ pip install sqlmodel ---> 100% ```
-## Create the SQLAlchemy parts - -Let's refer to the file `sql_app/database.py`. +## Create the App with a Single Model -### Import the SQLAlchemy parts +We'll create the simplest first version of the app with a single **SQLModel** model first. -```Python hl_lines="1-3" -{!../../docs_src/sql_databases/sql_app/database.py!} -``` - -### Create a database URL for SQLAlchemy - -```Python hl_lines="5-6" -{!../../docs_src/sql_databases/sql_app/database.py!} -``` - -In this example, we are "connecting" to a SQLite database (opening a file with the SQLite database). - -The file will be located at the same directory in the file `sql_app.db`. - -That's why the last part is `./sql_app.db`. - -If you were using a **PostgreSQL** database instead, you would just have to uncomment the line: - -```Python -SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db" -``` +Later we'll improve it increasing security and versatility with **multiple models** below. ๐Ÿค“ -...and adapt it with your database data and credentials (equivalently for MySQL, MariaDB or any other). +### Create Models -/// tip - -This is the main line that you would have to modify if you wanted to use a different database. - -/// - -### Create the SQLAlchemy `engine` +Import `SQLModel` and create a database model: -The first step is to create a SQLAlchemy "engine". +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *} -We will later use this `engine` in other places. +The `Hero` class is very similar to a Pydantic model (in fact, underneath, it actually *is a Pydantic model*). -```Python hl_lines="8-10" -{!../../docs_src/sql_databases/sql_app/database.py!} -``` +There are a few differences: -#### Note +* `table=True` tells SQLModel that this is a *table model*, it should represent a **table** in the SQL database, it's not just a *data model* (as would be any other regular Pydantic class). -The argument: +* `Field(primary_key=True)` tells SQLModel that the `id` is the **primary key** in the SQL database (you can learn more about SQL primary keys in the SQLModel docs). -```Python -connect_args={"check_same_thread": False} -``` + By having the type as `int | None`, SQLModel will know that this column should be an `INTEGER` in the SQL database and that it should be `NULLABLE`. -...is needed only for `SQLite`. It's not needed for other databases. +* `Field(index=True)` tells SQLModel that it should create a **SQL index** for this column, that would allow faster lookups in the database when reading data filtered by this column. -/// info | "Technical Details" + SQLModel will know that something declared as `str` will be a SQL column of type `TEXT` (or `VARCHAR`, depending on the database). -By default SQLite will only allow one thread to communicate with it, assuming that each thread would handle an independent request. +### Create an Engine -This is to prevent accidentally sharing the same connection for different things (for different requests). +A SQLModel `engine` (underneath it's actually a SQLAlchemy `engine`) is what **holds the connections** to the database. -But in FastAPI, using normal functions (`def`) more than one thread could interact with the database for the same request, so we need to make SQLite know that it should allow that with `connect_args={"check_same_thread": False}`. +You would have **one single `engine` object** for all your code to connect to the same database. -Also, we will make sure each request gets its own database connection session in a dependency, so there's no need for that default mechanism. +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *} -/// +Using `check_same_thread=False` allows FastAPI to use the same SQLite database in different threads. This is necessary as **one single request** could use **more than one thread** (for example in dependencies). -### Create a `SessionLocal` class +Don't worry, with the way the code is structured, we'll make sure we use **a single SQLModel *session* per request** later, this is actually what the `check_same_thread` is trying to achieve. -Each instance of the `SessionLocal` class will be a database session. The class itself is not a database session yet. +### Create the Tables -But once we create an instance of the `SessionLocal` class, this instance will be the actual database session. +We then add a function that uses `SQLModel.metadata.create_all(engine)` to **create the tables** for all the *table models*. -We name it `SessionLocal` to distinguish it from the `Session` we are importing from SQLAlchemy. +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *} -We will use `Session` (the one imported from SQLAlchemy) later. +### Create a Session Dependency -To create the `SessionLocal` class, use the function `sessionmaker`: - -```Python hl_lines="11" -{!../../docs_src/sql_databases/sql_app/database.py!} -``` +A **`Session`** is what stores the **objects in memory** and keeps track of any changes needed in the data, then it **uses the `engine`** to communicate with the database. -### Create a `Base` class +We will create a FastAPI **dependency** with `yield` that will provide a new `Session` for each request. This is what ensures that we use a single session per request. ๐Ÿค“ -Now we will use the function `declarative_base()` that returns a class. +Then we create an `Annotated` dependency `SessionDep` to simplify the rest of the code that will use this dependency. -Later we will inherit from this class to create each of the database models or classes (the ORM models): +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *} -```Python hl_lines="13" -{!../../docs_src/sql_databases/sql_app/database.py!} -``` +### Create Database Tables on Startup -## Create the database models +We will create the database tables when the application starts. -Let's now see the file `sql_app/models.py`. +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *} -### Create SQLAlchemy models from the `Base` class +Here we create the tables on an application startup event. -We will use this `Base` class we created before to create the SQLAlchemy models. +For production you would probably use a migration script that runs before you start your app. ๐Ÿค“ /// tip -SQLAlchemy uses the term "**model**" to refer to these classes and instances that interact with the database. - -But Pydantic also uses the term "**model**" to refer to something different, the data validation, conversion, and documentation classes and instances. +SQLModel will have migration utilities wrapping Alembic, but for now, you can use Alembic directly. /// -Import `Base` from `database` (the file `database.py` from above). - -Create classes that inherit from it. - -These classes are the SQLAlchemy models. - -```Python hl_lines="4 7-8 18-19" -{!../../docs_src/sql_databases/sql_app/models.py!} -``` - -The `__tablename__` attribute tells SQLAlchemy the name of the table to use in the database for each of these models. - -### Create model attributes/columns - -Now create all the model (class) attributes. - -Each of these attributes represents a column in its corresponding database table. +### Create a Hero -We use `Column` from SQLAlchemy as the default value. +Because each SQLModel model is also a Pydantic model, you can use it in the same **type annotations** that you could use Pydantic models. -And we pass a SQLAlchemy class "type", as `Integer`, `String`, and `Boolean`, that defines the type in the database, as an argument. +For example, if you declare a parameter of type `Hero`, it will be read from the **JSON body**. -```Python hl_lines="1 10-13 21-24" -{!../../docs_src/sql_databases/sql_app/models.py!} -``` - -### Create the relationships - -Now create the relationships. - -For this, we use `relationship` provided by SQLAlchemy ORM. - -This will become, more or less, a "magic" attribute that will contain the values from other tables related to this one. +The same way, you can declare it as the function's **return type**, and then the shape of the data will show up in the automatic API docs UI. -```Python hl_lines="2 15 26" -{!../../docs_src/sql_databases/sql_app/models.py!} -``` - -When accessing the attribute `items` in a `User`, as in `my_user.items`, it will have a list of `Item` SQLAlchemy models (from the `items` table) that have a foreign key pointing to this record in the `users` table. +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *} -When you access `my_user.items`, SQLAlchemy will actually go and fetch the items from the database in the `items` table and populate them here. + -And when accessing the attribute `owner` in an `Item`, it will contain a `User` SQLAlchemy model from the `users` table. It will use the `owner_id` attribute/column with its foreign key to know which record to get from the `users` table. +Here we use the `SessionDep` dependency (a `Session`) to add the new `Hero` to the `Session` instance, commit the changes to the database, refresh the data in the `hero`, and then return it. -## Create the Pydantic models +### Read Heroes -Now let's check the file `sql_app/schemas.py`. +We can **read** `Hero`s from the database using a `select()`. We can include a `limit` and `offset` to paginate the results. -/// tip +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *} -To avoid confusion between the SQLAlchemy *models* and the Pydantic *models*, we will have the file `models.py` with the SQLAlchemy models, and the file `schemas.py` with the Pydantic models. +### Read One Hero -These Pydantic models define more or less a "schema" (a valid data shape). +We can **read** a single `Hero`. -So this will help us avoiding confusion while using both. - -/// +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *} -### Create initial Pydantic *models* / schemas +### Delete a Hero -Create an `ItemBase` and `UserBase` Pydantic *models* (or let's say "schemas") to have common attributes while creating or reading data. +We can also **delete** a `Hero`. -And create an `ItemCreate` and `UserCreate` that inherit from them (so they will have the same attributes), plus any additional data (attributes) needed for creation. +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *} -So, the user will also have a `password` when creating it. +### Run the App -But for security, the `password` won't be in other Pydantic *models*, for example, it won't be sent from the API when reading a user. +You can run the app: -//// tab | Python 3.10+ - -```Python hl_lines="1 4-6 9-10 21-22 25-26" -{!> ../../docs_src/sql_databases/sql_app_py310/schemas.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python hl_lines="3 6-8 11-12 23-24 27-28" -{!> ../../docs_src/sql_databases/sql_app_py39/schemas.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="3 6-8 11-12 23-24 27-28" -{!> ../../docs_src/sql_databases/sql_app/schemas.py!} -``` - -//// - -#### SQLAlchemy style and Pydantic style - -Notice that SQLAlchemy *models* define attributes using `=`, and pass the type as a parameter to `Column`, like in: - -```Python -name = Column(String) -``` +
-while Pydantic *models* declare the types using `:`, the new type annotation syntax/type hints: +```console +$ fastapi dev main.py -```Python -name: str +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` -Keep these in mind, so you don't get confused when using `=` and `:` with them. - -### Create Pydantic *models* / schemas for reading / returning - -Now create Pydantic *models* (schemas) that will be used when reading data, when returning it from the API. - -For example, before creating an item, we don't know what will be the ID assigned to it, but when reading it (when returning it from the API) we will already know its ID. +
-The same way, when reading a user, we can now declare that `items` will contain the items that belong to this user. +Then go to the `/docs` UI, you will see that **FastAPI** is using these **models** to **document** the API, and it will use them to **serialize** and **validate** the data too. -Not only the IDs of those items, but all the data that we defined in the Pydantic *model* for reading items: `Item`. +
+ +
-//// tab | Python 3.10+ +## Update the App with Multiple Models -```Python hl_lines="13-15 29-32" -{!> ../../docs_src/sql_databases/sql_app_py310/schemas.py!} -``` +Now let's **refactor** this app a bit to increase **security** and **versatility**. -//// +If you check the previous app, in the UI you can see that, up to now, it lets the client decide the `id` of the `Hero` to create. ๐Ÿ˜ฑ -//// tab | Python 3.9+ +We shouldn't let that happen, they could overwrite an `id` we already have assigned in the DB. Deciding the `id` should be done by the **backend** or the **database**, **not by the client**. -```Python hl_lines="15-17 31-34" -{!> ../../docs_src/sql_databases/sql_app_py39/schemas.py!} -``` +Additionally, we create a `secret_name` for the hero, but so far, we are returning it everywhere, that's not very **secret**... ๐Ÿ˜… -//// +We'll fix these things by adding a few **extra models**. Here's where SQLModel will shine. โœจ -//// tab | Python 3.8+ +### Create Multiple Models -```Python hl_lines="15-17 31-34" -{!> ../../docs_src/sql_databases/sql_app/schemas.py!} -``` +In **SQLModel**, any model class that has `table=True` is a **table model**. -//// +And any model class that doesn't have `table=True` is a **data model**, these ones are actually just Pydantic models (with a couple of small extra features). ๐Ÿค“ -/// tip +With SQLModel, we can use **inheritance** to **avoid duplicating** all the fields in all the cases. -Notice that the `User`, the Pydantic *model* that will be used when reading a user (returning it from the API) doesn't include the `password`. +#### `HeroBase` - the base class -/// +Let's start with a `HeroBase` model that has all the **fields that are shared** by all the models: -### Use Pydantic's `orm_mode` +* `name` +* `age` -Now, in the Pydantic *models* for reading, `Item` and `User`, add an internal `Config` class. +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *} -This `Config` class is used to provide configurations to Pydantic. +#### `Hero` - the *table model* -In the `Config` class, set the attribute `orm_mode = True`. +Then let's create `Hero`, the actual *table model*, with the **extra fields** that are not always in the other models: -//// tab | Python 3.10+ +* `id` +* `secret_name` -```Python hl_lines="13 17-18 29 34-35" -{!> ../../docs_src/sql_databases/sql_app_py310/schemas.py!} -``` +Because `Hero` inherits form `HeroBase`, it **also** has the **fields** declared in `HeroBase`, so all the fields for `Hero` are: -//// +* `id` +* `name` +* `age` +* `secret_name` -//// tab | Python 3.9+ +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *} -```Python hl_lines="15 19-20 31 36-37" -{!> ../../docs_src/sql_databases/sql_app_py39/schemas.py!} -``` +#### `HeroPublic` - the public *data model* -//// +Next, we create a `HeroPublic` model, this is the one that will be **returned** to the clients of the API. -//// tab | Python 3.8+ +It has the same fields as `HeroBase`, so it won't include `secret_name`. -```Python hl_lines="15 19-20 31 36-37" -{!> ../../docs_src/sql_databases/sql_app/schemas.py!} -``` +Finally, the identity of our heroes is protected! ๐Ÿฅท -//// +It also re-declares `id: int`. By doing this, we are making a **contract** with the API clients, so that they can always expect the `id` to be there and to be an `int` (it will never be `None`). /// tip -Notice it's assigning a value with `=`, like: - -`orm_mode = True` +Having the return model ensure that a value is always available and always `int` (not `None`) is very useful for the API clients, they can write much simpler code having this certainty. -It doesn't use `:` as for the type declarations before. - -This is setting a config value, not declaring a type. +Also, **automatically generated clients** will have simpler interfaces, so that the developers communicating with your API can have a much better time working with your API. ๐Ÿ˜Ž /// -Pydantic's `orm_mode` will tell the Pydantic *model* to read the data even if it is not a `dict`, but an ORM model (or any other arbitrary object with attributes). - -This way, instead of only trying to get the `id` value from a `dict`, as in: - -```Python -id = data["id"] -``` - -it will also try to get it from an attribute, as in: - -```Python -id = data.id -``` +All the fields in `HeroPublic` are the same as in `HeroBase`, with `id` declared as `int` (not `None`): -And with this, the Pydantic *model* is compatible with ORMs, and you can just declare it in the `response_model` argument in your *path operations*. +* `id` +* `name` +* `age` +* `secret_name` -You will be able to return a database model and it will read the data from it. +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *} -#### Technical Details about ORM mode +#### `HeroCreate` - the *data model* to create a hero -SQLAlchemy and many others are by default "lazy loading". +Now we create a `HeroCreate` model, this is the one that will **validate** the data from the clients. -That means, for example, that they don't fetch the data for relationships from the database unless you try to access the attribute that would contain that data. +It has the same fields as `HeroBase`, and it also has `secret_name`. -For example, accessing the attribute `items`: - -```Python -current_user.items -``` - -would make SQLAlchemy go to the `items` table and get the items for this user, but not before. - -Without `orm_mode`, if you returned a SQLAlchemy model from your *path operation*, it wouldn't include the relationship data. - -Even if you declared those relationships in your Pydantic models. - -But with ORM mode, as Pydantic itself will try to access the data it needs from attributes (instead of assuming a `dict`), you can declare the specific data you want to return and it will be able to go and get it, even from ORMs. - -## CRUD utils - -Now let's see the file `sql_app/crud.py`. - -In this file we will have reusable functions to interact with the data in the database. - -**CRUD** comes from: **C**reate, **R**ead, **U**pdate, and **D**elete. - -...although in this example we are only creating and reading. - -### Read data - -Import `Session` from `sqlalchemy.orm`, this will allow you to declare the type of the `db` parameters and have better type checks and completion in your functions. - -Import `models` (the SQLAlchemy models) and `schemas` (the Pydantic *models* / schemas). - -Create utility functions to: - -* Read a single user by ID and by email. -* Read multiple users. -* Read multiple items. - -```Python hl_lines="1 3 6-7 10-11 14-15 27-28" -{!../../docs_src/sql_databases/sql_app/crud.py!} -``` +Now, when the clients **create a new hero**, they will send the `secret_name`, it will be stored in the database, but those secret names won't be returned in the API to the clients. /// tip -By creating functions that are only dedicated to interacting with the database (get a user or an item) independent of your *path operation function*, you can more easily reuse them in multiple parts and also add unit tests for them. - -/// +This is how you would handle **passwords**. Receive them, but don't return them in the API. -### Create data - -Now create utility functions to create data. - -The steps are: - -* Create a SQLAlchemy model *instance* with your data. -* `add` that instance object to your database session. -* `commit` the changes to the database (so that they are saved). -* `refresh` your instance (so that it contains any new data from the database, like the generated ID). - -```Python hl_lines="18-24 31-36" -{!../../docs_src/sql_databases/sql_app/crud.py!} -``` - -/// info - -In Pydantic v1 the method was called `.dict()`, it was deprecated (but still supported) in Pydantic v2, and renamed to `.model_dump()`. - -The examples here use `.dict()` for compatibility with Pydantic v1, but you should use `.model_dump()` instead if you can use Pydantic v2. +You would also **hash** the values of the passwords before storing them, **never store them in plain text**. /// -/// tip +The fields of `HeroCreate` are: -The SQLAlchemy model for `User` contains a `hashed_password` that should contain a secure hashed version of the password. +* `name` +* `age` +* `secret_name` -But as what the API client provides is the original password, you need to extract it and generate the hashed password in your application. +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *} -And then pass the `hashed_password` argument with the value to save. +#### `HeroUpdate` - the *data model* to update a hero -/// +We didn't have a way to **update a hero** in the previous version of the app, but now with **multiple models**, we can do it. ๐ŸŽ‰ -/// warning +The `HeroUpdate` *data model* is somewhat special, it has **all the same fields** that would be needed to create a new hero, but all the fields are **optional** (they all have a default value). This way, when you update a hero, you can send just the fields that you want to update. -This example is not secure, the password is not hashed. +Because all the **fields actually change** (the type now includes `None` and they now have a default value of `None`), we need to **re-declare** them. -In a real life application you would need to hash the password and never save them in plaintext. +We don't really need to inherit from `HeroBase` because we are re-declaring all the fields. I'll leave it inheriting just for consistency, but this is not necessary. It's more a matter of personal taste. ๐Ÿคท -For more details, go back to the Security section in the tutorial. +The fields of `HeroUpdate` are: -Here we are focusing only on the tools and mechanics of databases. +* `name` +* `age` +* `secret_name` -/// - -/// tip - -Instead of passing each of the keyword arguments to `Item` and reading each one of them from the Pydantic *model*, we are generating a `dict` with the Pydantic *model*'s data with: - -`item.dict()` +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *} -and then we are passing the `dict`'s key-value pairs as the keyword arguments to the SQLAlchemy `Item`, with: +### Create with `HeroCreate` and return a `HeroPublic` -`Item(**item.dict())` - -And then we pass the extra keyword argument `owner_id` that is not provided by the Pydantic *model*, with: - -`Item(**item.dict(), owner_id=user_id)` - -/// +Now that we have **multiple models**, we can update the parts of the app that use them. -## Main **FastAPI** app +We receive in the request a `HeroCreate` *data model*, and from it, we create a `Hero` *table model*. -And now in the file `sql_app/main.py` let's integrate and use all the other parts we created before. +This new *table model* `Hero` will have the fields sent by the client, and will also have an `id` generated by the database. -### Create the database tables +Then we return the same *table model* `Hero` as is from the function. But as we declare the `response_model` with the `HeroPublic` *data model*, **FastAPI** will use `HeroPublic` to validate and serialize the data. -In a very simplistic way create the database tables: - -//// tab | Python 3.9+ - -```Python hl_lines="7" -{!> ../../docs_src/sql_databases/sql_app_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="9" -{!> ../../docs_src/sql_databases/sql_app/main.py!} -``` - -//// - -#### Alembic Note - -Normally you would probably initialize your database (create tables, etc) with Alembic. - -And you would also use Alembic for "migrations" (that's its main job). - -A "migration" is the set of steps needed whenever you change the structure of your SQLAlchemy models, add a new attribute, etc. to replicate those changes in the database, add a new column, a new table, etc. - -You can find an example of Alembic in a FastAPI project in the [Full Stack FastAPI Template](../project-generation.md){.internal-link target=_blank}. Specifically in the `alembic` directory in the source code. - -### Create a dependency - -Now use the `SessionLocal` class we created in the `sql_app/database.py` file to create a dependency. - -We need to have an independent database session/connection (`SessionLocal`) per request, use the same session through all the request and then close it after the request is finished. - -And then a new session will be created for the next request. - -For that, we will create a new dependency with `yield`, as explained before in the section about [Dependencies with `yield`](dependencies/dependencies-with-yield.md){.internal-link target=_blank}. - -Our dependency will create a new SQLAlchemy `SessionLocal` that will be used in a single request, and then close it once the request is finished. - -//// tab | Python 3.9+ - -```Python hl_lines="13-18" -{!> ../../docs_src/sql_databases/sql_app_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="15-20" -{!> ../../docs_src/sql_databases/sql_app/main.py!} -``` - -//// - -/// info - -We put the creation of the `SessionLocal()` and handling of the requests in a `try` block. - -And then we close it in the `finally` block. - -This way we make sure the database session is always closed after the request. Even if there was an exception while processing the request. - -But you can't raise another exception from the exit code (after `yield`). See more in [Dependencies with `yield` and `HTTPException`](dependencies/dependencies-with-yield.md#dependencies-with-yield-and-httpexception){.internal-link target=_blank} - -/// - -And then, when using the dependency in a *path operation function*, we declare it with the type `Session` we imported directly from SQLAlchemy. - -This will then give us better editor support inside the *path operation function*, because the editor will know that the `db` parameter is of type `Session`: - -//// tab | Python 3.9+ - -```Python hl_lines="22 30 36 45 51" -{!> ../../docs_src/sql_databases/sql_app_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="24 32 38 47 53" -{!> ../../docs_src/sql_databases/sql_app/main.py!} -``` - -//// - -/// info | "Technical Details" - -The parameter `db` is actually of type `SessionLocal`, but this class (created with `sessionmaker()`) is a "proxy" of a SQLAlchemy `Session`, so, the editor doesn't really know what methods are provided. - -But by declaring the type as `Session`, the editor now can know the available methods (`.add()`, `.query()`, `.commit()`, etc) and can provide better support (like completion). The type declaration doesn't affect the actual object. - -/// - -### Create your **FastAPI** *path operations* - -Now, finally, here's the standard **FastAPI** *path operations* code. - -//// tab | Python 3.9+ - -```Python hl_lines="21-26 29-32 35-40 43-47 50-53" -{!> ../../docs_src/sql_databases/sql_app_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="23-28 31-34 37-42 45-49 52-55" -{!> ../../docs_src/sql_databases/sql_app/main.py!} -``` - -//// - -We are creating the database session before each request in the dependency with `yield`, and then closing it afterwards. - -And then we can create the required dependency in the *path operation function*, to get that session directly. - -With that, we can just call `crud.get_user` directly from inside of the *path operation function* and use that session. - -/// tip - -Notice that the values you return are SQLAlchemy models, or lists of SQLAlchemy models. - -But as all the *path operations* have a `response_model` with Pydantic *models* / schemas using `orm_mode`, the data declared in your Pydantic models will be extracted from them and returned to the client, with all the normal filtering and validation. - -/// +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *} /// tip -Also notice that there are `response_models` that have standard Python types like `List[schemas.Item]`. - -But as the content/parameter of that `List` is a Pydantic *model* with `orm_mode`, the data will be retrieved and returned to the client as normally, without problems. - -/// - -### About `def` vs `async def` - -Here we are using SQLAlchemy code inside of the *path operation function* and in the dependency, and, in turn, it will go and communicate with an external database. - -That could potentially require some "waiting". - -But as SQLAlchemy doesn't have compatibility for using `await` directly, as would be with something like: - -```Python -user = await db.query(User).first() -``` - -...and instead we are using: - -```Python -user = db.query(User).first() -``` - -Then we should declare the *path operation functions* and the dependency without `async def`, just with a normal `def`, as: - -```Python hl_lines="2" -@app.get("/users/{user_id}", response_model=schemas.User) -def read_user(user_id: int, db: Session = Depends(get_db)): - db_user = crud.get_user(db, user_id=user_id) - ... -``` - -/// info +Now we use `response_model=HeroPublic` instead of the **return type annotation** `-> HeroPublic` because the value that we are returning is actually *not* a `HeroPublic`. -If you need to connect to your relational database asynchronously, see [Async SQL (Relational) Databases](../how-to/async-sql-encode-databases.md){.internal-link target=_blank}. +If we had declared `-> HeroPublic`, your editor and linter would complain (rightfully so) that you are returning a `Hero` instead of a `HeroPublic`. -/// - -/// note | "Very Technical Details" - -If you are curious and have a deep technical knowledge, you can check the very technical details of how this `async def` vs `def` is handled in the [Async](../async.md#very-technical-details){.internal-link target=_blank} docs. +By declaring it in `response_model` we are telling **FastAPI** to do its thing, without interfering with the type annotations and the help from your editor and other tools. /// -## Migrations - -Because we are using SQLAlchemy directly and we don't require any kind of plug-in for it to work with **FastAPI**, we could integrate database migrations with Alembic directly. - -And as the code related to SQLAlchemy and the SQLAlchemy models lives in separate independent files, you would even be able to perform the migrations with Alembic without having to install FastAPI, Pydantic, or anything else. - -The same way, you would be able to use the same SQLAlchemy models and utilities in other parts of your code that are not related to **FastAPI**. - -For example, in a background task worker with Celery, RQ, or ARQ. - -## Review all the files - - Remember you should have a directory named `my_super_project` that contains a sub-directory called `sql_app`. - -`sql_app` should have the following files: - -* `sql_app/__init__.py`: is an empty file. - -* `sql_app/database.py`: - -```Python -{!../../docs_src/sql_databases/sql_app/database.py!} -``` - -* `sql_app/models.py`: +### Read Heroes with `HeroPublic` -```Python -{!../../docs_src/sql_databases/sql_app/models.py!} -``` - -* `sql_app/schemas.py`: - -//// tab | Python 3.10+ - -```Python -{!> ../../docs_src/sql_databases/sql_app_py310/schemas.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python -{!> ../../docs_src/sql_databases/sql_app_py39/schemas.py!} -``` - -//// +We can do the same as before to **read** `Hero`s, again, we use `response_model=list[HeroPublic]` to ensure that the data is validated and serialized correctly. -//// tab | Python 3.8+ +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *} -```Python -{!> ../../docs_src/sql_databases/sql_app/schemas.py!} -``` - -//// - -* `sql_app/crud.py`: - -```Python -{!../../docs_src/sql_databases/sql_app/crud.py!} -``` +### Read One Hero with `HeroPublic` -* `sql_app/main.py`: +We can **read** a single hero: -//// tab | Python 3.9+ +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *} -```Python -{!> ../../docs_src/sql_databases/sql_app_py39/main.py!} -``` +### Update a Hero with `HeroUpdate` -//// +We can **update a hero**. For this we use an HTTP `PATCH` operation. -//// tab | Python 3.8+ +And in the code, we get a `dict` with all the data sent by the client, **only the data sent by the client**, excluding any values that would be there just for being the default values. To do it we use `exclude_unset=True`. This is the main trick. ๐Ÿช„ -```Python -{!> ../../docs_src/sql_databases/sql_app/main.py!} -``` +Then we use `hero_db.sqlmodel_update(hero_data)` to update the `hero_db` with the data from `hero_data`. -//// +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *} -## Check it +### Delete a Hero Again -You can copy this code and use it as is. +**Deleting** a hero stays pretty much the same. -/// info +We won't satisfy the desire to refactor everything in this one. ๐Ÿ˜… -In fact, the code shown here is part of the tests. As most of the code in these docs. +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *} -/// - -Then you can run it with Uvicorn: +### Run the App Again +You can run the app again:
```console -$ uvicorn sql_app.main:app --reload +$ fastapi dev main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
-And then, you can open your browser at http://127.0.0.1:8000/docs. - -And you will be able to interact with your **FastAPI** application, reading data from a real database: - - - -## Interact with the database directly - -If you want to explore the SQLite database (file) directly, independently of FastAPI, to debug its contents, add tables, columns, records, modify data, etc. you can use DB Browser for SQLite. - -It will look like this: +If you go to the `/docs` API UI, you will see that it is now updated, and it won't expect to receive the `id` from the client when creating a hero, etc. +
+
-You can also use an online SQLite browser like SQLite Viewer or ExtendsClass. - -## Alternative DB session with middleware - -If you can't use dependencies with `yield` -- for example, if you are not using **Python 3.7** and can't install the "backports" mentioned above for **Python 3.6** -- you can set up the session in a "middleware" in a similar way. - -A "middleware" is basically a function that is always executed for each request, with some code executed before, and some code executed after the endpoint function. - -### Create a middleware - -The middleware we'll add (just a function) will create a new SQLAlchemy `SessionLocal` for each request, add it to the request and then close it once the request is finished. - -//// tab | Python 3.9+ - -```Python hl_lines="12-20" -{!> ../../docs_src/sql_databases/sql_app_py39/alt_main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="14-22" -{!> ../../docs_src/sql_databases/sql_app/alt_main.py!} -``` - -//// - -/// info - -We put the creation of the `SessionLocal()` and handling of the requests in a `try` block. - -And then we close it in the `finally` block. - -This way we make sure the database session is always closed after the request. Even if there was an exception while processing the request. - -/// - -### About `request.state` - -`request.state` is a property of each `Request` object. It is there to store arbitrary objects attached to the request itself, like the database session in this case. You can read more about it in Starlette's docs about `Request` state. - -For us in this case, it helps us ensure a single database session is used through all the request, and then closed afterwards (in the middleware). - -### Dependencies with `yield` or middleware - -Adding a **middleware** here is similar to what a dependency with `yield` does, with some differences: - -* It requires more code and is a bit more complex. -* The middleware has to be an `async` function. - * If there is code in it that has to "wait" for the network, it could "block" your application there and degrade performance a bit. - * Although it's probably not very problematic here with the way `SQLAlchemy` works. - * But if you added more code to the middleware that had a lot of I/O waiting, it could then be problematic. -* A middleware is run for *every* request. - * So, a connection will be created for every request. - * Even when the *path operation* that handles that request didn't need the DB. - -/// tip - -It's probably better to use dependencies with `yield` when they are enough for the use case. - -/// - -/// info - -Dependencies with `yield` were added recently to **FastAPI**. +## Recap -A previous version of this tutorial only had the examples with a middleware and there are probably several applications using the middleware for database session management. +You can use **SQLModel** to interact with a SQL database and simplify the code with *data models* and *table models*. -/// +You can learn a lot more at the **SQLModel** docs, there's a longer mini tutorial on using SQLModel with **FastAPI**. ๐Ÿš€ diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index e55c6f176..8e0f6765d 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -71,13 +71,11 @@ plugins: redirects: redirect_maps: deployment/deta.md: deployment/cloud.md - advanced/sql-databases-peewee.md: how-to/sql-databases-peewee.md - advanced/async-sql-databases.md: how-to/async-sql-encode-databases.md - advanced/nosql-databases.md: how-to/nosql-databases-couchbase.md advanced/graphql.md: how-to/graphql.md advanced/custom-request-and-route.md: how-to/custom-request-and-route.md advanced/conditional-openapi.md: how-to/conditional-openapi.md advanced/extending-openapi.md: how-to/extending-openapi.md + advanced/testing-database.md: how-to/testing-database.md mkdocstrings: handlers: python: @@ -187,7 +185,6 @@ nav: - advanced/testing-websockets.md - advanced/testing-events.md - advanced/testing-dependencies.md - - advanced/testing-database.md - advanced/async-tests.md - advanced/settings.md - advanced/openapi-callbacks.md @@ -214,9 +211,7 @@ nav: - how-to/separate-openapi-schemas.md - how-to/custom-docs-ui-assets.md - how-to/configure-swagger-ui.md - - how-to/sql-databases-peewee.md - - how-to/async-sql-encode-databases.md - - how-to/nosql-databases-couchbase.md + - how-to/testing-database.md - Reference (Code API): - reference/index.md - reference/fastapi.md diff --git a/docs/zh/docs/advanced/testing-database.md b/docs/zh/docs/advanced/testing-database.md deleted file mode 100644 index ecab4f65b..000000000 --- a/docs/zh/docs/advanced/testing-database.md +++ /dev/null @@ -1,101 +0,0 @@ -# ๆต‹่ฏ•ๆ•ฐๆฎๅบ“ - -ๆ‚จ่ฟ˜ๅฏไปฅไฝฟ็”จ[ๆต‹่ฏ•ไพ่ต–้กน](testing-dependencies.md){.internal-link target=_blank}ไธญ็š„่ฆ†็›–ไพ่ต–้กนๆ–นๆณ•ๅ˜ๆ›ดๆต‹่ฏ•็š„ๆ•ฐๆฎๅบ“ใ€‚ - -ๅฎž็Žฐ่ฎพ็ฝฎๅ…ถๅฎƒๆต‹่ฏ•ๆ•ฐๆฎๅบ“ใ€ๅœจๆต‹่ฏ•ๅŽๅ›žๆปšๆ•ฐๆฎใ€ๆˆ–้ข„ๅกซๆต‹่ฏ•ๆ•ฐๆฎ็ญ‰ๆ“ไฝœใ€‚ - -ๆœฌ็ซ ็š„ไธป่ฆๆ€่ทฏไธŽไธŠไธ€็ซ ๅฎŒๅ…จ็›ธๅŒใ€‚ - -## ไธบ SQL ๅบ”็”จๆทปๅŠ ๆต‹่ฏ• - -ไธบไบ†ไฝฟ็”จๆต‹่ฏ•ๆ•ฐๆฎๅบ“๏ผŒๆˆ‘ไปฌ่ฆๅ‡็บง [SQL ๅ…ณ็ณปๅž‹ๆ•ฐๆฎๅบ“](../tutorial/sql-databases.md){.internal-link target=_blank} ไธ€็ซ ไธญ็š„็คบไพ‹ใ€‚ - -ๅบ”็”จ็š„ๆ‰€ๆœ‰ไปฃ็ ้ƒฝไธ€ๆ ท๏ผŒ็›ดๆŽฅๆŸฅ็œ‹้‚ฃไธ€็ซ ็š„็คบไพ‹ไปฃ็ ๅณๅฏใ€‚ - -ๆœฌ็ซ ๅชๆ˜ฏๆ–ฐๆทปๅŠ ไบ†ๆต‹่ฏ•ๆ–‡ไปถใ€‚ - -ๆญฃๅธธ็š„ไพ่ต–้กน `get_db()` ่ฟ”ๅ›žๆ•ฐๆฎๅบ“ไผš่ฏใ€‚ - -ๆต‹่ฏ•ๆ—ถไฝฟ็”จ่ฆ†็›–ไพ่ต–้กน่ฟ”ๅ›ž่‡ชๅฎšไน‰ๆ•ฐๆฎๅบ“ไผš่ฏไปฃๆ›ฟๆญฃๅธธ็š„ไพ่ต–้กนใ€‚ - -ๆœฌไพ‹ไธญ๏ผŒ่ฆๅˆ›ๅปบไป…็”จไบŽๆต‹่ฏ•็š„ไธดๆ—ถๆ•ฐๆฎๅบ“ใ€‚ - -## ๆ–‡ไปถๆžถๆž„ - -ๅˆ›ๅปบๆ–ฐๆ–‡ไปถ `sql_app/tests/test_sql_app.py`ใ€‚ - -ๅ› ๆญค๏ผŒๆ–ฐ็š„ๆ–‡ไปถๆžถๆž„ๅฆ‚ไธ‹๏ผš - -``` hl_lines="9-11" -. -โ””โ”€โ”€ sql_app - โ”œโ”€โ”€ __init__.py - โ”œโ”€โ”€ crud.py - โ”œโ”€โ”€ database.py - โ”œโ”€โ”€ main.py - โ”œโ”€โ”€ models.py - โ”œโ”€โ”€ schemas.py - โ””โ”€โ”€ tests - โ”œโ”€โ”€ __init__.py - โ””โ”€โ”€ test_sql_app.py -``` - -## ๅˆ›ๅปบๆ–ฐ็š„ๆ•ฐๆฎๅบ“ไผš่ฏ - -้ฆ–ๅ…ˆ๏ผŒไธบๆ–ฐๅปบๆ•ฐๆฎๅบ“ๅˆ›ๅปบๆ–ฐ็š„ๆ•ฐๆฎๅบ“ไผš่ฏใ€‚ - -ๆต‹่ฏ•ๆ—ถ๏ผŒไฝฟ็”จ `test.db` ๆ›ฟไปฃ `sql_app.db`ใ€‚ - -ไฝ†ๅ…ถไฝ™็š„ไผš่ฏไปฃ็ ๅŸบๆœฌไธŠ้ƒฝๆ˜ฏไธ€ๆ ท็š„๏ผŒๅช่ฆๅคๅˆถๅฐฑๅฏไปฅไบ†ใ€‚ - -```Python hl_lines="8-13" -{!../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} -``` - -/// tip | "ๆ็คบ" - -ไธบๅ‡ๅฐ‘ไปฃ็ ้‡ๅค๏ผŒๆœ€ๅฅฝๆŠŠ่ฟ™ๆฎตไปฃ็ ๅ†™ๆˆๅ‡ฝๆ•ฐ๏ผŒๅœจ `database.py` ไธŽ `tests/test_sql_app.py`ไธญไฝฟ็”จใ€‚ - -ไธบไบ†ๆŠŠๆณจๆ„ๅŠ›้›†ไธญๅœจๆต‹่ฏ•ไปฃ็ ไธŠ๏ผŒๆœฌไพ‹ๅชๆ˜ฏๅคๅˆถไบ†่ฟ™ๆฎตไปฃ็ ใ€‚ - -/// - -## ๅˆ›ๅปบๆ•ฐๆฎๅบ“ - -ๅ› ไธบ็Žฐๅœจๆ˜ฏๆƒณๅœจๆ–ฐๆ–‡ไปถไธญไฝฟ็”จๆ–ฐๆ•ฐๆฎๅบ“๏ผŒๆ‰€ไปฅ่ฆไฝฟ็”จไปฅไธ‹ไปฃ็ ๅˆ›ๅปบๆ•ฐๆฎๅบ“๏ผš - -```Python -Base.metadata.create_all(bind=engine) -``` - -ไธ€่ˆฌๆ˜ฏๅœจ `main.py` ไธญ่ฐƒ็”จ่ฟ™่กŒไปฃ็ ๏ผŒไฝ†ๅœจ `main.py` ้‡Œ๏ผŒ่ฟ™่กŒไปฃ็ ็”จไบŽๅˆ›ๅปบ `sql_app.db`๏ผŒไฝ†ๆ˜ฏ็Žฐๅœจ่ฆไธบๆต‹่ฏ•ๅˆ›ๅปบ `test.db`ใ€‚ - -ๅ› ๆญค๏ผŒ่ฆๅœจๆต‹่ฏ•ไปฃ็ ไธญๆทปๅŠ ่ฟ™่กŒไปฃ็ ๅˆ›ๅปบๆ–ฐ็š„ๆ•ฐๆฎๅบ“ๆ–‡ไปถใ€‚ - -```Python hl_lines="16" -{!../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} -``` - -## ่ฆ†็›–ไพ่ต–้กน - -ๆŽฅไธ‹ๆฅ๏ผŒๅˆ›ๅปบ่ฆ†็›–ไพ่ต–้กน๏ผŒๅนถไธบๅบ”็”จๆทปๅŠ ่ฆ†็›–ๅ†…ๅฎนใ€‚ - -```Python hl_lines="19-24 27" -{!../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} -``` - -/// tip | "ๆ็คบ" - -`overrider_get_db()` ไธŽ `get_db` ็š„ไปฃ็ ๅ‡ ไนŽๅฎŒๅ…จไธ€ๆ ท๏ผŒๅชๆ˜ฏ `overrider_get_db` ไธญไฝฟ็”จๆต‹่ฏ•ๆ•ฐๆฎๅบ“็š„ `TestingSessionLocal`ใ€‚ - -/// - -## ๆต‹่ฏ•ๅบ”็”จ - -็„ถๅŽ๏ผŒๅฐฑๅฏไปฅๆญฃๅธธๆต‹่ฏ•ไบ†ใ€‚ - -```Python hl_lines="32-47" -{!../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} -``` - -ๆต‹่ฏ•ๆœŸ้—ด๏ผŒๆ‰€ๆœ‰ๅœจๆ•ฐๆฎๅบ“ไธญๆ‰€ๅš็š„ไฟฎๆ”น้ƒฝๅœจ `test.db` ้‡Œ๏ผŒไธไผšๅฝฑๅ“ไธปๅบ”็”จ็š„ `sql_app.db`ใ€‚ diff --git a/docs_src/async_sql_databases/tutorial001.py b/docs_src/async_sql_databases/tutorial001.py deleted file mode 100644 index cbf43d790..000000000 --- a/docs_src/async_sql_databases/tutorial001.py +++ /dev/null @@ -1,65 +0,0 @@ -from typing import List - -import databases -import sqlalchemy -from fastapi import FastAPI -from pydantic import BaseModel - -# SQLAlchemy specific code, as with any other app -DATABASE_URL = "sqlite:///./test.db" -# DATABASE_URL = "postgresql://user:password@postgresserver/db" - -database = databases.Database(DATABASE_URL) - -metadata = sqlalchemy.MetaData() - -notes = sqlalchemy.Table( - "notes", - metadata, - sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True), - sqlalchemy.Column("text", sqlalchemy.String), - sqlalchemy.Column("completed", sqlalchemy.Boolean), -) - - -engine = sqlalchemy.create_engine( - DATABASE_URL, connect_args={"check_same_thread": False} -) -metadata.create_all(engine) - - -class NoteIn(BaseModel): - text: str - completed: bool - - -class Note(BaseModel): - id: int - text: str - completed: bool - - -app = FastAPI() - - -@app.on_event("startup") -async def startup(): - await database.connect() - - -@app.on_event("shutdown") -async def shutdown(): - await database.disconnect() - - -@app.get("/notes/", response_model=List[Note]) -async def read_notes(): - query = notes.select() - return await database.fetch_all(query) - - -@app.post("/notes/", response_model=Note) -async def create_note(note: NoteIn): - query = notes.insert().values(text=note.text, completed=note.completed) - last_record_id = await database.execute(query) - return {**note.dict(), "id": last_record_id} diff --git a/docs_src/nosql_databases/tutorial001.py b/docs_src/nosql_databases/tutorial001.py deleted file mode 100644 index 91893e528..000000000 --- a/docs_src/nosql_databases/tutorial001.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Union - -from couchbase import LOCKMODE_WAIT -from couchbase.bucket import Bucket -from couchbase.cluster import Cluster, PasswordAuthenticator -from fastapi import FastAPI -from pydantic import BaseModel - -USERPROFILE_DOC_TYPE = "userprofile" - - -def get_bucket(): - cluster = Cluster( - "couchbase://couchbasehost:8091?fetch_mutation_tokens=1&operation_timeout=30&n1ql_timeout=300" - ) - authenticator = PasswordAuthenticator("username", "password") - cluster.authenticate(authenticator) - bucket: Bucket = cluster.open_bucket("bucket_name", lockmode=LOCKMODE_WAIT) - bucket.timeout = 30 - bucket.n1ql_timeout = 300 - return bucket - - -class User(BaseModel): - username: str - email: Union[str, None] = None - full_name: Union[str, None] = None - disabled: Union[bool, None] = None - - -class UserInDB(User): - type: str = USERPROFILE_DOC_TYPE - hashed_password: str - - -def get_user(bucket: Bucket, username: str): - doc_id = f"userprofile::{username}" - result = bucket.get(doc_id, quiet=True) - if not result.value: - return None - user = UserInDB(**result.value) - return user - - -# FastAPI specific code -app = FastAPI() - - -@app.get("/users/{username}", response_model=User) -def read_user(username: str): - bucket = get_bucket() - user = get_user(bucket=bucket, username=username) - return user diff --git a/docs_src/sql_databases/sql_app/__init__.py b/docs_src/sql_databases/sql_app/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/sql_databases/sql_app/alt_main.py b/docs_src/sql_databases/sql_app/alt_main.py deleted file mode 100644 index f7206bcb4..000000000 --- a/docs_src/sql_databases/sql_app/alt_main.py +++ /dev/null @@ -1,62 +0,0 @@ -from typing import List - -from fastapi import Depends, FastAPI, HTTPException, Request, Response -from sqlalchemy.orm import Session - -from . import crud, models, schemas -from .database import SessionLocal, engine - -models.Base.metadata.create_all(bind=engine) - -app = FastAPI() - - -@app.middleware("http") -async def db_session_middleware(request: Request, call_next): - response = Response("Internal server error", status_code=500) - try: - request.state.db = SessionLocal() - response = await call_next(request) - finally: - request.state.db.close() - return response - - -# Dependency -def get_db(request: Request): - return request.state.db - - -@app.post("/users/", response_model=schemas.User) -def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): - db_user = crud.get_user_by_email(db, email=user.email) - if db_user: - raise HTTPException(status_code=400, detail="Email already registered") - return crud.create_user(db=db, user=user) - - -@app.get("/users/", response_model=List[schemas.User]) -def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - users = crud.get_users(db, skip=skip, limit=limit) - return users - - -@app.get("/users/{user_id}", response_model=schemas.User) -def read_user(user_id: int, db: Session = Depends(get_db)): - db_user = crud.get_user(db, user_id=user_id) - if db_user is None: - raise HTTPException(status_code=404, detail="User not found") - return db_user - - -@app.post("/users/{user_id}/items/", response_model=schemas.Item) -def create_item_for_user( - user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db) -): - return crud.create_user_item(db=db, item=item, user_id=user_id) - - -@app.get("/items/", response_model=List[schemas.Item]) -def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - items = crud.get_items(db, skip=skip, limit=limit) - return items diff --git a/docs_src/sql_databases/sql_app/crud.py b/docs_src/sql_databases/sql_app/crud.py deleted file mode 100644 index 679acdb5c..000000000 --- a/docs_src/sql_databases/sql_app/crud.py +++ /dev/null @@ -1,36 +0,0 @@ -from sqlalchemy.orm import Session - -from . import models, schemas - - -def get_user(db: Session, user_id: int): - return db.query(models.User).filter(models.User.id == user_id).first() - - -def get_user_by_email(db: Session, email: str): - return db.query(models.User).filter(models.User.email == email).first() - - -def get_users(db: Session, skip: int = 0, limit: int = 100): - return db.query(models.User).offset(skip).limit(limit).all() - - -def create_user(db: Session, user: schemas.UserCreate): - fake_hashed_password = user.password + "notreallyhashed" - db_user = models.User(email=user.email, hashed_password=fake_hashed_password) - db.add(db_user) - db.commit() - db.refresh(db_user) - return db_user - - -def get_items(db: Session, skip: int = 0, limit: int = 100): - return db.query(models.Item).offset(skip).limit(limit).all() - - -def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int): - db_item = models.Item(**item.dict(), owner_id=user_id) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item diff --git a/docs_src/sql_databases/sql_app/database.py b/docs_src/sql_databases/sql_app/database.py deleted file mode 100644 index 45a8b9f69..000000000 --- a/docs_src/sql_databases/sql_app/database.py +++ /dev/null @@ -1,13 +0,0 @@ -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db" -# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db" - -engine = create_engine( - SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} -) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - -Base = declarative_base() diff --git a/docs_src/sql_databases/sql_app/main.py b/docs_src/sql_databases/sql_app/main.py deleted file mode 100644 index e7508c59d..000000000 --- a/docs_src/sql_databases/sql_app/main.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import List - -from fastapi import Depends, FastAPI, HTTPException -from sqlalchemy.orm import Session - -from . import crud, models, schemas -from .database import SessionLocal, engine - -models.Base.metadata.create_all(bind=engine) - -app = FastAPI() - - -# Dependency -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - - -@app.post("/users/", response_model=schemas.User) -def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): - db_user = crud.get_user_by_email(db, email=user.email) - if db_user: - raise HTTPException(status_code=400, detail="Email already registered") - return crud.create_user(db=db, user=user) - - -@app.get("/users/", response_model=List[schemas.User]) -def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - users = crud.get_users(db, skip=skip, limit=limit) - return users - - -@app.get("/users/{user_id}", response_model=schemas.User) -def read_user(user_id: int, db: Session = Depends(get_db)): - db_user = crud.get_user(db, user_id=user_id) - if db_user is None: - raise HTTPException(status_code=404, detail="User not found") - return db_user - - -@app.post("/users/{user_id}/items/", response_model=schemas.Item) -def create_item_for_user( - user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db) -): - return crud.create_user_item(db=db, item=item, user_id=user_id) - - -@app.get("/items/", response_model=List[schemas.Item]) -def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - items = crud.get_items(db, skip=skip, limit=limit) - return items diff --git a/docs_src/sql_databases/sql_app/models.py b/docs_src/sql_databases/sql_app/models.py deleted file mode 100644 index 09ae2a807..000000000 --- a/docs_src/sql_databases/sql_app/models.py +++ /dev/null @@ -1,26 +0,0 @@ -from sqlalchemy import Boolean, Column, ForeignKey, Integer, String -from sqlalchemy.orm import relationship - -from .database import Base - - -class User(Base): - __tablename__ = "users" - - id = Column(Integer, primary_key=True) - email = Column(String, unique=True, index=True) - hashed_password = Column(String) - is_active = Column(Boolean, default=True) - - items = relationship("Item", back_populates="owner") - - -class Item(Base): - __tablename__ = "items" - - id = Column(Integer, primary_key=True) - title = Column(String, index=True) - description = Column(String, index=True) - owner_id = Column(Integer, ForeignKey("users.id")) - - owner = relationship("User", back_populates="items") diff --git a/docs_src/sql_databases/sql_app/schemas.py b/docs_src/sql_databases/sql_app/schemas.py deleted file mode 100644 index c49beba88..000000000 --- a/docs_src/sql_databases/sql_app/schemas.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import List, Union - -from pydantic import BaseModel - - -class ItemBase(BaseModel): - title: str - description: Union[str, None] = None - - -class ItemCreate(ItemBase): - pass - - -class Item(ItemBase): - id: int - owner_id: int - - class Config: - orm_mode = True - - -class UserBase(BaseModel): - email: str - - -class UserCreate(UserBase): - password: str - - -class User(UserBase): - id: int - is_active: bool - items: List[Item] = [] - - class Config: - orm_mode = True diff --git a/docs_src/sql_databases/sql_app/tests/__init__.py b/docs_src/sql_databases/sql_app/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 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 deleted file mode 100644 index 5f55add0a..000000000 --- a/docs_src/sql_databases/sql_app/tests/test_sql_app.py +++ /dev/null @@ -1,50 +0,0 @@ -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://" - -engine = create_engine( - SQLALCHEMY_DATABASE_URL, - connect_args={"check_same_thread": False}, - poolclass=StaticPool, -) -TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - - -Base.metadata.create_all(bind=engine) - - -def override_get_db(): - try: - db = TestingSessionLocal() - yield db - finally: - db.close() - - -app.dependency_overrides[get_db] = override_get_db - -client = TestClient(app) - - -def test_create_user(): - response = client.post( - "/users/", - json={"email": "deadpool@example.com", "password": "chimichangas4life"}, - ) - assert response.status_code == 200, response.text - data = response.json() - assert data["email"] == "deadpool@example.com" - assert "id" in data - user_id = data["id"] - - response = client.get(f"/users/{user_id}") - assert response.status_code == 200, response.text - data = response.json() - assert data["email"] == "deadpool@example.com" - assert data["id"] == user_id diff --git a/docs_src/sql_databases/sql_app_py310/__init__.py b/docs_src/sql_databases/sql_app_py310/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/sql_databases/sql_app_py310/alt_main.py b/docs_src/sql_databases/sql_app_py310/alt_main.py deleted file mode 100644 index 5de88ec3a..000000000 --- a/docs_src/sql_databases/sql_app_py310/alt_main.py +++ /dev/null @@ -1,60 +0,0 @@ -from fastapi import Depends, FastAPI, HTTPException, Request, Response -from sqlalchemy.orm import Session - -from . import crud, models, schemas -from .database import SessionLocal, engine - -models.Base.metadata.create_all(bind=engine) - -app = FastAPI() - - -@app.middleware("http") -async def db_session_middleware(request: Request, call_next): - response = Response("Internal server error", status_code=500) - try: - request.state.db = SessionLocal() - response = await call_next(request) - finally: - request.state.db.close() - return response - - -# Dependency -def get_db(request: Request): - return request.state.db - - -@app.post("/users/", response_model=schemas.User) -def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): - db_user = crud.get_user_by_email(db, email=user.email) - if db_user: - raise HTTPException(status_code=400, detail="Email already registered") - return crud.create_user(db=db, user=user) - - -@app.get("/users/", response_model=list[schemas.User]) -def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - users = crud.get_users(db, skip=skip, limit=limit) - return users - - -@app.get("/users/{user_id}", response_model=schemas.User) -def read_user(user_id: int, db: Session = Depends(get_db)): - db_user = crud.get_user(db, user_id=user_id) - if db_user is None: - raise HTTPException(status_code=404, detail="User not found") - return db_user - - -@app.post("/users/{user_id}/items/", response_model=schemas.Item) -def create_item_for_user( - user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db) -): - return crud.create_user_item(db=db, item=item, user_id=user_id) - - -@app.get("/items/", response_model=list[schemas.Item]) -def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - items = crud.get_items(db, skip=skip, limit=limit) - return items diff --git a/docs_src/sql_databases/sql_app_py310/crud.py b/docs_src/sql_databases/sql_app_py310/crud.py deleted file mode 100644 index 679acdb5c..000000000 --- a/docs_src/sql_databases/sql_app_py310/crud.py +++ /dev/null @@ -1,36 +0,0 @@ -from sqlalchemy.orm import Session - -from . import models, schemas - - -def get_user(db: Session, user_id: int): - return db.query(models.User).filter(models.User.id == user_id).first() - - -def get_user_by_email(db: Session, email: str): - return db.query(models.User).filter(models.User.email == email).first() - - -def get_users(db: Session, skip: int = 0, limit: int = 100): - return db.query(models.User).offset(skip).limit(limit).all() - - -def create_user(db: Session, user: schemas.UserCreate): - fake_hashed_password = user.password + "notreallyhashed" - db_user = models.User(email=user.email, hashed_password=fake_hashed_password) - db.add(db_user) - db.commit() - db.refresh(db_user) - return db_user - - -def get_items(db: Session, skip: int = 0, limit: int = 100): - return db.query(models.Item).offset(skip).limit(limit).all() - - -def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int): - db_item = models.Item(**item.dict(), owner_id=user_id) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item diff --git a/docs_src/sql_databases/sql_app_py310/database.py b/docs_src/sql_databases/sql_app_py310/database.py deleted file mode 100644 index 45a8b9f69..000000000 --- a/docs_src/sql_databases/sql_app_py310/database.py +++ /dev/null @@ -1,13 +0,0 @@ -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db" -# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db" - -engine = create_engine( - SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} -) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - -Base = declarative_base() diff --git a/docs_src/sql_databases/sql_app_py310/main.py b/docs_src/sql_databases/sql_app_py310/main.py deleted file mode 100644 index a9856d0b6..000000000 --- a/docs_src/sql_databases/sql_app_py310/main.py +++ /dev/null @@ -1,53 +0,0 @@ -from fastapi import Depends, FastAPI, HTTPException -from sqlalchemy.orm import Session - -from . import crud, models, schemas -from .database import SessionLocal, engine - -models.Base.metadata.create_all(bind=engine) - -app = FastAPI() - - -# Dependency -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - - -@app.post("/users/", response_model=schemas.User) -def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): - db_user = crud.get_user_by_email(db, email=user.email) - if db_user: - raise HTTPException(status_code=400, detail="Email already registered") - return crud.create_user(db=db, user=user) - - -@app.get("/users/", response_model=list[schemas.User]) -def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - users = crud.get_users(db, skip=skip, limit=limit) - return users - - -@app.get("/users/{user_id}", response_model=schemas.User) -def read_user(user_id: int, db: Session = Depends(get_db)): - db_user = crud.get_user(db, user_id=user_id) - if db_user is None: - raise HTTPException(status_code=404, detail="User not found") - return db_user - - -@app.post("/users/{user_id}/items/", response_model=schemas.Item) -def create_item_for_user( - user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db) -): - return crud.create_user_item(db=db, item=item, user_id=user_id) - - -@app.get("/items/", response_model=list[schemas.Item]) -def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - items = crud.get_items(db, skip=skip, limit=limit) - return items diff --git a/docs_src/sql_databases/sql_app_py310/models.py b/docs_src/sql_databases/sql_app_py310/models.py deleted file mode 100644 index 09ae2a807..000000000 --- a/docs_src/sql_databases/sql_app_py310/models.py +++ /dev/null @@ -1,26 +0,0 @@ -from sqlalchemy import Boolean, Column, ForeignKey, Integer, String -from sqlalchemy.orm import relationship - -from .database import Base - - -class User(Base): - __tablename__ = "users" - - id = Column(Integer, primary_key=True) - email = Column(String, unique=True, index=True) - hashed_password = Column(String) - is_active = Column(Boolean, default=True) - - items = relationship("Item", back_populates="owner") - - -class Item(Base): - __tablename__ = "items" - - id = Column(Integer, primary_key=True) - title = Column(String, index=True) - description = Column(String, index=True) - owner_id = Column(Integer, ForeignKey("users.id")) - - owner = relationship("User", back_populates="items") diff --git a/docs_src/sql_databases/sql_app_py310/schemas.py b/docs_src/sql_databases/sql_app_py310/schemas.py deleted file mode 100644 index aea2e3f10..000000000 --- a/docs_src/sql_databases/sql_app_py310/schemas.py +++ /dev/null @@ -1,35 +0,0 @@ -from pydantic import BaseModel - - -class ItemBase(BaseModel): - title: str - description: str | None = None - - -class ItemCreate(ItemBase): - pass - - -class Item(ItemBase): - id: int - owner_id: int - - class Config: - orm_mode = True - - -class UserBase(BaseModel): - email: str - - -class UserCreate(UserBase): - password: str - - -class User(UserBase): - id: int - is_active: bool - items: list[Item] = [] - - class Config: - orm_mode = True diff --git a/docs_src/sql_databases/sql_app_py310/tests/__init__.py b/docs_src/sql_databases/sql_app_py310/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/sql_databases/sql_app_py310/tests/test_sql_app.py b/docs_src/sql_databases/sql_app_py310/tests/test_sql_app.py deleted file mode 100644 index c60c3356f..000000000 --- a/docs_src/sql_databases/sql_app_py310/tests/test_sql_app.py +++ /dev/null @@ -1,47 +0,0 @@ -from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker - -from ..database import Base -from ..main import app, get_db - -SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" - -engine = create_engine( - SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} -) -TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - - -Base.metadata.create_all(bind=engine) - - -def override_get_db(): - try: - db = TestingSessionLocal() - yield db - finally: - db.close() - - -app.dependency_overrides[get_db] = override_get_db - -client = TestClient(app) - - -def test_create_user(): - response = client.post( - "/users/", - json={"email": "deadpool@example.com", "password": "chimichangas4life"}, - ) - assert response.status_code == 200, response.text - data = response.json() - assert data["email"] == "deadpool@example.com" - assert "id" in data - user_id = data["id"] - - response = client.get(f"/users/{user_id}") - assert response.status_code == 200, response.text - data = response.json() - assert data["email"] == "deadpool@example.com" - assert data["id"] == user_id diff --git a/docs_src/sql_databases/sql_app_py39/__init__.py b/docs_src/sql_databases/sql_app_py39/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/sql_databases/sql_app_py39/alt_main.py b/docs_src/sql_databases/sql_app_py39/alt_main.py deleted file mode 100644 index 5de88ec3a..000000000 --- a/docs_src/sql_databases/sql_app_py39/alt_main.py +++ /dev/null @@ -1,60 +0,0 @@ -from fastapi import Depends, FastAPI, HTTPException, Request, Response -from sqlalchemy.orm import Session - -from . import crud, models, schemas -from .database import SessionLocal, engine - -models.Base.metadata.create_all(bind=engine) - -app = FastAPI() - - -@app.middleware("http") -async def db_session_middleware(request: Request, call_next): - response = Response("Internal server error", status_code=500) - try: - request.state.db = SessionLocal() - response = await call_next(request) - finally: - request.state.db.close() - return response - - -# Dependency -def get_db(request: Request): - return request.state.db - - -@app.post("/users/", response_model=schemas.User) -def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): - db_user = crud.get_user_by_email(db, email=user.email) - if db_user: - raise HTTPException(status_code=400, detail="Email already registered") - return crud.create_user(db=db, user=user) - - -@app.get("/users/", response_model=list[schemas.User]) -def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - users = crud.get_users(db, skip=skip, limit=limit) - return users - - -@app.get("/users/{user_id}", response_model=schemas.User) -def read_user(user_id: int, db: Session = Depends(get_db)): - db_user = crud.get_user(db, user_id=user_id) - if db_user is None: - raise HTTPException(status_code=404, detail="User not found") - return db_user - - -@app.post("/users/{user_id}/items/", response_model=schemas.Item) -def create_item_for_user( - user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db) -): - return crud.create_user_item(db=db, item=item, user_id=user_id) - - -@app.get("/items/", response_model=list[schemas.Item]) -def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - items = crud.get_items(db, skip=skip, limit=limit) - return items diff --git a/docs_src/sql_databases/sql_app_py39/crud.py b/docs_src/sql_databases/sql_app_py39/crud.py deleted file mode 100644 index 679acdb5c..000000000 --- a/docs_src/sql_databases/sql_app_py39/crud.py +++ /dev/null @@ -1,36 +0,0 @@ -from sqlalchemy.orm import Session - -from . import models, schemas - - -def get_user(db: Session, user_id: int): - return db.query(models.User).filter(models.User.id == user_id).first() - - -def get_user_by_email(db: Session, email: str): - return db.query(models.User).filter(models.User.email == email).first() - - -def get_users(db: Session, skip: int = 0, limit: int = 100): - return db.query(models.User).offset(skip).limit(limit).all() - - -def create_user(db: Session, user: schemas.UserCreate): - fake_hashed_password = user.password + "notreallyhashed" - db_user = models.User(email=user.email, hashed_password=fake_hashed_password) - db.add(db_user) - db.commit() - db.refresh(db_user) - return db_user - - -def get_items(db: Session, skip: int = 0, limit: int = 100): - return db.query(models.Item).offset(skip).limit(limit).all() - - -def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int): - db_item = models.Item(**item.dict(), owner_id=user_id) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item diff --git a/docs_src/sql_databases/sql_app_py39/database.py b/docs_src/sql_databases/sql_app_py39/database.py deleted file mode 100644 index 45a8b9f69..000000000 --- a/docs_src/sql_databases/sql_app_py39/database.py +++ /dev/null @@ -1,13 +0,0 @@ -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker - -SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db" -# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db" - -engine = create_engine( - SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} -) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - -Base = declarative_base() diff --git a/docs_src/sql_databases/sql_app_py39/main.py b/docs_src/sql_databases/sql_app_py39/main.py deleted file mode 100644 index a9856d0b6..000000000 --- a/docs_src/sql_databases/sql_app_py39/main.py +++ /dev/null @@ -1,53 +0,0 @@ -from fastapi import Depends, FastAPI, HTTPException -from sqlalchemy.orm import Session - -from . import crud, models, schemas -from .database import SessionLocal, engine - -models.Base.metadata.create_all(bind=engine) - -app = FastAPI() - - -# Dependency -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - - -@app.post("/users/", response_model=schemas.User) -def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): - db_user = crud.get_user_by_email(db, email=user.email) - if db_user: - raise HTTPException(status_code=400, detail="Email already registered") - return crud.create_user(db=db, user=user) - - -@app.get("/users/", response_model=list[schemas.User]) -def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - users = crud.get_users(db, skip=skip, limit=limit) - return users - - -@app.get("/users/{user_id}", response_model=schemas.User) -def read_user(user_id: int, db: Session = Depends(get_db)): - db_user = crud.get_user(db, user_id=user_id) - if db_user is None: - raise HTTPException(status_code=404, detail="User not found") - return db_user - - -@app.post("/users/{user_id}/items/", response_model=schemas.Item) -def create_item_for_user( - user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db) -): - return crud.create_user_item(db=db, item=item, user_id=user_id) - - -@app.get("/items/", response_model=list[schemas.Item]) -def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): - items = crud.get_items(db, skip=skip, limit=limit) - return items diff --git a/docs_src/sql_databases/sql_app_py39/models.py b/docs_src/sql_databases/sql_app_py39/models.py deleted file mode 100644 index 09ae2a807..000000000 --- a/docs_src/sql_databases/sql_app_py39/models.py +++ /dev/null @@ -1,26 +0,0 @@ -from sqlalchemy import Boolean, Column, ForeignKey, Integer, String -from sqlalchemy.orm import relationship - -from .database import Base - - -class User(Base): - __tablename__ = "users" - - id = Column(Integer, primary_key=True) - email = Column(String, unique=True, index=True) - hashed_password = Column(String) - is_active = Column(Boolean, default=True) - - items = relationship("Item", back_populates="owner") - - -class Item(Base): - __tablename__ = "items" - - id = Column(Integer, primary_key=True) - title = Column(String, index=True) - description = Column(String, index=True) - owner_id = Column(Integer, ForeignKey("users.id")) - - owner = relationship("User", back_populates="items") diff --git a/docs_src/sql_databases/sql_app_py39/schemas.py b/docs_src/sql_databases/sql_app_py39/schemas.py deleted file mode 100644 index dadc403d9..000000000 --- a/docs_src/sql_databases/sql_app_py39/schemas.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Union - -from pydantic import BaseModel - - -class ItemBase(BaseModel): - title: str - description: Union[str, None] = None - - -class ItemCreate(ItemBase): - pass - - -class Item(ItemBase): - id: int - owner_id: int - - class Config: - orm_mode = True - - -class UserBase(BaseModel): - email: str - - -class UserCreate(UserBase): - password: str - - -class User(UserBase): - id: int - is_active: bool - items: list[Item] = [] - - class Config: - orm_mode = True diff --git a/docs_src/sql_databases/sql_app_py39/tests/__init__.py b/docs_src/sql_databases/sql_app_py39/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/sql_databases/sql_app_py39/tests/test_sql_app.py b/docs_src/sql_databases/sql_app_py39/tests/test_sql_app.py deleted file mode 100644 index c60c3356f..000000000 --- a/docs_src/sql_databases/sql_app_py39/tests/test_sql_app.py +++ /dev/null @@ -1,47 +0,0 @@ -from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker - -from ..database import Base -from ..main import app, get_db - -SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" - -engine = create_engine( - SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} -) -TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - - -Base.metadata.create_all(bind=engine) - - -def override_get_db(): - try: - db = TestingSessionLocal() - yield db - finally: - db.close() - - -app.dependency_overrides[get_db] = override_get_db - -client = TestClient(app) - - -def test_create_user(): - response = client.post( - "/users/", - json={"email": "deadpool@example.com", "password": "chimichangas4life"}, - ) - assert response.status_code == 200, response.text - data = response.json() - assert data["email"] == "deadpool@example.com" - assert "id" in data - user_id = data["id"] - - response = client.get(f"/users/{user_id}") - assert response.status_code == 200, response.text - data = response.json() - assert data["email"] == "deadpool@example.com" - assert data["id"] == user_id diff --git a/docs_src/sql_databases/tutorial001.py b/docs_src/sql_databases/tutorial001.py new file mode 100644 index 000000000..be86ec0ee --- /dev/null +++ b/docs_src/sql_databases/tutorial001.py @@ -0,0 +1,71 @@ +from typing import List, Union + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class Hero(SQLModel, table=True): + id: Union[int, None] = Field(default=None, primary_key=True) + name: str = Field(index=True) + age: Union[int, None] = Field(default=None, index=True) + secret_name: str + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/") +def create_hero(hero: Hero, session: Session = Depends(get_session)) -> Hero: + session.add(hero) + session.commit() + session.refresh(hero) + return hero + + +@app.get("/heroes/") +def read_heroes( + session: Session = Depends(get_session), + offset: int = 0, + limit: int = Query(default=100, le=100), +) -> List[Hero]: + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}") +def read_hero(hero_id: int, session: Session = Depends(get_session)) -> Hero: + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial001_an.py b/docs_src/sql_databases/tutorial001_an.py new file mode 100644 index 000000000..8c000d31c --- /dev/null +++ b/docs_src/sql_databases/tutorial001_an.py @@ -0,0 +1,74 @@ +from typing import List, Union + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select +from typing_extensions import Annotated + + +class Hero(SQLModel, table=True): + id: Union[int, None] = Field(default=None, primary_key=True) + name: str = Field(index=True) + age: Union[int, None] = Field(default=None, index=True) + secret_name: str + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +SessionDep = Annotated[Session, Depends(get_session)] + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/") +def create_hero(hero: Hero, session: SessionDep) -> Hero: + session.add(hero) + session.commit() + session.refresh(hero) + return hero + + +@app.get("/heroes/") +def read_heroes( + session: SessionDep, + offset: int = 0, + limit: Annotated[int, Query(le=100)] = 100, +) -> List[Hero]: + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}") +def read_hero(hero_id: int, session: SessionDep) -> Hero: + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial001_an_py310.py b/docs_src/sql_databases/tutorial001_an_py310.py new file mode 100644 index 000000000..de1fb81fa --- /dev/null +++ b/docs_src/sql_databases/tutorial001_an_py310.py @@ -0,0 +1,73 @@ +from typing import Annotated + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class Hero(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + name: str = Field(index=True) + age: int | None = Field(default=None, index=True) + secret_name: str + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +SessionDep = Annotated[Session, Depends(get_session)] + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/") +def create_hero(hero: Hero, session: SessionDep) -> Hero: + session.add(hero) + session.commit() + session.refresh(hero) + return hero + + +@app.get("/heroes/") +def read_heroes( + session: SessionDep, + offset: int = 0, + limit: Annotated[int, Query(le=100)] = 100, +) -> list[Hero]: + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}") +def read_hero(hero_id: int, session: SessionDep) -> Hero: + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial001_an_py39.py b/docs_src/sql_databases/tutorial001_an_py39.py new file mode 100644 index 000000000..595892746 --- /dev/null +++ b/docs_src/sql_databases/tutorial001_an_py39.py @@ -0,0 +1,73 @@ +from typing import Annotated, Union + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class Hero(SQLModel, table=True): + id: Union[int, None] = Field(default=None, primary_key=True) + name: str = Field(index=True) + age: Union[int, None] = Field(default=None, index=True) + secret_name: str + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +SessionDep = Annotated[Session, Depends(get_session)] + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/") +def create_hero(hero: Hero, session: SessionDep) -> Hero: + session.add(hero) + session.commit() + session.refresh(hero) + return hero + + +@app.get("/heroes/") +def read_heroes( + session: SessionDep, + offset: int = 0, + limit: Annotated[int, Query(le=100)] = 100, +) -> list[Hero]: + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}") +def read_hero(hero_id: int, session: SessionDep) -> Hero: + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial001_py310.py b/docs_src/sql_databases/tutorial001_py310.py new file mode 100644 index 000000000..b58462e6a --- /dev/null +++ b/docs_src/sql_databases/tutorial001_py310.py @@ -0,0 +1,69 @@ +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class Hero(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + name: str = Field(index=True) + age: int | None = Field(default=None, index=True) + secret_name: str + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/") +def create_hero(hero: Hero, session: Session = Depends(get_session)) -> Hero: + session.add(hero) + session.commit() + session.refresh(hero) + return hero + + +@app.get("/heroes/") +def read_heroes( + session: Session = Depends(get_session), + offset: int = 0, + limit: int = Query(default=100, le=100), +) -> list[Hero]: + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}") +def read_hero(hero_id: int, session: Session = Depends(get_session)) -> Hero: + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial001_py39.py b/docs_src/sql_databases/tutorial001_py39.py new file mode 100644 index 000000000..410a52d0c --- /dev/null +++ b/docs_src/sql_databases/tutorial001_py39.py @@ -0,0 +1,71 @@ +from typing import Union + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class Hero(SQLModel, table=True): + id: Union[int, None] = Field(default=None, primary_key=True) + name: str = Field(index=True) + age: Union[int, None] = Field(default=None, index=True) + secret_name: str + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/") +def create_hero(hero: Hero, session: Session = Depends(get_session)) -> Hero: + session.add(hero) + session.commit() + session.refresh(hero) + return hero + + +@app.get("/heroes/") +def read_heroes( + session: Session = Depends(get_session), + offset: int = 0, + limit: int = Query(default=100, le=100), +) -> list[Hero]: + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}") +def read_hero(hero_id: int, session: Session = Depends(get_session)) -> Hero: + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial002.py b/docs_src/sql_databases/tutorial002.py new file mode 100644 index 000000000..4350d19c6 --- /dev/null +++ b/docs_src/sql_databases/tutorial002.py @@ -0,0 +1,104 @@ +from typing import List, Union + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class HeroBase(SQLModel): + name: str = Field(index=True) + age: Union[int, None] = Field(default=None, index=True) + + +class Hero(HeroBase, table=True): + id: Union[int, None] = Field(default=None, primary_key=True) + secret_name: str + + +class HeroPublic(HeroBase): + id: int + + +class HeroCreate(HeroBase): + secret_name: str + + +class HeroUpdate(HeroBase): + name: Union[str, None] = None + age: Union[int, None] = None + secret_name: Union[str, None] = None + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/", response_model=HeroPublic) +def create_hero(hero: HeroCreate, session: Session = Depends(get_session)): + db_hero = Hero.model_validate(hero) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + + +@app.get("/heroes/", response_model=List[HeroPublic]) +def read_heroes( + session: Session = Depends(get_session), + offset: int = 0, + limit: int = Query(default=100, le=100), +): + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}", response_model=HeroPublic) +def read_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) +def update_hero( + hero_id: int, hero: HeroUpdate, session: Session = Depends(get_session) +): + hero_db = session.get(Hero, hero_id) + if not hero_db: + raise HTTPException(status_code=404, detail="Hero not found") + hero_data = hero.model_dump(exclude_unset=True) + hero_db.sqlmodel_update(hero_data) + session.add(hero_db) + session.commit() + session.refresh(hero_db) + return hero_db + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial002_an.py b/docs_src/sql_databases/tutorial002_an.py new file mode 100644 index 000000000..15e3d7c3a --- /dev/null +++ b/docs_src/sql_databases/tutorial002_an.py @@ -0,0 +1,104 @@ +from typing import List, Union + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select +from typing_extensions import Annotated + + +class HeroBase(SQLModel): + name: str = Field(index=True) + age: Union[int, None] = Field(default=None, index=True) + + +class Hero(HeroBase, table=True): + id: Union[int, None] = Field(default=None, primary_key=True) + secret_name: str + + +class HeroPublic(HeroBase): + id: int + + +class HeroCreate(HeroBase): + secret_name: str + + +class HeroUpdate(HeroBase): + name: Union[str, None] = None + age: Union[int, None] = None + secret_name: Union[str, None] = None + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +SessionDep = Annotated[Session, Depends(get_session)] +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/", response_model=HeroPublic) +def create_hero(hero: HeroCreate, session: SessionDep): + db_hero = Hero.model_validate(hero) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + + +@app.get("/heroes/", response_model=List[HeroPublic]) +def read_heroes( + session: SessionDep, + offset: int = 0, + limit: Annotated[int, Query(le=100)] = 100, +): + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}", response_model=HeroPublic) +def read_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) +def update_hero(hero_id: int, hero: HeroUpdate, session: SessionDep): + hero_db = session.get(Hero, hero_id) + if not hero_db: + raise HTTPException(status_code=404, detail="Hero not found") + hero_data = hero.model_dump(exclude_unset=True) + hero_db.sqlmodel_update(hero_data) + session.add(hero_db) + session.commit() + session.refresh(hero_db) + return hero_db + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial002_an_py310.py b/docs_src/sql_databases/tutorial002_an_py310.py new file mode 100644 index 000000000..64c554b8a --- /dev/null +++ b/docs_src/sql_databases/tutorial002_an_py310.py @@ -0,0 +1,103 @@ +from typing import Annotated + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class HeroBase(SQLModel): + name: str = Field(index=True) + age: int | None = Field(default=None, index=True) + + +class Hero(HeroBase, table=True): + id: int | None = Field(default=None, primary_key=True) + secret_name: str + + +class HeroPublic(HeroBase): + id: int + + +class HeroCreate(HeroBase): + secret_name: str + + +class HeroUpdate(HeroBase): + name: str | None = None + age: int | None = None + secret_name: str | None = None + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +SessionDep = Annotated[Session, Depends(get_session)] +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/", response_model=HeroPublic) +def create_hero(hero: HeroCreate, session: SessionDep): + db_hero = Hero.model_validate(hero) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + + +@app.get("/heroes/", response_model=list[HeroPublic]) +def read_heroes( + session: SessionDep, + offset: int = 0, + limit: Annotated[int, Query(le=100)] = 100, +): + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}", response_model=HeroPublic) +def read_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) +def update_hero(hero_id: int, hero: HeroUpdate, session: SessionDep): + hero_db = session.get(Hero, hero_id) + if not hero_db: + raise HTTPException(status_code=404, detail="Hero not found") + hero_data = hero.model_dump(exclude_unset=True) + hero_db.sqlmodel_update(hero_data) + session.add(hero_db) + session.commit() + session.refresh(hero_db) + return hero_db + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial002_an_py39.py b/docs_src/sql_databases/tutorial002_an_py39.py new file mode 100644 index 000000000..a8a0721ff --- /dev/null +++ b/docs_src/sql_databases/tutorial002_an_py39.py @@ -0,0 +1,103 @@ +from typing import Annotated, Union + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class HeroBase(SQLModel): + name: str = Field(index=True) + age: Union[int, None] = Field(default=None, index=True) + + +class Hero(HeroBase, table=True): + id: Union[int, None] = Field(default=None, primary_key=True) + secret_name: str + + +class HeroPublic(HeroBase): + id: int + + +class HeroCreate(HeroBase): + secret_name: str + + +class HeroUpdate(HeroBase): + name: Union[str, None] = None + age: Union[int, None] = None + secret_name: Union[str, None] = None + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +SessionDep = Annotated[Session, Depends(get_session)] +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/", response_model=HeroPublic) +def create_hero(hero: HeroCreate, session: SessionDep): + db_hero = Hero.model_validate(hero) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + + +@app.get("/heroes/", response_model=list[HeroPublic]) +def read_heroes( + session: SessionDep, + offset: int = 0, + limit: Annotated[int, Query(le=100)] = 100, +): + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}", response_model=HeroPublic) +def read_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) +def update_hero(hero_id: int, hero: HeroUpdate, session: SessionDep): + hero_db = session.get(Hero, hero_id) + if not hero_db: + raise HTTPException(status_code=404, detail="Hero not found") + hero_data = hero.model_dump(exclude_unset=True) + hero_db.sqlmodel_update(hero_data) + session.add(hero_db) + session.commit() + session.refresh(hero_db) + return hero_db + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial002_py310.py b/docs_src/sql_databases/tutorial002_py310.py new file mode 100644 index 000000000..ec3d68db5 --- /dev/null +++ b/docs_src/sql_databases/tutorial002_py310.py @@ -0,0 +1,102 @@ +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class HeroBase(SQLModel): + name: str = Field(index=True) + age: int | None = Field(default=None, index=True) + + +class Hero(HeroBase, table=True): + id: int | None = Field(default=None, primary_key=True) + secret_name: str + + +class HeroPublic(HeroBase): + id: int + + +class HeroCreate(HeroBase): + secret_name: str + + +class HeroUpdate(HeroBase): + name: str | None = None + age: int | None = None + secret_name: str | None = None + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/", response_model=HeroPublic) +def create_hero(hero: HeroCreate, session: Session = Depends(get_session)): + db_hero = Hero.model_validate(hero) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + + +@app.get("/heroes/", response_model=list[HeroPublic]) +def read_heroes( + session: Session = Depends(get_session), + offset: int = 0, + limit: int = Query(default=100, le=100), +): + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}", response_model=HeroPublic) +def read_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) +def update_hero( + hero_id: int, hero: HeroUpdate, session: Session = Depends(get_session) +): + hero_db = session.get(Hero, hero_id) + if not hero_db: + raise HTTPException(status_code=404, detail="Hero not found") + hero_data = hero.model_dump(exclude_unset=True) + hero_db.sqlmodel_update(hero_data) + session.add(hero_db) + session.commit() + session.refresh(hero_db) + return hero_db + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/docs_src/sql_databases/tutorial002_py39.py b/docs_src/sql_databases/tutorial002_py39.py new file mode 100644 index 000000000..d8f5dd090 --- /dev/null +++ b/docs_src/sql_databases/tutorial002_py39.py @@ -0,0 +1,104 @@ +from typing import Union + +from fastapi import Depends, FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class HeroBase(SQLModel): + name: str = Field(index=True) + age: Union[int, None] = Field(default=None, index=True) + + +class Hero(HeroBase, table=True): + id: Union[int, None] = Field(default=None, primary_key=True) + secret_name: str + + +class HeroPublic(HeroBase): + id: int + + +class HeroCreate(HeroBase): + secret_name: str + + +class HeroUpdate(HeroBase): + name: Union[str, None] = None + age: Union[int, None] = None + secret_name: Union[str, None] = None + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def get_session(): + with Session(engine) as session: + yield session + + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/", response_model=HeroPublic) +def create_hero(hero: HeroCreate, session: Session = Depends(get_session)): + db_hero = Hero.model_validate(hero) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + + +@app.get("/heroes/", response_model=list[HeroPublic]) +def read_heroes( + session: Session = Depends(get_session), + offset: int = 0, + limit: int = Query(default=100, le=100), +): + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes + + +@app.get("/heroes/{hero_id}", response_model=HeroPublic) +def read_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + return hero + + +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) +def update_hero( + hero_id: int, hero: HeroUpdate, session: Session = Depends(get_session) +): + hero_db = session.get(Hero, hero_id) + if not hero_db: + raise HTTPException(status_code=404, detail="Hero not found") + hero_data = hero.model_dump(exclude_unset=True) + hero_db.sqlmodel_update(hero_data) + session.add(hero_db) + session.commit() + session.refresh(hero_db) + return hero_db + + +@app.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: Session = Depends(get_session)): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=404, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} diff --git a/requirements-docs.txt b/requirements-docs.txt index c05bd51e3..1639159af 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -16,4 +16,4 @@ griffe-typingdoc==0.2.7 # For griffe, it formats with black black==24.3.0 mkdocs-macros-plugin==1.0.5 -markdown-include-variants==0.0.1 +markdown-include-variants==0.0.3 diff --git a/requirements-tests.txt b/requirements-tests.txt index 7b1f7ea1a..189fcaf7e 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -4,10 +4,7 @@ pytest >=7.1.3,<8.0.0 coverage[toml] >= 6.5.0,< 8.0 mypy ==1.8.0 dirty-equals ==0.6.0 -# TODO: once removing databases from tutorial, upgrade SQLAlchemy -# probably when including SQLModel -sqlalchemy >=1.3.18,<2.0.33 -databases[sqlite] >=0.3.2,<0.7.0 +sqlmodel==0.0.22 flask >=1.1.2,<3.0.0 anyio[trio] >=3.2.1,<4.0.0 PyJWT==2.8.0 diff --git a/scripts/playwright/sql_databases/image01.py b/scripts/playwright/sql_databases/image01.py new file mode 100644 index 000000000..0dd6f2514 --- /dev/null +++ b/scripts/playwright/sql_databases/image01.py @@ -0,0 +1,37 @@ +import subprocess +import time + +import httpx +from playwright.sync_api import Playwright, sync_playwright + + +# Run playwright codegen to generate the code below, copy paste the sections in run() +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + # Update the viewport manually + context = browser.new_context(viewport={"width": 960, "height": 1080}) + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_label("post /heroes/").click() + # Manually add the screenshot + page.screenshot(path="docs/en/docs/img/tutorial/sql-databases/image01.png") + + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["fastapi", "run", "docs_src/sql_databases/tutorial001.py"], +) +try: + for _ in range(3): + try: + response = httpx.get("http://localhost:8000/docs") + except httpx.ConnectError: + time.sleep(1) + break + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/scripts/playwright/sql_databases/image02.py b/scripts/playwright/sql_databases/image02.py new file mode 100644 index 000000000..6c4f685e8 --- /dev/null +++ b/scripts/playwright/sql_databases/image02.py @@ -0,0 +1,37 @@ +import subprocess +import time + +import httpx +from playwright.sync_api import Playwright, sync_playwright + + +# Run playwright codegen to generate the code below, copy paste the sections in run() +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + # Update the viewport manually + context = browser.new_context(viewport={"width": 960, "height": 1080}) + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_label("post /heroes/").click() + # Manually add the screenshot + page.screenshot(path="docs/en/docs/img/tutorial/sql-databases/image02.png") + + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["fastapi", "run", "docs_src/sql_databases/tutorial002.py"], +) +try: + for _ in range(3): + try: + response = httpx.get("http://localhost:8000/docs") + except httpx.ConnectError: + time.sleep(1) + break + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/tests/test_tutorial/test_async_sql_databases/__init__.py b/tests/test_tutorial/test_async_sql_databases/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py b/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py deleted file mode 100644 index 13568a532..000000000 --- a/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py +++ /dev/null @@ -1,146 +0,0 @@ -import pytest -from fastapi import FastAPI -from fastapi.testclient import TestClient - -from ...utils import needs_pydanticv1 - - -@pytest.fixture(name="app", scope="module") -def get_app(): - with pytest.warns(DeprecationWarning): - from docs_src.async_sql_databases.tutorial001 import app - yield app - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_create_read(app: FastAPI): - with TestClient(app) as client: - note = {"text": "Foo bar", "completed": False} - response = client.post("/notes/", json=note) - assert response.status_code == 200, response.text - data = response.json() - assert data["text"] == note["text"] - assert data["completed"] == note["completed"] - assert "id" in data - response = client.get("/notes/") - assert response.status_code == 200, response.text - assert data in response.json() - - -def test_openapi_schema(app: FastAPI): - with TestClient(app) as client: - 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": { - "/notes/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Notes Notes Get", - "type": "array", - "items": { - "$ref": "#/components/schemas/Note" - }, - } - } - }, - } - }, - "summary": "Read Notes", - "operationId": "read_notes_notes__get", - }, - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Note"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Note", - "operationId": "create_note_notes__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/NoteIn"} - } - }, - "required": True, - }, - }, - } - }, - "components": { - "schemas": { - "NoteIn": { - "title": "NoteIn", - "required": ["text", "completed"], - "type": "object", - "properties": { - "text": {"title": "Text", "type": "string"}, - "completed": {"title": "Completed", "type": "boolean"}, - }, - }, - "Note": { - "title": "Note", - "required": ["id", "text", "completed"], - "type": "object", - "properties": { - "id": {"title": "Id", "type": "integer"}, - "text": {"title": "Text", "type": "string"}, - "completed": {"title": "Completed", "type": "boolean"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases.py b/tests/test_tutorial/test_sql_databases/test_sql_databases.py deleted file mode 100644 index e3e2b36a8..000000000 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases.py +++ /dev/null @@ -1,419 +0,0 @@ -import importlib -import os -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_pydanticv1 - - -@pytest.fixture(scope="module") -def client(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("data") - cwd = os.getcwd() - os.chdir(tmp_path) - test_db = Path("./sql_app.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app import main - - # Ensure import side effects are re-executed - importlib.reload(main) - with TestClient(main.app) as c: - yield c - if test_db.is_file(): # pragma: nocover - test_db.unlink() - os.chdir(cwd) - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_create_user(client): - test_user = {"email": "johndoe@example.com", "password": "secret"} - response = client.post("/users/", json=test_user) - assert response.status_code == 200, response.text - data = response.json() - assert test_user["email"] == data["email"] - assert "id" in data - response = client.post("/users/", json=test_user) - assert response.status_code == 400, response.text - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_user(client): - response = client.get("/users/1") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data - assert "id" in data - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_nonexistent_user(client): - response = client.get("/users/999") - assert response.status_code == 404, response.text - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_users(client): - response = client.get("/users/") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data[0] - assert "id" in data[0] - - -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_create_item(client): - item = {"title": "Foo", "description": "Something that fights"} - response = client.post("/users/1/items/", json=item) - assert response.status_code == 200, response.text - item_data = response.json() - assert item["title"] == item_data["title"] - assert item["description"] == item_data["description"] - assert "id" in item_data - assert "owner_id" in item_data - response = client.get("/users/1") - assert response.status_code == 200, response.text - user_data = response.json() - item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0] - assert item_to_check["title"] == item["title"] - assert item_to_check["description"] == item["description"] - - -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_read_items(client): - response = client.get("/items/") - assert response.status_code == 200, response.text - data = response.json() - assert data - first_item = data[0] - assert "title" in first_item - assert "description" in first_item - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Users Users Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - }, - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create User", - "operationId": "create_user_users__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserCreate"} - } - }, - "required": True, - }, - }, - }, - "/users/{user_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read User", - "operationId": "read_user_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - } - }, - "/users/{user_id}/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Item For User", - "operationId": "create_item_for_user_users__user_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ItemCreate"} - } - }, - "required": True, - }, - } - }, - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ItemCreate": { - "title": "ItemCreate", - "required": ["title"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"}, - ), - "id": {"title": "Id", "type": "integer"}, - "owner_id": {"title": "Owner Id", "type": "integer"}, - }, - }, - "User": { - "title": "User", - "required": ["email", "id", "is_active"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "is_active": {"title": "Is Active", "type": "boolean"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - "default": [], - }, - }, - }, - "UserCreate": { - "title": "UserCreate", - "required": ["email", "password"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index 73b97e09d..000000000 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py +++ /dev/null @@ -1,421 +0,0 @@ -import importlib -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_pydanticv1 - - -@pytest.fixture(scope="module") -def client(): - test_db = Path("./sql_app.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app import alt_main - - # Ensure import side effects are re-executed - importlib.reload(alt_main) - - with TestClient(alt_main.app) as c: - yield c - if test_db.is_file(): # pragma: nocover - test_db.unlink() - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_create_user(client): - test_user = {"email": "johndoe@example.com", "password": "secret"} - response = client.post("/users/", json=test_user) - assert response.status_code == 200, response.text - data = response.json() - assert test_user["email"] == data["email"] - assert "id" in data - response = client.post("/users/", json=test_user) - assert response.status_code == 400, response.text - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_user(client): - response = client.get("/users/1") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data - assert "id" in data - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_nonexistent_user(client): - response = client.get("/users/999") - assert response.status_code == 404, response.text - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_users(client): - response = client.get("/users/") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data[0] - assert "id" in data[0] - - -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_create_item(client): - item = {"title": "Foo", "description": "Something that fights"} - response = client.post("/users/1/items/", json=item) - assert response.status_code == 200, response.text - item_data = response.json() - assert item["title"] == item_data["title"] - assert item["description"] == item_data["description"] - assert "id" in item_data - assert "owner_id" in item_data - response = client.get("/users/1") - assert response.status_code == 200, response.text - user_data = response.json() - item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0] - assert item_to_check["title"] == item["title"] - assert item_to_check["description"] == item["description"] - response = client.get("/users/1") - assert response.status_code == 200, response.text - user_data = response.json() - item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0] - assert item_to_check["title"] == item["title"] - assert item_to_check["description"] == item["description"] - - -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_read_items(client): - response = client.get("/items/") - assert response.status_code == 200, response.text - data = response.json() - assert data - first_item = data[0] - assert "title" in first_item - assert "description" in first_item - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Users Users Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - }, - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create User", - "operationId": "create_user_users__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserCreate"} - } - }, - "required": True, - }, - }, - }, - "/users/{user_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read User", - "operationId": "read_user_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - } - }, - "/users/{user_id}/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Item For User", - "operationId": "create_item_for_user_users__user_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ItemCreate"} - } - }, - "required": True, - }, - } - }, - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ItemCreate": { - "title": "ItemCreate", - "required": ["title"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"}, - ), - "id": {"title": "Id", "type": "integer"}, - "owner_id": {"title": "Owner Id", "type": "integer"}, - }, - }, - "User": { - "title": "User", - "required": ["email", "id", "is_active"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "is_active": {"title": "Is Active", "type": "boolean"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - "default": [], - }, - }, - }, - "UserCreate": { - "title": "UserCreate", - "required": ["email", "password"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index a078f012a..000000000 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py +++ /dev/null @@ -1,433 +0,0 @@ -import importlib -import os -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310, needs_pydanticv1 - - -@pytest.fixture(scope="module") -def client(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("data") - cwd = os.getcwd() - os.chdir(tmp_path) - test_db = Path("./sql_app.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app_py310 import alt_main - - # Ensure import side effects are re-executed - importlib.reload(alt_main) - - with TestClient(alt_main.app) as c: - yield c - if test_db.is_file(): # pragma: nocover - test_db.unlink() - os.chdir(cwd) - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_create_user(client): - test_user = {"email": "johndoe@example.com", "password": "secret"} - response = client.post("/users/", json=test_user) - assert response.status_code == 200, response.text - data = response.json() - assert test_user["email"] == data["email"] - assert "id" in data - response = client.post("/users/", json=test_user) - assert response.status_code == 400, response.text - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_user(client): - response = client.get("/users/1") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data - assert "id" in data - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_nonexistent_user(client): - response = client.get("/users/999") - assert response.status_code == 404, response.text - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_users(client): - response = client.get("/users/") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data[0] - assert "id" in data[0] - - -@needs_py310 -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_create_item(client): - item = {"title": "Foo", "description": "Something that fights"} - response = client.post("/users/1/items/", json=item) - assert response.status_code == 200, response.text - item_data = response.json() - assert item["title"] == item_data["title"] - assert item["description"] == item_data["description"] - assert "id" in item_data - assert "owner_id" in item_data - response = client.get("/users/1") - assert response.status_code == 200, response.text - user_data = response.json() - item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0] - assert item_to_check["title"] == item["title"] - assert item_to_check["description"] == item["description"] - response = client.get("/users/1") - assert response.status_code == 200, response.text - user_data = response.json() - item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0] - assert item_to_check["title"] == item["title"] - assert item_to_check["description"] == item["description"] - - -@needs_py310 -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_read_items(client): - response = client.get("/items/") - assert response.status_code == 200, response.text - data = response.json() - assert data - first_item = data[0] - assert "title" in first_item - assert "description" in first_item - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Users Users Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - }, - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create User", - "operationId": "create_user_users__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserCreate"} - } - }, - "required": True, - }, - }, - }, - "/users/{user_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read User", - "operationId": "read_user_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - } - }, - "/users/{user_id}/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Item For User", - "operationId": "create_item_for_user_users__user_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ItemCreate"} - } - }, - "required": True, - }, - } - }, - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ItemCreate": { - "title": "ItemCreate", - "required": ["title"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"}, - ), - "id": {"title": "Id", "type": "integer"}, - "owner_id": {"title": "Owner Id", "type": "integer"}, - }, - }, - "User": { - "title": "User", - "required": ["email", "id", "is_active"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "is_active": {"title": "Is Active", "type": "boolean"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - "default": [], - }, - }, - }, - "UserCreate": { - "title": "UserCreate", - "required": ["email", "password"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index a5da07ac6..000000000 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py +++ /dev/null @@ -1,433 +0,0 @@ -import importlib -import os -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39, needs_pydanticv1 - - -@pytest.fixture(scope="module") -def client(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("data") - cwd = os.getcwd() - os.chdir(tmp_path) - test_db = Path("./sql_app.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app_py39 import alt_main - - # Ensure import side effects are re-executed - importlib.reload(alt_main) - - with TestClient(alt_main.app) as c: - yield c - if test_db.is_file(): # pragma: nocover - test_db.unlink() - os.chdir(cwd) - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_create_user(client): - test_user = {"email": "johndoe@example.com", "password": "secret"} - response = client.post("/users/", json=test_user) - assert response.status_code == 200, response.text - data = response.json() - assert test_user["email"] == data["email"] - assert "id" in data - response = client.post("/users/", json=test_user) - assert response.status_code == 400, response.text - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_user(client): - response = client.get("/users/1") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data - assert "id" in data - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_nonexistent_user(client): - response = client.get("/users/999") - assert response.status_code == 404, response.text - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_users(client): - response = client.get("/users/") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data[0] - assert "id" in data[0] - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_create_item(client): - item = {"title": "Foo", "description": "Something that fights"} - response = client.post("/users/1/items/", json=item) - assert response.status_code == 200, response.text - item_data = response.json() - assert item["title"] == item_data["title"] - assert item["description"] == item_data["description"] - assert "id" in item_data - assert "owner_id" in item_data - response = client.get("/users/1") - assert response.status_code == 200, response.text - user_data = response.json() - item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0] - assert item_to_check["title"] == item["title"] - assert item_to_check["description"] == item["description"] - response = client.get("/users/1") - assert response.status_code == 200, response.text - user_data = response.json() - item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0] - assert item_to_check["title"] == item["title"] - assert item_to_check["description"] == item["description"] - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_read_items(client): - response = client.get("/items/") - assert response.status_code == 200, response.text - data = response.json() - assert data - first_item = data[0] - assert "title" in first_item - assert "description" in first_item - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Users Users Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - }, - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create User", - "operationId": "create_user_users__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserCreate"} - } - }, - "required": True, - }, - }, - }, - "/users/{user_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read User", - "operationId": "read_user_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - } - }, - "/users/{user_id}/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Item For User", - "operationId": "create_item_for_user_users__user_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ItemCreate"} - } - }, - "required": True, - }, - } - }, - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ItemCreate": { - "title": "ItemCreate", - "required": ["title"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"}, - ), - "id": {"title": "Id", "type": "integer"}, - "owner_id": {"title": "Owner Id", "type": "integer"}, - }, - }, - "User": { - "title": "User", - "required": ["email", "id", "is_active"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "is_active": {"title": "Is Active", "type": "boolean"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - "default": [], - }, - }, - }, - "UserCreate": { - "title": "UserCreate", - "required": ["email", "password"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index 5a9106598..000000000 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py +++ /dev/null @@ -1,432 +0,0 @@ -import importlib -import os -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py310, needs_pydanticv1 - - -@pytest.fixture(scope="module", name="client") -def get_client(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("data") - cwd = os.getcwd() - os.chdir(tmp_path) - test_db = Path("./sql_app.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app_py310 import main - - # Ensure import side effects are re-executed - importlib.reload(main) - with TestClient(main.app) as c: - yield c - if test_db.is_file(): # pragma: nocover - test_db.unlink() - os.chdir(cwd) - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_create_user(client): - test_user = {"email": "johndoe@example.com", "password": "secret"} - response = client.post("/users/", json=test_user) - assert response.status_code == 200, response.text - data = response.json() - assert test_user["email"] == data["email"] - assert "id" in data - response = client.post("/users/", json=test_user) - assert response.status_code == 400, response.text - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_user(client): - response = client.get("/users/1") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data - assert "id" in data - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_nonexistent_user(client): - response = client.get("/users/999") - assert response.status_code == 404, response.text - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_users(client): - response = client.get("/users/") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data[0] - assert "id" in data[0] - - -@needs_py310 -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_create_item(client): - item = {"title": "Foo", "description": "Something that fights"} - response = client.post("/users/1/items/", json=item) - assert response.status_code == 200, response.text - item_data = response.json() - assert item["title"] == item_data["title"] - assert item["description"] == item_data["description"] - assert "id" in item_data - assert "owner_id" in item_data - response = client.get("/users/1") - assert response.status_code == 200, response.text - user_data = response.json() - item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0] - assert item_to_check["title"] == item["title"] - assert item_to_check["description"] == item["description"] - response = client.get("/users/1") - assert response.status_code == 200, response.text - user_data = response.json() - item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0] - assert item_to_check["title"] == item["title"] - assert item_to_check["description"] == item["description"] - - -@needs_py310 -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_read_items(client): - response = client.get("/items/") - assert response.status_code == 200, response.text - data = response.json() - assert data - first_item = data[0] - assert "title" in first_item - assert "description" in first_item - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Users Users Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - }, - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create User", - "operationId": "create_user_users__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserCreate"} - } - }, - "required": True, - }, - }, - }, - "/users/{user_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read User", - "operationId": "read_user_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - } - }, - "/users/{user_id}/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Item For User", - "operationId": "create_item_for_user_users__user_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ItemCreate"} - } - }, - "required": True, - }, - } - }, - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ItemCreate": { - "title": "ItemCreate", - "required": ["title"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"}, - ), - "id": {"title": "Id", "type": "integer"}, - "owner_id": {"title": "Owner Id", "type": "integer"}, - }, - }, - "User": { - "title": "User", - "required": ["email", "id", "is_active"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "is_active": {"title": "Is Active", "type": "boolean"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - "default": [], - }, - }, - }, - "UserCreate": { - "title": "UserCreate", - "required": ["email", "password"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } 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 deleted file mode 100644 index a354ba905..000000000 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py +++ /dev/null @@ -1,432 +0,0 @@ -import importlib -import os -from pathlib import Path - -import pytest -from dirty_equals import IsDict -from fastapi.testclient import TestClient - -from ...utils import needs_py39, needs_pydanticv1 - - -@pytest.fixture(scope="module", name="client") -def get_client(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("data") - cwd = os.getcwd() - os.chdir(tmp_path) - test_db = Path("./sql_app.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app_py39 import main - - # Ensure import side effects are re-executed - importlib.reload(main) - with TestClient(main.app) as c: - yield c - if test_db.is_file(): # pragma: nocover - test_db.unlink() - os.chdir(cwd) - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_create_user(client): - test_user = {"email": "johndoe@example.com", "password": "secret"} - response = client.post("/users/", json=test_user) - assert response.status_code == 200, response.text - data = response.json() - assert test_user["email"] == data["email"] - assert "id" in data - response = client.post("/users/", json=test_user) - assert response.status_code == 400, response.text - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_user(client): - response = client.get("/users/1") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data - assert "id" in data - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_nonexistent_user(client): - response = client.get("/users/999") - assert response.status_code == 404, response.text - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_get_users(client): - response = client.get("/users/") - assert response.status_code == 200, response.text - data = response.json() - assert "email" in data[0] - assert "id" in data[0] - - -@needs_py39 -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_create_item(client): - item = {"title": "Foo", "description": "Something that fights"} - response = client.post("/users/1/items/", json=item) - assert response.status_code == 200, response.text - item_data = response.json() - assert item["title"] == item_data["title"] - assert item["description"] == item_data["description"] - assert "id" in item_data - assert "owner_id" in item_data - response = client.get("/users/1") - assert response.status_code == 200, response.text - user_data = response.json() - item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0] - assert item_to_check["title"] == item["title"] - assert item_to_check["description"] == item["description"] - response = client.get("/users/1") - assert response.status_code == 200, response.text - user_data = response.json() - item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0] - assert item_to_check["title"] == item["title"] - assert item_to_check["description"] == item["description"] - - -@needs_py39 -# TODO: pv2 add Pydantic v2 version -@needs_pydanticv1 -def test_read_items(client): - response = client.get("/items/") - assert response.status_code == 200, response.text - data = response.json() - assert data - first_item = data[0] - assert "title" in first_item - assert "description" in first_item - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Users Users Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - }, - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create User", - "operationId": "create_user_users__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserCreate"} - } - }, - "required": True, - }, - }, - }, - "/users/{user_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read User", - "operationId": "read_user_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - } - }, - "/users/{user_id}/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create Item For User", - "operationId": "create_item_for_user_users__user_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ItemCreate"} - } - }, - "required": True, - }, - } - }, - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ItemCreate": { - "title": "ItemCreate", - "required": ["title"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"} - ), - }, - }, - "Item": { - "title": "Item", - "required": ["title", "id", "owner_id"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | IsDict( - # TODO: remove when deprecating Pydantic v1 - {"title": "Description", "type": "string"}, - ), - "id": {"title": "Id", "type": "integer"}, - "owner_id": {"title": "Owner Id", "type": "integer"}, - }, - }, - "User": { - "title": "User", - "required": ["email", "id", "is_active"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "is_active": {"title": "Is Active", "type": "boolean"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - "default": [], - }, - }, - }, - "UserCreate": { - "title": "UserCreate", - "required": ["email", "password"], - "type": "object", - "properties": { - "email": {"title": "Email", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_sql_databases/test_testing_databases.py b/tests/test_tutorial/test_sql_databases/test_testing_databases.py deleted file mode 100644 index ce6ce230c..000000000 --- a/tests/test_tutorial/test_sql_databases/test_testing_databases.py +++ /dev/null @@ -1,27 +0,0 @@ -import importlib -import os -from pathlib import Path - -import pytest - -from ...utils import needs_pydanticv1 - - -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_testing_dbs(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("data") - cwd = os.getcwd() - os.chdir(tmp_path) - test_db = Path("./test.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app.tests import test_sql_app - - # Ensure import side effects are re-executed - importlib.reload(test_sql_app) - test_sql_app.test_create_user() - if test_db.is_file(): # pragma: nocover - test_db.unlink() - os.chdir(cwd) diff --git a/tests/test_tutorial/test_sql_databases/test_testing_databases_py310.py b/tests/test_tutorial/test_sql_databases/test_testing_databases_py310.py deleted file mode 100644 index 545d63c2a..000000000 --- a/tests/test_tutorial/test_sql_databases/test_testing_databases_py310.py +++ /dev/null @@ -1,28 +0,0 @@ -import importlib -import os -from pathlib import Path - -import pytest - -from ...utils import needs_py310, needs_pydanticv1 - - -@needs_py310 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_testing_dbs_py39(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("data") - cwd = os.getcwd() - os.chdir(tmp_path) - test_db = Path("./test.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app_py310.tests import test_sql_app - - # Ensure import side effects are re-executed - importlib.reload(test_sql_app) - test_sql_app.test_create_user() - if test_db.is_file(): # pragma: nocover - test_db.unlink() - os.chdir(cwd) diff --git a/tests/test_tutorial/test_sql_databases/test_testing_databases_py39.py b/tests/test_tutorial/test_sql_databases/test_testing_databases_py39.py deleted file mode 100644 index 99bfd3fa8..000000000 --- a/tests/test_tutorial/test_sql_databases/test_testing_databases_py39.py +++ /dev/null @@ -1,28 +0,0 @@ -import importlib -import os -from pathlib import Path - -import pytest - -from ...utils import needs_py39, needs_pydanticv1 - - -@needs_py39 -# TODO: pv2 add version with Pydantic v2 -@needs_pydanticv1 -def test_testing_dbs_py39(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("data") - cwd = os.getcwd() - os.chdir(tmp_path) - test_db = Path("./test.db") - if test_db.is_file(): # pragma: nocover - test_db.unlink() - # Import while creating the client to create the DB after starting the test session - from docs_src.sql_databases.sql_app_py39.tests import test_sql_app - - # Ensure import side effects are re-executed - importlib.reload(test_sql_app) - test_sql_app.test_create_user() - if test_db.is_file(): # pragma: nocover - test_db.unlink() - os.chdir(cwd) diff --git a/tests/test_tutorial/test_sql_databases/test_tutorial001.py b/tests/test_tutorial/test_sql_databases/test_tutorial001.py new file mode 100644 index 000000000..cc7e590df --- /dev/null +++ b/tests/test_tutorial/test_sql_databases/test_tutorial001.py @@ -0,0 +1,373 @@ +import importlib +import warnings + +import pytest +from dirty_equals import IsDict, IsInt +from fastapi.testclient import TestClient +from inline_snapshot import snapshot +from sqlalchemy import StaticPool +from sqlmodel import SQLModel, create_engine +from sqlmodel.main import default_registry + +from tests.utils import needs_py39, needs_py310 + + +def clear_sqlmodel(): + # Clear the tables in the metadata for the default base model + SQLModel.metadata.clear() + # Clear the Models associated with the registry, to avoid warnings + default_registry.dispose() + + +@pytest.fixture( + name="client", + params=[ + "tutorial001", + pytest.param("tutorial001_py39", marks=needs_py39), + pytest.param("tutorial001_py310", marks=needs_py310), + "tutorial001_an", + pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + clear_sqlmodel() + # TODO: remove when updating SQL tutorial to use new lifespan API + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + mod = importlib.import_module(f"docs_src.sql_databases.{request.param}") + clear_sqlmodel() + importlib.reload(mod) + mod.sqlite_url = "sqlite://" + mod.engine = create_engine( + mod.sqlite_url, connect_args={"check_same_thread": False}, poolclass=StaticPool + ) + + with TestClient(mod.app) as c: + yield c + + +def test_crud_app(client: TestClient): + # TODO: this warns that SQLModel.from_orm is deprecated in Pydantic v1, refactor + # this if using obj.model_validate becomes independent of Pydantic v2 + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + # No heroes before creating + response = client.get("heroes/") + assert response.status_code == 200, response.text + assert response.json() == [] + + # Create a hero + response = client.post( + "/heroes/", + json={ + "id": 999, + "name": "Dead Pond", + "age": 30, + "secret_name": "Dive Wilson", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == snapshot( + {"age": 30, "secret_name": "Dive Wilson", "id": 999, "name": "Dead Pond"} + ) + + # Read a hero + hero_id = response.json()["id"] + response = client.get(f"/heroes/{hero_id}") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + {"name": "Dead Pond", "age": 30, "id": 999, "secret_name": "Dive Wilson"} + ) + + # Read all heroes + # Create more heroes first + response = client.post( + "/heroes/", + json={"name": "Spider-Boy", "age": 18, "secret_name": "Pedro Parqueador"}, + ) + assert response.status_code == 200, response.text + response = client.post( + "/heroes/", json={"name": "Rusty-Man", "secret_name": "Tommy Sharp"} + ) + assert response.status_code == 200, response.text + + response = client.get("/heroes/") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + [ + { + "name": "Dead Pond", + "age": 30, + "id": IsInt(), + "secret_name": "Dive Wilson", + }, + { + "name": "Spider-Boy", + "age": 18, + "id": IsInt(), + "secret_name": "Pedro Parqueador", + }, + { + "name": "Rusty-Man", + "age": None, + "id": IsInt(), + "secret_name": "Tommy Sharp", + }, + ] + ) + + response = client.get("/heroes/?offset=1&limit=1") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + [ + { + "name": "Spider-Boy", + "age": 18, + "id": IsInt(), + "secret_name": "Pedro Parqueador", + } + ] + ) + + # Delete a hero + response = client.delete(f"/heroes/{hero_id}") + assert response.status_code == 200, response.text + assert response.json() == snapshot({"ok": True}) + + response = client.get(f"/heroes/{hero_id}") + assert response.status_code == 404, response.text + + response = client.delete(f"/heroes/{hero_id}") + assert response.status_code == 404, response.text + assert response.json() == snapshot({"detail": "Hero not found"}) + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/heroes/": { + "post": { + "summary": "Create Hero", + "operationId": "create_hero_heroes__post", + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Hero"} + } + }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Hero"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "get": { + "summary": "Read Heroes", + "operationId": "read_heroes_heroes__get", + "parameters": [ + { + "name": "offset", + "in": "query", + "required": False, + "schema": { + "type": "integer", + "default": 0, + "title": "Offset", + }, + }, + { + "name": "limit", + "in": "query", + "required": False, + "schema": { + "type": "integer", + "maximum": 100, + "default": 100, + "title": "Limit", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Hero" + }, + "title": "Response Read Heroes Heroes Get", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/heroes/{hero_id}": { + "get": { + "summary": "Read Hero", + "operationId": "read_hero_heroes__hero_id__get", + "parameters": [ + { + "name": "hero_id", + "in": "path", + "required": True, + "schema": {"type": "integer", "title": "Hero Id"}, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Hero"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "delete": { + "summary": "Delete Hero", + "operationId": "delete_hero_heroes__hero_id__delete", + "parameters": [ + { + "name": "hero_id", + "in": "path", + "required": True, + "schema": {"type": "integer", "title": "Hero Id"}, + } + ], + "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", + }, + "Hero": { + "properties": { + "id": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Id", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "integer", + "title": "Id", + } + ), + "name": {"type": "string", "title": "Name"}, + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "integer", + "title": "Age", + } + ), + "secret_name": {"type": "string", "title": "Secret Name"}, + }, + "type": "object", + "required": ["name", "secret_name"], + "title": "Hero", + }, + "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_sql_databases/test_tutorial002.py b/tests/test_tutorial/test_sql_databases/test_tutorial002.py new file mode 100644 index 000000000..68c1966f5 --- /dev/null +++ b/tests/test_tutorial/test_sql_databases/test_tutorial002.py @@ -0,0 +1,481 @@ +import importlib +import warnings + +import pytest +from dirty_equals import IsDict, IsInt +from fastapi.testclient import TestClient +from inline_snapshot import snapshot +from sqlalchemy import StaticPool +from sqlmodel import SQLModel, create_engine +from sqlmodel.main import default_registry + +from tests.utils import needs_py39, needs_py310 + + +def clear_sqlmodel(): + # Clear the tables in the metadata for the default base model + SQLModel.metadata.clear() + # Clear the Models associated with the registry, to avoid warnings + default_registry.dispose() + + +@pytest.fixture( + name="client", + params=[ + "tutorial002", + pytest.param("tutorial002_py39", marks=needs_py39), + pytest.param("tutorial002_py310", marks=needs_py310), + "tutorial002_an", + pytest.param("tutorial002_an_py39", marks=needs_py39), + pytest.param("tutorial002_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + clear_sqlmodel() + # TODO: remove when updating SQL tutorial to use new lifespan API + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + mod = importlib.import_module(f"docs_src.sql_databases.{request.param}") + clear_sqlmodel() + importlib.reload(mod) + mod.sqlite_url = "sqlite://" + mod.engine = create_engine( + mod.sqlite_url, connect_args={"check_same_thread": False}, poolclass=StaticPool + ) + + with TestClient(mod.app) as c: + yield c + + +def test_crud_app(client: TestClient): + # TODO: this warns that SQLModel.from_orm is deprecated in Pydantic v1, refactor + # this if using obj.model_validate becomes independent of Pydantic v2 + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + # No heroes before creating + response = client.get("heroes/") + assert response.status_code == 200, response.text + assert response.json() == [] + + # Create a hero + response = client.post( + "/heroes/", + json={ + "id": 9000, + "name": "Dead Pond", + "age": 30, + "secret_name": "Dive Wilson", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == snapshot( + {"age": 30, "id": IsInt(), "name": "Dead Pond"} + ) + assert ( + response.json()["id"] != 9000 + ), "The ID should be generated by the database" + + # Read a hero + hero_id = response.json()["id"] + response = client.get(f"/heroes/{hero_id}") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + {"name": "Dead Pond", "age": 30, "id": IsInt()} + ) + + # Read all heroes + # Create more heroes first + response = client.post( + "/heroes/", + json={"name": "Spider-Boy", "age": 18, "secret_name": "Pedro Parqueador"}, + ) + assert response.status_code == 200, response.text + response = client.post( + "/heroes/", json={"name": "Rusty-Man", "secret_name": "Tommy Sharp"} + ) + assert response.status_code == 200, response.text + + response = client.get("/heroes/") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + [ + {"name": "Dead Pond", "age": 30, "id": IsInt()}, + {"name": "Spider-Boy", "age": 18, "id": IsInt()}, + {"name": "Rusty-Man", "age": None, "id": IsInt()}, + ] + ) + + response = client.get("/heroes/?offset=1&limit=1") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + [{"name": "Spider-Boy", "age": 18, "id": IsInt()}] + ) + + # Update a hero + response = client.patch( + f"/heroes/{hero_id}", json={"name": "Dog Pond", "age": None} + ) + assert response.status_code == 200, response.text + assert response.json() == snapshot( + {"name": "Dog Pond", "age": None, "id": hero_id} + ) + + # Get updated hero + response = client.get(f"/heroes/{hero_id}") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + {"name": "Dog Pond", "age": None, "id": hero_id} + ) + + # Delete a hero + response = client.delete(f"/heroes/{hero_id}") + assert response.status_code == 200, response.text + assert response.json() == snapshot({"ok": True}) + + # The hero is no longer found + response = client.get(f"/heroes/{hero_id}") + assert response.status_code == 404, response.text + + # Delete a hero that does not exist + response = client.delete(f"/heroes/{hero_id}") + assert response.status_code == 404, response.text + assert response.json() == snapshot({"detail": "Hero not found"}) + + # Update a hero that does not exist + response = client.patch(f"/heroes/{hero_id}", json={"name": "Dog Pond"}) + assert response.status_code == 404, response.text + assert response.json() == snapshot({"detail": "Hero not found"}) + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/heroes/": { + "post": { + "summary": "Create Hero", + "operationId": "create_hero_heroes__post", + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroCreate" + } + } + }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroPublic" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "get": { + "summary": "Read Heroes", + "operationId": "read_heroes_heroes__get", + "parameters": [ + { + "name": "offset", + "in": "query", + "required": False, + "schema": { + "type": "integer", + "default": 0, + "title": "Offset", + }, + }, + { + "name": "limit", + "in": "query", + "required": False, + "schema": { + "type": "integer", + "maximum": 100, + "default": 100, + "title": "Limit", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HeroPublic" + }, + "title": "Response Read Heroes Heroes Get", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/heroes/{hero_id}": { + "get": { + "summary": "Read Hero", + "operationId": "read_hero_heroes__hero_id__get", + "parameters": [ + { + "name": "hero_id", + "in": "path", + "required": True, + "schema": {"type": "integer", "title": "Hero Id"}, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroPublic" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "patch": { + "summary": "Update Hero", + "operationId": "update_hero_heroes__hero_id__patch", + "parameters": [ + { + "name": "hero_id", + "in": "path", + "required": True, + "schema": {"type": "integer", "title": "Hero Id"}, + } + ], + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroUpdate" + } + } + }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroPublic" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "delete": { + "summary": "Delete Hero", + "operationId": "delete_hero_heroes__hero_id__delete", + "parameters": [ + { + "name": "hero_id", + "in": "path", + "required": True, + "schema": {"type": "integer", "title": "Hero Id"}, + } + ], + "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", + }, + "HeroCreate": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "integer", + "title": "Age", + } + ), + "secret_name": {"type": "string", "title": "Secret Name"}, + }, + "type": "object", + "required": ["name", "secret_name"], + "title": "HeroCreate", + }, + "HeroPublic": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "integer", + "title": "Age", + } + ), + "id": {"type": "integer", "title": "Id"}, + }, + "type": "object", + "required": ["name", "id"], + "title": "HeroPublic", + }, + "HeroUpdate": { + "properties": { + "name": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Name", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Name", + } + ), + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "integer", + "title": "Age", + } + ), + "secret_name": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Secret Name", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "type": "string", + "title": "Secret Name", + } + ), + }, + "type": "object", + "title": "HeroUpdate", + }, + "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", + }, + } + }, + } + )

$5q*9#iM5)%>&v0gd! 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 KzDn-xr~eO~3Q%MK diff --git a/docs/en/docs/tutorial/sql-databases.md b/docs/en/docs/tutorial/sql-databases.md index 7836efae1..972eb9308 100644 --- a/docs/en/docs/tutorial/sql-databases.md +++ b/docs/en/docs/tutorial/sql-databases.md @@ -1,22 +1,18 @@ # SQL (Relational) Databases -/// info +**FastAPI** doesn't require you to use a SQL (relational) database. But you can use **any database** that you want. -These docs are about to be updated. ๐ŸŽ‰ +Here we'll see an example using SQLModel. -The current version assumes Pydantic v1, and SQLAlchemy versions less than 2.0. +**SQLModel** is built on top of SQLAlchemy and Pydantic. It was made by the same author of **FastAPI** to be the perfect match for FastAPI applications that need to use **SQL databases**. -The new docs will include Pydantic v2 and will use SQLModel (which is also based on SQLAlchemy) once it is updated to use Pydantic v2 as well. - -/// - -**FastAPI** doesn't require you to use a SQL (relational) database. +/// tip -But you can use any relational database that you want. +You could use any other SQL or NoSQL database library you want (in some cases called "ORMs"), FastAPI doesn't force you to use anything. ๐Ÿ˜Ž -Here we'll see an example using SQLAlchemy. +/// -You can easily adapt it to any database supported by SQLAlchemy, like: +As SQLModel is based on SQLAlchemy, you can easily use **any database supported** by SQLAlchemy (which makes them also supported by SQLModel), like: * PostgreSQL * MySQL @@ -30,891 +26,335 @@ Later, for your production application, you might want to use a database server /// tip -There is an official project generator with **FastAPI** and **PostgreSQL**, all based on **Docker**, including a frontend and more tools: https://github.com/tiangolo/full-stack-fastapi-postgresql - -/// - -/// note - -Notice that most of the code is the standard `SQLAlchemy` code you would use with any framework. - -The **FastAPI** specific code is as small as always. - -/// - -## ORMs - -**FastAPI** works with any database and any style of library to talk to the database. - -A common pattern is to use an "ORM": an "object-relational mapping" library. - -An ORM has tools to convert ("*map*") between *objects* in code and database tables ("*relations*"). - -With an ORM, you normally create a class that represents a table in a SQL database, each attribute of the class represents a column, with a name and a type. - -For example a class `Pet` could represent a SQL table `pets`. - -And each *instance* object of that class represents a row in the database. - -For example an object `orion_cat` (an instance of `Pet`) could have an attribute `orion_cat.type`, for the column `type`. And the value of that attribute could be, e.g. `"cat"`. - -These ORMs also have tools to make the connections or relations between tables or entities. - -This way, you could also have an attribute `orion_cat.owner` and the owner would contain the data for this pet's owner, taken from the table *owners*. - -So, `orion_cat.owner.name` could be the name (from the `name` column in the `owners` table) of this pet's owner. - -It could have a value like `"Arquilian"`. - -And the ORM will do all the work to get the information from the corresponding table *owners* when you try to access it from your pet object. - -Common ORMs are for example: Django-ORM (part of the Django framework), SQLAlchemy ORM (part of SQLAlchemy, independent of framework) and Peewee (independent of framework), among others. - -Here we will see how to work with **SQLAlchemy ORM**. - -In a similar way you could use any other ORM. - -/// tip - -There's an equivalent article using Peewee here in the docs. +There is an official project generator with **FastAPI** and **PostgreSQL** including a frontend and more tools: https://github.com/fastapi/full-stack-fastapi-template /// -## File structure - -For these examples, let's say you have a directory named `my_super_project` that contains a sub-directory called `sql_app` with a structure like this: - -``` -. -โ””โ”€โ”€ sql_app - โ”œโ”€โ”€ __init__.py - โ”œโ”€โ”€ crud.py - โ”œโ”€โ”€ database.py - โ”œโ”€โ”€ main.py - โ”œโ”€โ”€ models.py - โ””โ”€โ”€ schemas.py -``` - -The file `__init__.py` is just an empty file, but it tells Python that `sql_app` with all its modules (Python files) is a package. - -Now let's see what each file/module does. - -## Install `SQLAlchemy` +This is a very simple and short tutorial, if you want to learn about databases in general, about SQL, or more advanced features, go to the SQLModel docs. -First you need to install `SQLAlchemy`. +## Install `SQLModel` -Make sure you create a [virtual environment](../virtual-environments.md){.internal-link target=_blank}, activate it, and then install it, for example: +First, make sure you create your [virtual environment](../virtual-environments.md){.internal-link target=_blank}, activate it, and then install `sqlmodel`: