diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 47ef7c07f..000000000 --- a/.flake8 +++ /dev/null @@ -1,5 +0,0 @@ -[flake8] -max-line-length = 88 -select = C,E,F,W,B,B9 -ignore = E203, E501, W503 -exclude = __init__.py diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index e7de37465..16da4a2da 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -27,6 +27,7 @@ jobs: - README.md - docs/** - docs_src/** + - requirements-docs.txt - pyproject.toml - mkdocs.yml - mkdocs.insiders.yml @@ -45,28 +46,23 @@ jobs: run: echo "$GITHUB_CONTEXT" - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11" - uses: actions/cache@v3 id: cache with: path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-v01 - - name: Install Poetry + key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt') }}-v01 + - name: Install docs extras if: steps.cache.outputs.cache-hit != 'true' - run: | - python -m pip install --upgrade pip - python -m pip install "poetry" - python -m poetry self add poetry-version-plugin - - name: Configure poetry - run: python -m poetry config virtualenvs.create false - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: python -m poetry install + run: pip install -r requirements-docs.txt - name: Install Material for MkDocs Insiders if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' ) && steps.cache.outputs.cache-hit != 'true' - run: python -m poetry run pip install git+https://${{ secrets.SQLMODEL_MKDOCS_MATERIAL_INSIDERS }}@github.com/squidfunk/mkdocs-material-insiders.git + run: | + pip install git+https://${{ secrets.SQLMODEL_MKDOCS_MATERIAL_INSIDERS }}@github.com/squidfunk/mkdocs-material-insiders.git + pip install git+https://${{ secrets.SQLMODEL_MKDOCS_MATERIAL_INSIDERS }}@github.com/pawamoy-insiders/griffe-typing-deprecated.git + pip install git+https://${{ secrets.SQLMODEL_MKDOCS_MATERIAL_INSIDERS }}@github.com/pawamoy-insiders/mkdocstrings-python.git - uses: actions/cache@v3 with: key: mkdocs-cards-${{ github.ref }} @@ -75,7 +71,7 @@ jobs: run: python ./scripts/docs.py verify-readme - name: Build Docs run: python ./scripts/docs.py build - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: docs-site path: ./site/** diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index f9035d89a..41320b57b 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -19,18 +19,16 @@ jobs: run: | rm -rf ./site mkdir ./site - - name: Download Artifact Docs - id: download - uses: dawidd6/action-download-artifact@v2.28.0 + - uses: actions/download-artifact@v4 with: - if_no_artifact_found: ignore - github_token: ${{ secrets.GITHUB_TOKEN }} - workflow: build-docs.yml - run_id: ${{ github.event.workflow_run.id }} - name: docs-site path: ./site/ + pattern: docs-site + merge-multiple: true + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} - name: Deploy to Cloudflare Pages - if: steps.download.outputs.found_artifact == 'true' + # hashFiles returns an empty string if there are no files + if: hashFiles('./site/*') id: deploy uses: cloudflare/pages-action@v1 with: diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml index e2fb4f7a4..4eeda34b4 100644 --- a/.github/workflows/issue-manager.yml +++ b/.github/workflows/issue-manager.yml @@ -18,7 +18,7 @@ jobs: issue-manager: runs-on: ubuntu-latest steps: - - uses: tiangolo/issue-manager@0.4.0 + - uses: tiangolo/issue-manager@0.4.1 with: token: ${{ secrets.GITHUB_TOKEN }} config: > diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 42a0eec3c..1397e17ae 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,37 +14,24 @@ on: jobs: publish: runs-on: ubuntu-latest + strategy: + matrix: + package: + - sqlmodel + - sqlmodel-slim + permissions: + id-token: write steps: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: "3.7" - # Allow debugging with tmate - - name: Setup tmate session - uses: mxschmitt/action-tmate@v3 - if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} - with: - limit-access-to-actor: true - - uses: actions/cache@v3 - id: cache - with: - path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root-v2 - - name: Install poetry - if: steps.cache.outputs.cache-hit != 'true' - run: | - python -m pip install --upgrade pip - python -m pip install "poetry" - python -m poetry self add poetry-version-plugin - - name: Configure poetry - run: python -m poetry config virtualenvs.create false - - name: Install Dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: python -m poetry install - - name: Publish + python-version: "3.11" + - name: Install build dependencies + run: pip install build + - name: Build distribution env: - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - run: | - python -m poetry config pypi-token.pypi $PYPI_TOKEN - bash scripts/publish.sh + TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} + run: python -m build + - name: Publish + uses: pypa/gh-action-pypi-publish@v1.8.11 diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml index d2c274ff2..bc37a92e7 100644 --- a/.github/workflows/smokeshow.yml +++ b/.github/workflows/smokeshow.yml @@ -14,18 +14,20 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.9' - run: pip install smokeshow - - uses: dawidd6/action-download-artifact@v2.28.0 + - uses: actions/download-artifact@v4 with: - workflow: test.yml - commit: ${{ github.event.workflow_run.head_sha }} + name: coverage-html + path: htmlcov + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} - - run: smokeshow upload coverage-html + - run: smokeshow upload htmlcov env: SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage} SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 95 diff --git a/.github/workflows/test-redistribute.yml b/.github/workflows/test-redistribute.yml new file mode 100644 index 000000000..dc86da69c --- /dev/null +++ b/.github/workflows/test-redistribute.yml @@ -0,0 +1,53 @@ +name: Test Redistribute + +on: + push: + branches: + - main + pull_request: + types: + - opened + - synchronize + +jobs: + test-redistribute: + runs-on: ubuntu-latest + strategy: + matrix: + package: + - sqlmodel + - sqlmodel-slim + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + - name: Install build dependencies + run: pip install build + - name: Build source distribution + env: + TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} + run: python -m build --sdist + - name: Decompress source distribution + run: | + cd dist + tar xvf sqlmodel*.tar.gz + - name: Install test dependencies + run: | + cd dist/sqlmodel*/ + pip install -r requirements-tests.txt + env: + TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} + - name: Run source distribution tests + run: | + cd dist/sqlmodel*/ + bash scripts/test.sh + - name: Build wheel distribution + run: | + cd dist + pip wheel --no-deps sqlmodel*.tar.gz diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ade60f255..70e64094a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,6 +14,9 @@ on: description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' required: false default: 'false' + schedule: + # cron every week on monday + - cron: "0 0 * * 1" jobs: test: @@ -35,7 +38,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} # Allow debugging with tmate @@ -48,38 +51,30 @@ jobs: id: cache with: path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root-v2 - - name: Install poetry - if: steps.cache.outputs.cache-hit != 'true' - run: | - python -m pip install --upgrade pip - python -m pip install "poetry" - python -m poetry self add poetry-version-plugin - - name: Configure poetry - run: python -m poetry config virtualenvs.create false + key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }} - name: Install Dependencies if: steps.cache.outputs.cache-hit != 'true' - run: python -m poetry install + run: pip install -r requirements-tests.txt - name: Install Pydantic v1 if: matrix.pydantic-version == 'pydantic-v1' - run: pip install "pydantic>=1.10.0,<2.0.0" + run: pip install --upgrade "pydantic>=1.10.0,<2.0.0" - name: Install Pydantic v2 if: matrix.pydantic-version == 'pydantic-v2' - run: pip install "pydantic>=2.0.2,<3.0.0" + run: pip install --upgrade "pydantic>=2.0.2,<3.0.0" - name: Lint # Do not run on Python 3.7 as mypy behaves differently if: matrix.python-version != '3.7' && matrix.pydantic-version == 'pydantic-v2' - run: python -m poetry run bash scripts/lint.sh + run: bash scripts/lint.sh - run: mkdir coverage - name: Test - run: python -m poetry run bash scripts/test.sh + run: bash scripts/test.sh env: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} - name: Store coverage files - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: coverage + name: coverage-${{ matrix.python-version }}-${{ matrix.pydantic-version }} path: coverage coverage-combine: needs: @@ -89,15 +84,16 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.8' - name: Get coverage files - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: - name: coverage + pattern: coverage-* path: coverage + merge-multiple: true - run: pip install coverage[toml] @@ -107,7 +103,7 @@ jobs: - run: coverage html --show-contexts --title "Coverage for ${{ github.sha }}" - name: Store coverage HTML - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: coverage-html path: htmlcov diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b21e5ac9c..3289dd095 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.6 + rev: v0.2.0 hooks: - id: ruff args: diff --git a/docs/advanced/decimal.md b/docs/advanced/decimal.md index 2b58550d7..26994eccf 100644 --- a/docs/advanced/decimal.md +++ b/docs/advanced/decimal.md @@ -33,18 +33,44 @@ For the database, **SQLModel** will use - - - - -{%- endblock %} diff --git a/docs/release-notes.md b/docs/release-notes.md index 4a8f4c9cd..5b7d2b7e3 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,57 @@ ### Internal +* 👷 Update GitHub Actions to download and upload artifacts. PR [#936](https://github.com/tiangolo/sqlmodel/pull/936) by [@tiangolo](https://github.com/tiangolo). +* 👷 Tweak CI for test-redistribute, add needed env vars for slim. PR [#929](https://github.com/tiangolo/sqlmodel/pull/929) by [@tiangolo](https://github.com/tiangolo). + +## 0.0.18 + +### Internal + +* ✨ Add `sqlmodel-slim` setup. PR [#916](https://github.com/tiangolo/sqlmodel/pull/916) by [@tiangolo](https://github.com/tiangolo). + +In the future SQLModel will include the standard default recommended packages, and `sqlmodel-slim` will come without those recommended standard packages and with a group of optional dependencies `sqlmodel-slim[standard]`, equivalent to `sqlmodel`, for those that want to opt out of those packages. + +* 🔧 Re-enable MkDocs Material Social plugin. PR [#915](https://github.com/tiangolo/sqlmodel/pull/915) by [@tiangolo](https://github.com/tiangolo). + +## 0.0.17 + +### Refactors + +* ♻️ Refactor types to properly support Pydantic 2.7. PR [#913](https://github.com/tiangolo/sqlmodel/pull/913) by [@tiangolo](https://github.com/tiangolo). + +### Docs + +* 📝 Update ModelRead to ModelPublic documentation and examples. PR [#885](https://github.com/tiangolo/sqlmodel/pull/885) by [@estebanx64](https://github.com/estebanx64). +* ✨ Add source examples for Python 3.10 and 3.9 with updated syntax. PR [#842](https://github.com/tiangolo/sqlmodel/pull/842) by [@tiangolo](https://github.com/tiangolo) and [@estebanx64](https://github.com/estebanx64). + +### Internal + +* ⬆ Bump actions/setup-python from 4 to 5. PR [#733](https://github.com/tiangolo/sqlmodel/pull/733) by [@dependabot[bot]](https://github.com/apps/dependabot). +* 🔨 Update internal scripts and remove unused ones. PR [#914](https://github.com/tiangolo/sqlmodel/pull/914) by [@tiangolo](https://github.com/tiangolo). +* 🔧 Migrate from Poetry to PDM for the internal build config. PR [#912](https://github.com/tiangolo/sqlmodel/pull/912) by [@tiangolo](https://github.com/tiangolo). +* 🔧 Update MkDocs, disable cards while I can upgrade to the latest MkDocs Material, that fixes an issue with social cards. PR [#888](https://github.com/tiangolo/sqlmodel/pull/888) by [@tiangolo](https://github.com/tiangolo). +* 👷 Add cron to run test once a week on monday. PR [#869](https://github.com/tiangolo/sqlmodel/pull/869) by [@estebanx64](https://github.com/estebanx64). +* ⬆️ Upgrade Ruff version and configs. PR [#859](https://github.com/tiangolo/sqlmodel/pull/859) by [@tiangolo](https://github.com/tiangolo). +* 🔥 Remove Jina QA Bot as it has been discontinued. PR [#840](https://github.com/tiangolo/sqlmodel/pull/840) by [@tiangolo](https://github.com/tiangolo). + +## 0.0.16 + +### Features + +* ✨ Add new method `.sqlmodel_update()` to update models in place, including an `update` parameter for extra data. And fix implementation for the (now documented) `update` parameter for `.model_validate()`. PR [#804](https://github.com/tiangolo/sqlmodel/pull/804) by [@tiangolo](https://github.com/tiangolo). + * Updated docs: [Update Data with FastAPI](https://sqlmodel.tiangolo.com/tutorial/fastapi/update/). + * New docs: [Update with Extra Data (Hashed Passwords) with FastAPI](https://sqlmodel.tiangolo.com/tutorial/fastapi/update-extra-data/). + +## 0.0.15 + +### Fixes + +* 🐛 Fix class initialization compatibility with Pydantic and SQLModel, fixing errors revealed by the latest Pydantic. PR [#807](https://github.com/tiangolo/sqlmodel/pull/807) by [@tiangolo](https://github.com/tiangolo). + +### Internal + +* ⬆ Bump tiangolo/issue-manager from 0.4.0 to 0.4.1. PR [#775](https://github.com/tiangolo/sqlmodel/pull/775) by [@dependabot[bot]](https://github.com/apps/dependabot). * 👷 Fix GitHub Actions build docs filter paths for GitHub workflows. PR [#738](https://github.com/tiangolo/sqlmodel/pull/738) by [@tiangolo](https://github.com/tiangolo). ## 0.0.14 diff --git a/docs/tutorial/automatic-id-none-refresh.md b/docs/tutorial/automatic-id-none-refresh.md index fdde93a32..1f98a76cf 100644 --- a/docs/tutorial/automatic-id-none-refresh.md +++ b/docs/tutorial/automatic-id-none-refresh.md @@ -6,6 +6,20 @@ Now let's talk a bit about why the `id` field **can't be `NULL`** on the databas But the same `id` field actually **can be `None`** in the Python code, so we declare the type with `Optional[int]`, and set the default value to `Field(default=None)`: +//// tab | Python 3.10+ + +```Python hl_lines="4" +# Code above omitted 👆 + +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py[ln:4-8]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="4" # Code above omitted 👆 @@ -14,12 +28,26 @@ But the same `id` field actually **can be `None`** in the Python code, so we dec # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py!} ``` +//// + /// Next, I'll show you a bit more about the synchronization of data between the database and the Python code. @@ -30,6 +58,20 @@ When do we get an actual `int` from the database in that `id` field? Let's see a When we create a new `Hero` instance, we don't set the `id`: +//// tab | Python 3.10+ + +```Python hl_lines="3-6" +# Code above omitted 👆 + +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py[ln:21-24]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3-6" # Code above omitted 👆 @@ -38,12 +80,26 @@ When we create a new `Hero` instance, we don't set the `id`: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py!} ``` +//// + /// ### How `Optional` Helps @@ -72,6 +128,20 @@ But by declaring it with `Optional[int]`, the editor will help us to avoid writi We can confirm that by printing our heroes before adding them to the database: +//// tab | Python 3.10+ + +```Python hl_lines="9-11" +# Code above omitted 👆 + +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py[ln:21-29]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="9-11" # Code above omitted 👆 @@ -80,12 +150,26 @@ We can confirm that by printing our heroes before adding them to the database: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py!} ``` +//// + /// That will output: @@ -117,6 +201,20 @@ After we add the `Hero` instance objects to the **session**, the IDs are *still* We can verify by creating a session using a `with` block and adding the objects. And then printing them again: +//// tab | Python 3.10+ + +```Python hl_lines="19-21" +# Code above omitted 👆 + +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py[ln:21-39]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="19-21" # Code above omitted 👆 @@ -125,12 +223,26 @@ We can verify by creating a session using a `with` block and adding the objects. # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py!} ``` +//// + /// This will, again, output the `id`s of the objects as `None`: @@ -156,7 +268,21 @@ As we saw before, the **session** is smart and doesn't talk to the database ever Then we can `commit` the changes in the session, and print again: -```Python hl_lines="13 16-18" +//// tab | Python 3.10+ + +```Python hl_lines="13 16-18" +# Code above omitted 👆 + +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py[ln:31-46]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + +```Python hl_lines="13 16-18" # Code above omitted 👆 {!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:33-48]!} @@ -164,12 +290,26 @@ Then we can `commit` the changes in the session, and print again: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py!} ``` +//// + /// And now, something unexpected happens, look at the output, it seems as if the `Hero` instance objects had no data at all: @@ -228,7 +368,21 @@ We didn't access the object's attributes, like `hero.name`. We only accessed the To confirm and understand how this **automatic expiration and refresh** of data when accessing attributes work, we can print some individual fields (instance attributes): -```Python hl_lines="21-23 26-28" +//// tab | Python 3.10+ + +```Python hl_lines="21-23 26-28" +# Code above omitted 👆 + +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py[ln:31-56]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + +```Python hl_lines="21-23 26-28" # Code above omitted 👆 {!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:33-58]!} @@ -236,12 +390,26 @@ To confirm and understand how this **automatic expiration and refresh** of data # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py!} ``` +//// + /// Now we are actually accessing the attributes, because instead of printing the whole object `hero_1`: @@ -311,7 +479,6 @@ Hero 2 name: Spider-Boy Hero 3 name: Rusty-Man // Because the Session already refreshed these objects with all their data and the session knows they are not expired, it doesn't have to go again to the database for the names 🤓 - ``` @@ -324,7 +491,21 @@ But what if you want to **explicitly refresh** the data? You can do that too with `session.refresh(object)`: -```Python hl_lines="30-32 35-37" +//// tab | Python 3.10+ + +```Python hl_lines="30-32 35-37" +# Code above omitted 👆 + +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py[ln:31-65]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + +```Python hl_lines="30-32 35-37" # Code above omitted 👆 {!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:33-67]!} @@ -332,12 +513,26 @@ You can do that too with `session.refresh(object)`: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py!} ``` +//// + /// When Python executes this code: @@ -396,6 +591,20 @@ Now, as a final experiment, we can also print data after the **session** is clos There are no surprises here, it still works: +//// tab | Python 3.10+ + +```Python hl_lines="40-42" +# Code above omitted 👆 + +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py[ln:31-70]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="40-42" # Code above omitted 👆 @@ -404,12 +613,26 @@ There are no surprises here, it still works: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py!} ``` +//// + /// And the output shows again the same data: @@ -445,12 +668,26 @@ And as we created the **engine** with `echo=True`, we can see the SQL statements /// -```{ .python .annotate } +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/automatic_id_none_refresh/tutorial002_py310.py!} +``` + +{!./docs_src/tutorial/automatic_id_none_refresh/annotations/en/tutorial002.md!} + +//// + +//// tab | Python 3.7+ + +```Python {!./docs_src/tutorial/automatic_id_none_refresh/tutorial002.py!} ``` {!./docs_src/tutorial/automatic_id_none_refresh/annotations/en/tutorial002.md!} +//// + And here's all the output generated by running this program, all together:
diff --git a/docs/tutorial/connect/create-connected-rows.md b/docs/tutorial/connect/create-connected-rows.md index e2818c95e..d72c0b224 100644 --- a/docs/tutorial/connect/create-connected-rows.md +++ b/docs/tutorial/connect/create-connected-rows.md @@ -47,10 +47,22 @@ We will continue with the code in the previous example and we will add more thin /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/create_tables/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/create_tables/tutorial001.py!} ``` +//// + /// Make sure you remove the `database.db` file before running the examples to get the same results. @@ -63,6 +75,20 @@ And now we will also create the teams there. 🎉 Let's start by creating two teams: +//// tab | Python 3.10+ + +```Python hl_lines="3-9" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/insert/tutorial001_py310.py[ln:29-35]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3-9" # Code above omitted 👆 @@ -71,12 +97,26 @@ Let's start by creating two teams: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/insert/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/insert/tutorial001.py!} ``` +//// + /// This would hopefully look already familiar. @@ -93,6 +133,20 @@ And finally we **commit** the session to save the changes to the database. Let's not forget to add this function `create_heroes()` to the `main()` function so that we run it when calling the program from the command line: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/insert/tutorial001_py310.py[ln:61-63]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -101,12 +155,26 @@ Let's not forget to add this function `create_heroes()` to the `main()` function # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/insert/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/insert/tutorial001.py!} ``` +//// + /// ## Run it @@ -140,6 +208,20 @@ Now let's create one hero object to start. As the `Hero` class model now has a field (column, attribute) `team_id`, we can set it by using the ID field from the `Team` objects we just created before: +//// tab | Python 3.10+ + +```Python hl_lines="12" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/insert/tutorial001_py310.py[ln:29-39]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="12" # Code above omitted 👆 @@ -148,12 +230,26 @@ As the `Hero` class model now has a field (column, attribute) `team_id`, we can # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/insert/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/insert/tutorial001.py!} ``` +//// + /// We haven't committed this hero to the database yet, but there are already a couple of things to pay **attention** to. @@ -178,6 +274,20 @@ INFO Engine [generated in 0.00025s] (2,) Let's now create two more heroes: +//// tab | Python 3.10+ + +```Python hl_lines="14-20" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/insert/tutorial001_py310.py[ln:29-50]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="14-20" # Code above omitted 👆 @@ -186,12 +296,26 @@ Let's now create two more heroes: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/insert/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/insert/tutorial001.py!} ``` +//// + /// When creating `hero_rusty_man`, we are accessing `team_preventers.id`, so that will also trigger a refresh of its data, generating an output of: @@ -223,7 +347,21 @@ INFO Engine COMMIT Now let's refresh and print those new heroes to see their new ID pointing to their teams: -```Python hl_lines="26-28 30-32" +//// tab | Python 3.10+ + +```Python hl_lines="26-28 30-32" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/insert/tutorial001_py310.py[ln:29-58]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + +```Python hl_lines="26-28 30-32" # Code above omitted 👆 {!./docs_src/tutorial/connect/insert/tutorial001.py[ln:31-60]!} @@ -231,14 +369,27 @@ Now let's refresh and print those new heroes to see their new ID pointing to the # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/insert/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/insert/tutorial001.py!} ``` -/// +//// +/// If we execute that in the command line, it will output: diff --git a/docs/tutorial/connect/create-connected-tables.md b/docs/tutorial/connect/create-connected-tables.md index a27cbfed7..42e870f70 100644 --- a/docs/tutorial/connect/create-connected-tables.md +++ b/docs/tutorial/connect/create-connected-tables.md @@ -57,18 +57,44 @@ Let's start by creating the tables in code. Import the things we need from `sqlmodel` and create a new `Team` model: +//// tab | Python 3.10+ + +```Python hl_lines="4-7" +{!./docs_src/tutorial/connect/create_tables/tutorial001_py310.py[ln:1-7]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="6-9" {!./docs_src/tutorial/connect/create_tables/tutorial001.py[ln:1-9]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/create_tables/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/create_tables/tutorial001.py!} ``` +//// + /// This is very similar to what we have been doing with the `Hero` model. @@ -88,18 +114,44 @@ Now let's create the `hero` table. This is the same model we have been using up to now, we are just adding the new column `team_id`: +//// tab | Python 3.10+ + +```Python hl_lines="16" +{!./docs_src/tutorial/connect/create_tables/tutorial001_py310.py[ln:1-16]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="18" {!./docs_src/tutorial/connect/create_tables/tutorial001.py[ln:1-18]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/create_tables/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/create_tables/tutorial001.py!} ``` +//// + /// Most of that should look familiar: @@ -134,34 +186,86 @@ You can learn about setting a custom table name for a model in the Advanced User Now we can add the same code as before to create the engine and the function to create the tables: +//// tab | Python 3.10+ + +```Python hl_lines="3-4 6 9-10" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/create_tables/tutorial001_py310.py[ln:19-26]!} +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3-4 6 9-10" # Code above omitted 👆 {!./docs_src/tutorial/connect/create_tables/tutorial001.py[ln:21-28]!} ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/create_tables/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/create_tables/tutorial001.py!} ``` +//// + /// And as before, we'll call this function from another function `main()`, and we'll add that function `main()` to the main block of the file: +//// tab | Python 3.10+ + +```Python hl_lines="3-4 7-8" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/create_tables/tutorial001_py310.py[ln:29-34]!} +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3-4 7-8" # Code above omitted 👆 {!./docs_src/tutorial/connect/create_tables/tutorial001.py[ln:31-36]!} ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/create_tables/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/create_tables/tutorial001.py!} ``` +//// + /// ## Run the Code diff --git a/docs/tutorial/connect/read-connected-data.md b/docs/tutorial/connect/read-connected-data.md index 128eb550b..19cdbe172 100644 --- a/docs/tutorial/connect/read-connected-data.md +++ b/docs/tutorial/connect/read-connected-data.md @@ -37,10 +37,22 @@ We will continue with the code in the previous example and we will add more thin /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/insert/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/insert/tutorial001.py!} ``` +//// + /// ## `SELECT` Connected Data with SQL @@ -123,6 +135,20 @@ Remember SQLModel's `select()` function? It can take more than one argument. So, we can pass the `Hero` and `Team` model classes. And we can also use both their columns in the `.where()` part: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/select/tutorial001_py310.py[ln:61-63]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -131,12 +157,26 @@ So, we can pass the `Hero` and `Team` model classes. And we can also use both th # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/select/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/select/tutorial001.py!} ``` +//// + /// Notice that in the comparison with `==` we are using the class attributes for both `Hero.team_id` and `Team.id`. @@ -147,6 +187,20 @@ Now we can execute it and get the `results` object. And as we used `select` with two models, we will receive tuples of instances of those two models, so we can iterate over them naturally in a `for` loop: +//// tab | Python 3.10+ + +```Python hl_lines="7" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/select/tutorial001_py310.py[ln:61-66]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="7" # Code above omitted 👆 @@ -155,12 +209,26 @@ And as we used `select` with two models, we will receive tuples of instances of # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/select/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/select/tutorial001.py!} ``` +//// + /// For each iteration in the `for` loop we get a a tuple with an instance of the class `Hero` and an instance of the class `Team`. @@ -179,6 +247,20 @@ And you should get autocompletion and inline errors in your editor for both `her As always, we must remember to add this new `select_heroes()` function to the `main()` function to make sure it is executed when we call this program from the command line. +//// tab | Python 3.10+ + +```Python hl_lines="6" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/select/tutorial001_py310.py[ln:69-72]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="6" # Code above omitted 👆 @@ -187,12 +269,26 @@ As always, we must remember to add this new `select_heroes()` function to the `m # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/select/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/select/tutorial001.py!} ``` +//// + /// @@ -300,6 +396,20 @@ The same way there's a `.where()` available when using `select()`, there's also And in SQLModel (actually SQLAlchemy), when using the `.join()`, because we already declared what is the `foreign_key` when creating the models, we don't have to pass an `ON` part, it is inferred automatically: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/select/tutorial002_py310.py[ln:61-66]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -308,12 +418,26 @@ And in SQLModel (actually SQLAlchemy), when using the `.join()`, because we alre # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/select/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/select/tutorial002.py!} ``` +//// + /// Also notice that we are still including `Team` in the `select(Hero, Team)`, because we still want to access that data. @@ -441,6 +565,20 @@ Now let's replicate the same query in **SQLModel**. `.join()` has a parameter we can use `isouter=True` to make the `JOIN` be a `LEFT OUTER JOIN`: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/select/tutorial003_py310.py[ln:61-66]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -449,12 +587,26 @@ Now let's replicate the same query in **SQLModel**. # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/select/tutorial003_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/select/tutorial003.py!} ``` +//// + /// And if we run it, it will output: @@ -502,6 +654,20 @@ But we would still be able to **filter** the rows with it. 🤓 We could even add some additional `.where()` after `.join()` to filter the data more, for example to return only the heroes from one team: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/select/tutorial004_py310.py[ln:61-66]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -510,12 +676,26 @@ We could even add some additional `.where()` after `.join()` to filter the data # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/select/tutorial004_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/select/tutorial004.py!} ``` +//// + /// Here we are **filtering** with `.where()` to get only the heroes that belong to the **Preventers** team. @@ -547,6 +727,20 @@ Preventer Hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48 By putting the `Team` in `select()` we tell **SQLModel** and the database that we want the team data too. +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/select/tutorial005_py310.py[ln:61-66]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -555,12 +749,26 @@ By putting the `Team` in `select()` we tell **SQLModel** and the database that w # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/select/tutorial005_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/select/tutorial005.py!} ``` +//// + /// And if we run that, it will output: diff --git a/docs/tutorial/connect/remove-data-connections.md b/docs/tutorial/connect/remove-data-connections.md index 72e933cb6..5c2977fb1 100644 --- a/docs/tutorial/connect/remove-data-connections.md +++ b/docs/tutorial/connect/remove-data-connections.md @@ -37,10 +37,22 @@ We will continue with the code from the previous chapter. /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/update/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/update/tutorial001.py!} ``` +//// + /// ## Break a Connection @@ -51,6 +63,24 @@ Let's say **Spider-Boy** is tired of the lack of friendly neighbors and wants to We can simply set the `team_id` to `None`, and now it doesn't have a connection with the team: +//// tab | Python 3.10+ + +```Python hl_lines="8" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/delete/tutorial001_py310.py[ln:29-30]!} + + # Previous code here omitted 👈 + +{!./docs_src/tutorial/connect/delete/tutorial001_py310.py[ln:66-70]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="8" # Code above omitted 👆 @@ -63,12 +93,26 @@ We can simply set the `team_id` to `None`, and now it doesn't have a connection # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/delete/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/delete/tutorial001.py!} ``` +//// + /// Again, we just **assign** a value to that field attribute `team_id`, now the value is `None`, which means `NULL` in the database. Then we `add()` the hero to the session, and then `commit()`. diff --git a/docs/tutorial/connect/update-data-connections.md b/docs/tutorial/connect/update-data-connections.md index c141aa69d..8f5687b8e 100644 --- a/docs/tutorial/connect/update-data-connections.md +++ b/docs/tutorial/connect/update-data-connections.md @@ -39,10 +39,22 @@ We will continue with the code we used to create some heroes, and we'll update t /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/insert/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/insert/tutorial001.py!} ``` +//// + /// ## Assign a Team to a Hero @@ -51,6 +63,24 @@ Let's say that **Tommy Sharp** uses his "rich uncle" charms to recruit **Spider- Doing it is just like updating any other field: +//// tab | Python 3.10+ + +```Python hl_lines="8" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/update/tutorial001_py310.py[ln:29-30]!} + + # Previous code here omitted 👈 + +{!./docs_src/tutorial/connect/update/tutorial001_py310.py[ln:60-64]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="8" # Code above omitted 👆 @@ -63,12 +93,26 @@ Doing it is just like updating any other field: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/update/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/update/tutorial001.py!} ``` +//// + /// We can simply **assign** a value to that field attribute `team_id`, then `add()` the hero to the session, and then `commit()`. diff --git a/docs/tutorial/create-db-and-table.md b/docs/tutorial/create-db-and-table.md index 0d8a9a21c..d87b935a1 100644 --- a/docs/tutorial/create-db-and-table.md +++ b/docs/tutorial/create-db-and-table.md @@ -41,18 +41,44 @@ That's why this package is called `SQLModel`. Because it's mainly used to create For that, we will import `SQLModel` (plus other things we will also use) and create a class `Hero` that inherits from `SQLModel` and represents the **table model** for our heroes: +//// tab | Python 3.10+ + +```Python hl_lines="1 4" +{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py[ln:1-8]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3 6" {!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-10]!} # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/create_db_and_table/tutorial001.py!} ``` +//// + /// This class `Hero` **represents the table** for our heroes. And each instance we create later will **represent a row** in the table. @@ -75,18 +101,44 @@ The name of each of these variables will be the name of the column in the table. And the type of each of them will also be the type of table column: +//// tab | Python 3.10+ + +```Python hl_lines="1 5-8" +{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py[ln:1-8]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="1 3 7-10" {!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-10]!} # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/create_db_and_table/tutorial001.py!} ``` +//// + /// Let's now see with more detail these field/column declarations. @@ -101,18 +153,44 @@ That is the standard way to declare that something "could be an `int` or `None`" And we also set the default value of `age` to `None`. +//// tab | Python 3.10+ + +```Python hl_lines="8" +{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py[ln:1-8]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="1 10" {!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-10]!} # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/create_db_and_table/tutorial001.py!} ``` +//// + /// /// tip @@ -143,18 +221,44 @@ So, we need to mark `id` as the **primary key**. To do that, we use the special `Field` function from `sqlmodel` and set the argument `primary_key=True`: +//// tab | Python 3.10+ + +```Python hl_lines="1 5" +{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py[ln:1-8]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3 7" {!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-10]!} # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/create_db_and_table/tutorial001.py!} ``` +//// + /// That way, we tell **SQLModel** that this `id` field/column is the primary key of the table. @@ -198,18 +302,44 @@ If you have a server database (for example PostgreSQL or MySQL), the **engine** Creating the **engine** is very simple, just call `create_engine()` with a URL for the database to use: +//// tab | Python 3.10+ + +```Python hl_lines="1 14" +{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py[ln:1-16]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3 16" -{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-16]!} +{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-18]!} # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/create_db_and_table/tutorial001.py!} ``` +//// + /// You should normally have a single **engine** object for your whole application and re-use it everywhere. @@ -234,18 +364,44 @@ SQLite supports a special database that lives all *in memory*. Hence, it's very * `sqlite://` +//// tab | Python 3.10+ + +```Python hl_lines="11-12 14" +{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py[ln:1-16]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="13-14 16" -{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-19]!} +{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-18]!} # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/create_db_and_table/tutorial001.py!} ``` +//// + /// You can read a lot more about all the databases supported by **SQLAlchemy** (and that way supported by **SQLModel**) in the SQLAlchemy documentation. @@ -258,18 +414,44 @@ It will make the engine print all the SQL statements it executes, which can help It is particularly useful for **learning** and **debugging**: +//// tab | Python 3.10+ + +```Python hl_lines="14" +{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py[ln:1-16]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="16" -{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-16]!} +{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-18]!} # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/create_db_and_table/tutorial001.py!} ``` +//// + /// But in production, you would probably want to remove `echo=True`: @@ -296,10 +478,22 @@ And SQLModel's version of `create_engine()` is type annotated internally, so you Now everything is in place to finally create the database and table: +//// tab | Python 3.10+ + +```Python hl_lines="16" +{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="18" {!./docs_src/tutorial/create_db_and_table/tutorial001.py!} ``` +//// + /// tip Creating the engine doesn't create the `database.db` file. @@ -411,10 +605,22 @@ Put the code it in a file `app.py` if you haven't already. /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/create_db_and_table/tutorial001.py!} ``` +//// + /// /// tip @@ -520,18 +726,44 @@ In this example it's just the `SQLModel.metadata.create_all(engine)`. Let's put it in a function `create_db_and_tables()`: +//// tab | Python 3.10+ + +```Python hl_lines="17-18" +{!./docs_src/tutorial/create_db_and_table/tutorial002_py310.py[ln:1-18]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="19-20" {!./docs_src/tutorial/create_db_and_table/tutorial002.py[ln:1-20]!} # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/create_db_and_table/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/create_db_and_table/tutorial002.py!} ``` +//// + /// If `SQLModel.metadata.create_all(engine)` was not in a function and we tried to import something from this module (from this file) in another, it would try to create the database and table **every time** we executed that other file that imported this module. @@ -562,10 +794,22 @@ The word **script** often implies that the code could be run independently and e For that we can use the special variable `__name__` in an `if` block: +//// tab | Python 3.10+ + +```Python hl_lines="21-22" +{!./docs_src/tutorial/create_db_and_table/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="23-24" {!./docs_src/tutorial/create_db_and_table/tutorial002.py!} ``` +//// + ### About `__name__ == "__main__"` The main purpose of the `__name__ == "__main__"` is to have some code that is executed when your file is called with: @@ -658,12 +902,26 @@ But now we can import things from this module in other files. Now, let's give the code a final look: +//// tab | Python 3.10+ + +```{.python .annotate} +{!./docs_src/tutorial/create_db_and_table/tutorial003_py310.py!} +``` + +{!./docs_src/tutorial/create_db_and_table/annotations/en/tutorial003.md!} + +//// + +//// tab | Python 3.7+ + ```{.python .annotate} {!./docs_src/tutorial/create_db_and_table/tutorial003.py!} ``` {!./docs_src/tutorial/create_db_and_table/annotations/en/tutorial003.md!} +//// + /// tip Review what each line does by clicking each number bubble in the code. 👆 diff --git a/docs/tutorial/delete.md b/docs/tutorial/delete.md index 437d388b8..9cb6748b7 100644 --- a/docs/tutorial/delete.md +++ b/docs/tutorial/delete.md @@ -8,10 +8,22 @@ As before, we'll continue from where we left off with the previous code. /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/update/tutorial003_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/update/tutorial003.py!} ``` +//// + /// Remember to remove the `database.db` file before running the examples to get the same results. @@ -62,6 +74,20 @@ To get the same results, delete the `database.db` file before running the exampl We'll start by selecting the hero `"Spider-Youngster"` that we updated in the previous chapter, this is the one we will delete: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/delete/tutorial001_py310.py[ln:70-75]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -70,28 +96,68 @@ We'll start by selecting the hero `"Spider-Youngster"` that we updated in the pr # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/delete/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/delete/tutorial001.py!} ``` +//// + /// As this is a new function `delete_heroes()`, we'll also add it to the `main()` function so that we call it when executing the program from the command line: +//// tab | Python 3.10+ + +```Python hl_lines="7" +# Code above omitted 👆 + +{!./docs_src/tutorial/delete/tutorial001_py310.py[ln:90-98]!} +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="7" # Code above omitted 👆 {!./docs_src/tutorial/delete/tutorial001.py[ln:92-100]!} ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/delete/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/delete/tutorial001.py!} ``` +//// + /// That will print the same existing hero **Spider-Youngster**: @@ -120,6 +186,20 @@ Hero: name='Spider-Youngster' secret_name='Pedro Parqueador' age=16 id=2 Now, very similar to how we used `session.add()` to add or update new heroes, we can use `session.delete()` to delete the hero from the session: +//// tab | Python 3.10+ + +```Python hl_lines="10" +# Code above omitted 👆 + +{!./docs_src/tutorial/delete/tutorial001_py310.py[ln:70-77]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="10" # Code above omitted 👆 @@ -128,12 +208,26 @@ Now, very similar to how we used `session.add()` to add or update new heroes, we # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/delete/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/delete/tutorial001.py!} ``` +//// + /// ## Commit the Session @@ -142,6 +236,20 @@ To save the current changes in the session, **commit** it. This will save all the changes stored in the **session**, like the deleted hero: +//// tab | Python 3.10+ + +```Python hl_lines="11" +# Code above omitted 👆 + +{!./docs_src/tutorial/delete/tutorial001_py310.py[ln:70-78]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="11" # Code above omitted 👆 @@ -150,12 +258,26 @@ This will save all the changes stored in the **session**, like the deleted hero: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/delete/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/delete/tutorial001.py!} ``` +//// + /// The same as we have seen before, `.commit()` will also save anything else that was added to the session. Including updates, or created heroes. @@ -191,6 +313,20 @@ As the object is not connected to the session, it is not marked as "expired", th Because of that, the object still contains its attributes with the data in it, so we can print it: +//// tab | Python 3.10+ + +```Python hl_lines="13" +# Code above omitted 👆 + +{!./docs_src/tutorial/delete/tutorial001_py310.py[ln:70-80]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="13" # Code above omitted 👆 @@ -199,12 +335,26 @@ Because of that, the object still contains its attributes with the data in it, s # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/delete/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/delete/tutorial001.py!} ``` +//// + /// This will output: @@ -228,6 +378,20 @@ Deleted hero: name='Spider-Youngster' secret_name='Pedro Parqueador' age=16 id=2 To confirm if it was deleted, now let's query the database again, with the same `"Spider-Youngster"` name: +//// tab | Python 3.10+ + +```Python hl_lines="15-17" +# Code above omitted 👆 + +{!./docs_src/tutorial/delete/tutorial001_py310.py[ln:70-84]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="15-17" # Code above omitted 👆 @@ -236,12 +400,26 @@ To confirm if it was deleted, now let's query the database again, with the same # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/delete/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/delete/tutorial001.py!} ``` +//// + /// Here we are using `results.first()` to get the first object found (in case it found multiple) or `None`, if it didn't find anything. @@ -279,6 +457,20 @@ Now let's just confirm that, indeed, no hero was found in the database with that We'll do it by checking that the "first" item in the `results` is `None`: +//// tab | Python 3.10+ + +```Python hl_lines="19-20" +# Code above omitted 👆 + +{!./docs_src/tutorial/delete/tutorial001_py310.py[ln:70-87]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="19-20" # Code above omitted 👆 @@ -287,12 +479,26 @@ We'll do it by checking that the "first" item in the `results` is `None`: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/delete/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/delete/tutorial001.py!} ``` +//// + /// This will output: @@ -319,12 +525,26 @@ INFO Engine ROLLBACK Now let's review all that code: +//// tab | Python 3.10+ + +```{ .python .annotate hl_lines="70-88" } +{!./docs_src/tutorial/delete/tutorial002_py310.py!} +``` + +{!./docs_src/tutorial/delete/annotations/en/tutorial002.md!} + +//// + +//// tab | Python 3.7+ + ```{ .python .annotate hl_lines="72-90" } {!./docs_src/tutorial/delete/tutorial002.py!} ``` {!./docs_src/tutorial/delete/annotations/en/tutorial002.md!} +//// + /// tip Check out the number bubbles to see what is done by each line of code. diff --git a/docs/tutorial/fastapi/delete.md b/docs/tutorial/fastapi/delete.md index 02fdc9fb5..87144cc08 100644 --- a/docs/tutorial/fastapi/delete.md +++ b/docs/tutorial/fastapi/delete.md @@ -12,6 +12,32 @@ We get a `hero_id` from the path parameter and verify if it exists, just as we d And if we actually find a hero, we just delete it with the **session**. +//// tab | Python 3.10+ + +```Python hl_lines="3-11" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/delete/tutorial001_py310.py[ln:89-97]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3-11" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/delete/tutorial001_py39.py[ln:91-99]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3-11" # Code above omitted 👆 @@ -20,12 +46,34 @@ And if we actually find a hero, we just delete it with the **session**. # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/delete/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/delete/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/delete/tutorial001.py!} ``` +//// + /// After deleting it successfully, we just return a response of: diff --git a/docs/tutorial/fastapi/limit-and-offset.md b/docs/tutorial/fastapi/limit-and-offset.md index b9d1c8b73..61d282f60 100644 --- a/docs/tutorial/fastapi/limit-and-offset.md +++ b/docs/tutorial/fastapi/limit-and-offset.md @@ -22,6 +22,36 @@ By default, we will return the first results from the database, so `offset` will And by default, we will return a maximum of `100` heroes, so `limit` will have a default value of `100`. +//// tab | Python 3.10+ + +```Python hl_lines="1 7 9" +{!./docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py310.py[ln:1-2]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py310.py[ln:52-56]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3 9 11" +{!./docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py39.py[ln:1-4]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py39.py[ln:54-58]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3 9 11" {!./docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py[ln:1-4]!} @@ -32,12 +62,34 @@ And by default, we will return a maximum of `100` heroes, so `limit` will have a # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py!} ``` +//// + /// We want to allow clients to set different `offset` and `limit` values. diff --git a/docs/tutorial/fastapi/multiple-models.md b/docs/tutorial/fastapi/multiple-models.md index 3995daa65..41c1ac649 100644 --- a/docs/tutorial/fastapi/multiple-models.md +++ b/docs/tutorial/fastapi/multiple-models.md @@ -98,7 +98,7 @@ But we also want to have a `HeroCreate` for the data we want to receive when **c * `secret_name`, required * `age`, optional -And we want to have a `HeroRead` with the `id` field, but this time annotated with `id: int`, instead of `id: Optional[int]`, to make it clear that it is required in responses **read** from the clients: +And we want to have a `HeroPublic` with the `id` field, but this time annotated with `id: int`, instead of `id: Optional[int]`, to make it clear that it is required in responses **read** from the clients: * `id`, required * `name`, required @@ -109,6 +109,36 @@ And we want to have a `HeroRead` with the `id` field, but this time annotated wi The simplest way to solve it could be to create **multiple models**, each one with all the corresponding fields: +//// tab | Python 3.10+ + +```Python hl_lines="5-9 12-15 18-22" +# This would work, but there's a better option below 🚨 + +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py[ln:5-22]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="5-9 12-15 18-22" +# This would work, but there's a better option below 🚨 + +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial001_py39.py[ln:7-24]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5-9 12-15 18-22" # This would work, but there's a better option below 🚨 @@ -119,21 +149,43 @@ The simplest way to solve it could be to create **multiple models**, each one wi # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/multiple_models/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/multiple_models/tutorial001.py!} ``` +//// + /// Here's the important detail, and probably the most important feature of **SQLModel**: only `Hero` is declared with `table = True`. This means that the class `Hero` represents a **table** in the database. It is both a **Pydantic** model and a **SQLAlchemy** model. -But `HeroCreate` and `HeroRead` don't have `table = True`. They are only **data models**, they are only **Pydantic** models. They won't be used with the database, but only to declare data schemas for the API (or for other uses). +But `HeroCreate` and `HeroPublic` don't have `table = True`. They are only **data models**, they are only **Pydantic** models. They won't be used with the database, but only to declare data schemas for the API (or for other uses). -This also means that `SQLModel.metadata.create_all()` won't create tables in the database for `HeroCreate` and `HeroRead`, because they don't have `table = True`, which is exactly what we want. 🚀 +This also means that `SQLModel.metadata.create_all()` won't create tables in the database for `HeroCreate` and `HeroPublic`, because they don't have `table = True`, which is exactly what we want. 🚀 /// tip @@ -147,6 +199,32 @@ Let's now see how to use these new models in the FastAPI application. Let's first check how is the process to create a hero now: +//// tab | Python 3.10+ + +```Python hl_lines="3-4 6" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py[ln:44-51]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3-4 6" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial001_py39.py[ln:46-53]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3-4 6" # Code above omitted 👆 @@ -155,18 +233,66 @@ Let's first check how is the process to create a hero now: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/multiple_models/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/multiple_models/tutorial001.py!} ``` +//// + /// Let's check that in detail. Now we use the type annotation `HeroCreate` for the request JSON data in the `hero` parameter of the **path operation function**. +//// tab | Python 3.10+ + +```Python hl_lines="3" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py[ln:45]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial001_py39.py[ln:47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3" # Code above omitted 👆 @@ -175,6 +301,8 @@ Now we use the type annotation `HeroCreate` for the request JSON data in the `he # Code below omitted 👇 ``` +//// + Then we create a new `Hero` (this is the actual **table** model that saves things to the database) using `Hero.model_validate()`. The method `.model_validate()` reads data from another object with attributes (or a dict) and creates a new instance of this class, in this case `Hero`. @@ -187,6 +315,32 @@ In versions of **SQLModel** before `0.0.14` you would use the method `.from_orm( We can now create a new `Hero` instance (the one for the database) and put it in the variable `db_hero` from the data in the `hero` variable that is the `HeroCreate` instance we received from the request. +//// tab | Python 3.10+ + +```Python hl_lines="3" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py[ln:47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial001_py39.py[ln:49]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3" # Code above omitted 👆 @@ -195,11 +349,39 @@ We can now create a new `Hero` instance (the one for the database) and put it in # Code below omitted 👇 ``` +//// + Then we just `add` it to the **session**, `commit`, and `refresh` it, and finally, we return the same `db_hero` variable that has the just refreshed `Hero` instance. Because it is just refreshed, it has the `id` field set with a new ID taken from the database. -And now that we return it, FastAPI will validate the data with the `response_model`, which is a `HeroRead`: +And now that we return it, FastAPI will validate the data with the `response_model`, which is a `HeroPublic`: + +//// tab | Python 3.10+ + +```Python hl_lines="3" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py[ln:44]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial001_py39.py[ln:46]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ ```Python hl_lines="3" # Code above omitted 👆 @@ -209,6 +391,8 @@ And now that we return it, FastAPI will validate the data with the `response_mod # Code below omitted 👇 ``` +//// + This will validate that all the data that we promised is there and will remove any data we didn't declare. /// tip @@ -259,6 +443,32 @@ We can see from above that they all share some **base** fields: So let's create a **base** model `HeroBase` that the others can inherit from: +//// tab | Python 3.10+ + +```Python hl_lines="3-6" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py[ln:5-8]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3-6" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py39.py[ln:7-10]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3-6" # Code above omitted 👆 @@ -267,12 +477,34 @@ So let's create a **base** model `HeroBase` that the others can inherit from: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py!} ``` +//// + /// As you can see, this is *not* a **table model**, it doesn't have the `table = True` config. @@ -283,6 +515,32 @@ But now we can create the **other models inheriting from it**, they will all sha Let's start with the only **table model**, the `Hero`: +//// tab | Python 3.10+ + +```Python hl_lines="9-10" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py[ln:5-12]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="9-10" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py39.py[ln:7-14]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="9-10" # Code above omitted 👆 @@ -291,12 +549,34 @@ Let's start with the only **table model**, the `Hero`: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py!} ``` +//// + /// Notice that `Hero` now doesn't inherit from `SQLModel`, but from `HeroBase`. @@ -313,6 +593,32 @@ And those inherited fields will also be in the **autocompletion** and **inline e Notice that the parent model `HeroBase` is not a **table model**, but still, we can declare `name` and `age` using `Field(index=True)`. +//// tab | Python 3.10+ + +```Python hl_lines="4 6 9" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py[ln:5-12]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="4 6 9" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py39.py[ln:7-14]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="4 6 9" # Code above omitted 👆 @@ -321,12 +627,34 @@ Notice that the parent model `HeroBase` is not a **table model**, but still, we # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py!} ``` +//// + /// This won't affect this parent **data model** `HeroBase`. @@ -339,6 +667,32 @@ Now let's see the `HeroCreate` model that will be used to define the data that w This is a fun one: +//// tab | Python 3.10+ + +```Python hl_lines="13-14" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py[ln:5-16]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="13-14" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py39.py[ln:7-18]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="13-14" # Code above omitted 👆 @@ -347,12 +701,34 @@ This is a fun one: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py!} ``` +//// + /// What's happening here? @@ -367,12 +743,38 @@ As an alternative, we could use `HeroBase` directly in the API code instead of ` On top of that, we could easily decide in the future that we want to receive **more data** when creating a new hero apart from the data in `HeroBase` (for example, a password), and now we already have the class to put those extra fields. -### The `HeroRead` **Data Model** +### The `HeroPublic` **Data Model** -Now let's check the `HeroRead` model. +Now let's check the `HeroPublic` model. This one just declares that the `id` field is required when reading a hero from the API, because a hero read from the API will come from the database, and in the database it will always have an ID. +//// tab | Python 3.10+ + +```Python hl_lines="17-18" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py[ln:5-20]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="17-18" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py39.py[ln:7-22]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="17-18" # Code above omitted 👆 @@ -381,17 +783,39 @@ This one just declares that the `id` field is required when reading a hero from # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py!} ``` +//// + /// ## Review the Updated Docs UI -The FastAPI code is still the same as above, we still use `Hero`, `HeroCreate`, and `HeroRead`. But now, we define them in a smarter way with inheritance. +The FastAPI code is still the same as above, we still use `Hero`, `HeroCreate`, and `HeroPublic`. But now, we define them in a smarter way with inheritance. So, we can jump to the docs UI right away and see how they look with the updated data. diff --git a/docs/tutorial/fastapi/read-one.md b/docs/tutorial/fastapi/read-one.md index 0fab696c1..0976961e0 100644 --- a/docs/tutorial/fastapi/read-one.md +++ b/docs/tutorial/fastapi/read-one.md @@ -14,6 +14,32 @@ If you need to refresh how *path parameters* work, including their data validati /// +//// tab | Python 3.10+ + +```Python hl_lines="6" +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py[ln:1-2]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py[ln:59-65]!} +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="8" +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py39.py[ln:1-4]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py39.py[ln:61-67]!} +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="8" {!./docs_src/tutorial/fastapi/read_one/tutorial001.py[ln:1-4]!} @@ -22,12 +48,34 @@ If you need to refresh how *path parameters* work, including their data validati {!./docs_src/tutorial/fastapi/read_one/tutorial001.py[ln:61-67]!} ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/read_one/tutorial001.py!} ``` +//// + /// For example, to get the hero with ID `2` we would send a `GET` request to: @@ -48,6 +96,32 @@ And to use it, we first import `HTTPException` from `fastapi`. This will let the client know that they probably made a mistake on their side and requested a hero that doesn't exist in the database. +//// tab | Python 3.10+ + +```Python hl_lines="1 9-11" +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py[ln:1-2]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py[ln:59-65]!} +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3 11-13" +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py39.py[ln:1-4]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py39.py[ln:61-67]!} +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3 11-13" {!./docs_src/tutorial/fastapi/read_one/tutorial001.py[ln:1-4]!} @@ -56,19 +130,67 @@ This will let the client know that they probably made a mistake on their side an {!./docs_src/tutorial/fastapi/read_one/tutorial001.py[ln:61-67]!} ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/read_one/tutorial001.py!} ``` +//// + /// ## Return the Hero Then, if the hero exists, we return it. -And because we are using the `response_model` with `HeroRead`, it will be validated, documented, etc. +And because we are using the `response_model` with `HeroPublic`, it will be validated, documented, etc. + +//// tab | Python 3.10+ + +```Python hl_lines="6 12" +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py[ln:1-2]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py[ln:59-65]!} +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="8 14" +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py39.py[ln:1-4]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py39.py[ln:61-67]!} +``` + +//// + +//// tab | Python 3.7+ ```Python hl_lines="8 14" {!./docs_src/tutorial/fastapi/read_one/tutorial001.py[ln:1-4]!} @@ -78,12 +200,34 @@ And because we are using the `response_model` with `HeroRead`, it will be valida {!./docs_src/tutorial/fastapi/read_one/tutorial001.py[ln:61-67]!} ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/read_one/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/read_one/tutorial001.py!} ``` +//// + /// ## Check the Docs UI diff --git a/docs/tutorial/fastapi/relationships.md b/docs/tutorial/fastapi/relationships.md index 3275dcfa1..4087dfca1 100644 --- a/docs/tutorial/fastapi/relationships.md +++ b/docs/tutorial/fastapi/relationships.md @@ -40,9 +40,59 @@ Let's update that. 🤓 First, why is it that we are not getting the related data for each hero and for each team? -It's because we declared the `HeroRead` with only the same base fields of the `HeroBase` plus the `id`. But it doesn't include a field `team` for the **relationship attribute**. +It's because we declared the `HeroPublic` with only the same base fields of the `HeroBase` plus the `id`. But it doesn't include a field `team` for the **relationship attribute**. -And the same way, we declared the `TeamRead` with only the same base fields of the `TeamBase` plus the `id`. But it doesn't include a field `heroes` for the **relationship attribute**. +And the same way, we declared the `TeamPublic` with only the same base fields of the `TeamBase` plus the `id`. But it doesn't include a field `heroes` for the **relationship attribute**. + +//// tab | Python 3.10+ + +```Python hl_lines="3-5 9-10 14-19 23-24" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py[ln:5-7]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py[ln:20-21]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py[ln:29-34]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py[ln:43-44]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3-5 9-10 14-19 23-24" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py39.py[ln:7-9]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py39.py[ln:22-23]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py39.py[ln:31-36]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py39.py[ln:45-46]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ ```Python hl_lines="3-5 9-10 14-19 23-24" # Code above omitted 👆 @@ -64,17 +114,73 @@ And the same way, we declared the `TeamRead` with only the same base fields of t # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/teams/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/teams/tutorial001.py!} ``` +//// + /// Now, remember that FastAPI uses the `response_model` to validate and **filter** the response data? -In this case, we used `response_model=TeamRead` and `response_model=HeroRead`, so FastAPI will use them to filter the response data, even if we return a **table model** that includes **relationship attributes**: +In this case, we used `response_model=TeamPublic` and `response_model=HeroPublic`, so FastAPI will use them to filter the response data, even if we return a **table model** that includes **relationship attributes**: + +//// tab | Python 3.10+ + +```Python hl_lines="3 8 12 17" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py[ln:102-107]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py[ln:156-161]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3 8 12 17" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py39.py[ln:104-109]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py39.py[ln:158-163]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ ```Python hl_lines="3 8 12 17" # Code above omitted 👆 @@ -88,12 +194,34 @@ In this case, we used `response_model=TeamRead` and `response_model=HeroRead`, s # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/teams/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/teams/tutorial001.py!} ``` +//// + /// ## Don't Include All the Data @@ -172,10 +300,36 @@ Let's add a couple more **data models** that declare that data so we can use the ## Models with Relationships -Let's add the models `HeroReadWithTeam` and `TeamReadWithHeroes`. +Let's add the models `HeroPublicWithTeam` and `TeamPublicWithHeroes`. We'll add them **after** the other models so that we can easily reference the previous models. +//// tab | Python 3.10+ + +```Python hl_lines="3-4 7-8" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/relationships/tutorial001_py310.py[ln:59-64]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3-4 7-8" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/relationships/tutorial001_py39.py[ln:61-66]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3-4 7-8" # Code above omitted 👆 @@ -184,23 +338,45 @@ We'll add them **after** the other models so that we can easily reference the pr # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/relationships/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/relationships/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/relationships/tutorial001.py!} ``` +//// + /// These two models are very **simple in code**, but there's a lot happening here. Let's check it out. ### Inheritance and Type Annotations -The `HeroReadWithTeam` **inherits** from `HeroRead`, which means that it will have the **normal fields for reading**, including the required `id` that was declared in `HeroRead`. +The `HeroPublicWithTeam` **inherits** from `HeroPublic`, which means that it will have the **normal fields for reading**, including the required `id` that was declared in `HeroPublic`. -And then it adds the **new field** `team`, which could be `None`, and is declared with the type `TeamRead` with the base fields for reading a team. +And then it adds the **new field** `team`, which could be `None`, and is declared with the type `TeamPublic` with the base fields for reading a team. -Then we do the same for the `TeamReadWithHeroes`, it **inherits** from `TeamRead`, and declares the **new field** `heroes`, which is a list of `HeroRead`. +Then we do the same for the `TeamPublicWithHeroes`, it **inherits** from `TeamPublic`, and declares the **new field** `heroes`, which is a list of `HeroPublic`. ### Data Models Without Relationship Attributes @@ -210,11 +386,11 @@ Instead, here these are only **data models** that will tell FastAPI **which attr ### Reference to Other Models -Also, notice that the field `team` is not declared with this new `TeamReadWithHeroes`, because that would again create that infinite recursion of data. Instead, we declare it with the normal `TeamRead` model. +Also, notice that the field `team` is not declared with this new `TeamPublicWithHeroes`, because that would again create that infinite recursion of data. Instead, we declare it with the normal `TeamPublic` model. -And the same for `TeamReadWithHeroes`, the model used for the new field `heroes` uses `HeroRead` to get only each hero's data. +And the same for `TeamPublicWithHeroes`, the model used for the new field `heroes` uses `HeroPublic` to get only each hero's data. -This also means that, even though we have these two new models, **we still need the previous ones**, `HeroRead` and `TeamRead`, because we need to reference them here (and we are also using them in the rest of the *path operations*). +This also means that, even though we have these two new models, **we still need the previous ones**, `HeroPublic` and `TeamPublic`, because we need to reference them here (and we are also using them in the rest of the *path operations*). ## Update the Path Operations @@ -224,6 +400,40 @@ This will tell **FastAPI** to take the object that we return from the *path oper In the case of the hero, this tells FastAPI to extract the `team` too. And in the case of the team, to extract the list of `heroes` too. +//// tab | Python 3.10+ + +```Python hl_lines="3 8 12 17" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/relationships/tutorial001_py310.py[ln:111-116]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/relationships/tutorial001_py310.py[ln:165-170]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3 8 12 17" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/relationships/tutorial001_py39.py[ln:113-118]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/relationships/tutorial001_py39.py[ln:167-172]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3 8 12 17" # Code above omitted 👆 @@ -236,12 +446,34 @@ In the case of the hero, this tells FastAPI to extract the `team` too. And in th # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/relationships/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/relationships/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/relationships/tutorial001.py!} ``` +//// + /// ## Check It Out in the Docs UI diff --git a/docs/tutorial/fastapi/response-model.md b/docs/tutorial/fastapi/response-model.md index f6e20b335..b333d58b1 100644 --- a/docs/tutorial/fastapi/response-model.md +++ b/docs/tutorial/fastapi/response-model.md @@ -32,6 +32,32 @@ We can use `response_model` to tell FastAPI the schema of the data we want to se For example, we can pass the same `Hero` **SQLModel** class (because it is also a Pydantic model): +//// tab | Python 3.10+ + +```Python hl_lines="3" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/response_model/tutorial001_py310.py[ln:31-37]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/response_model/tutorial001_py39.py[ln:33-39]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3" # Code above omitted 👆 @@ -40,12 +66,34 @@ For example, we can pass the same `Hero` **SQLModel** class (because it is also # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/response_model/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/response_model/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/response_model/tutorial001.py!} ``` +//// + /// ## List of Heroes in `response_model` @@ -54,6 +102,34 @@ We can also use other type annotations, the same way we can use with Pydantic fi First, we import `List` from `typing` and then we declare the `response_model` with `List[Hero]`: +//// tab | Python 3.10+ + +```Python hl_lines="3" + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/response_model/tutorial001_py310.py[ln:40-44]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3" + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/response_model/tutorial001_py39.py[ln:42-46]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="1 5" {!./docs_src/tutorial/fastapi/response_model/tutorial001.py[ln:1]!} @@ -64,12 +140,34 @@ First, we import `List` from `typing` and then we declare the `response_model` w # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/response_model/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/response_model/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/response_model/tutorial001.py!} ``` +//// + /// ## FastAPI and Response Model diff --git a/docs/tutorial/fastapi/session-with-dependency.md b/docs/tutorial/fastapi/session-with-dependency.md index ce2421fa8..e148452dc 100644 --- a/docs/tutorial/fastapi/session-with-dependency.md +++ b/docs/tutorial/fastapi/session-with-dependency.md @@ -6,6 +6,32 @@ Before we keep adding things, let's change a bit how we get the session for each Up to now, we have been creating a session in each *path operation*, in a `with` block. +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/delete/tutorial001_py310.py[ln:48-55]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/delete/tutorial001_py39.py[ln:50-57]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -14,12 +40,34 @@ Up to now, we have been creating a session in each *path operation*, in a `with` # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/delete/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/delete/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/delete/tutorial001.py!} ``` +//// + /// That's perfectly fine, but in many use cases we would want to use FastAPI Dependencies, for example to **verify** that the client is **logged in** and get the **current user** before executing any other code in the *path operation*. @@ -34,6 +82,32 @@ A **FastAPI** dependency is very simple, it's just a function that returns a val It could use `yield` instead of `return`, and in that case **FastAPI** will make sure it executes all the code **after** the `yield`, once it is done with the request. +//// tab | Python 3.10+ + +```Python hl_lines="3-5" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py[ln:40-42]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3-5" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py[ln:42-44]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3-5" # Code above omitted 👆 @@ -42,12 +116,34 @@ It could use `yield` instead of `return`, and in that case **FastAPI** will make # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py!} ``` +//// + /// ## Use the Dependency @@ -56,6 +152,44 @@ Now let's make FastAPI execute a dependency and get its value in the *path opera We import `Depends()` from `fastapi`. Then we use it in the *path operation function* in a **parameter**, the same way we declared parameters to get JSON bodies, path parameters, etc. +//// tab | Python 3.10+ + +```Python hl_lines="1 13" +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py[ln:1-2]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py[ln:40-42]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py[ln:53-59]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3 15" +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py[ln:1-4]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py[ln:42-44]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py[ln:55-61]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3 15" {!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:1-4]!} @@ -70,12 +204,34 @@ We import `Depends()` from `fastapi`. Then we use it in the *path operation func # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py!} ``` +//// + /// /// tip @@ -104,6 +260,44 @@ And because dependencies can use `yield`, FastAPI will make sure to run the code This means that in the main code of the *path operation function*, it will work equivalently to the previous version with the explicit `with` block. +//// tab | Python 3.10+ + +```Python hl_lines="14-18" +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py[ln:1-2]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py[ln:40-42]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py[ln:53-59]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="16-20" +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py[ln:1-4]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py[ln:42-44]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py[ln:55-61]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="16-20" {!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:1-4]!} @@ -118,18 +312,78 @@ This means that in the main code of the *path operation function*, it will work # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py!} ``` +//// + /// In fact, you could think that all that block of code inside of the `create_hero()` function is still inside a `with` block for the **session**, because this is more or less what's happening behind the scenes. But now, the `with` block is not explicitly in the function, but in the dependency above: +//// tab | Python 3.10+ + +```Python hl_lines="7-8" +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py[ln:1-2]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py[ln:40-42]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py[ln:53-59]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="9-10" +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py[ln:1-4]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py[ln:42-44]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py[ln:55-61]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="9-10" {!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:1-4]!} @@ -144,12 +398,34 @@ But now, the `with` block is not explicitly in the function, but in the dependen # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py!} ``` +//// + /// We will see how this is very useful when testing the code later. ✅ @@ -166,6 +442,40 @@ session: Session = Depends(get_session) And then we remove the previous `with` block with the old **session**. +//// tab | Python 3.10+ + +```Python hl_lines="13 24 33 42 57" +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py[ln:1-2]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py[ln:40-42]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py[ln:53-104]!} +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="15 26 35 44 59" +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py[ln:1-4]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py[ln:42-44]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py[ln:55-106]!} +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="15 26 35 44 59" {!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:1-4]!} @@ -178,12 +488,34 @@ And then we remove the previous `with` block with the old **session**. {!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:55-106]!} ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py!} ``` +//// + /// ## Recap diff --git a/docs/tutorial/fastapi/simple-hero-api.md b/docs/tutorial/fastapi/simple-hero-api.md index 2ef8b436d..0a9611895 100644 --- a/docs/tutorial/fastapi/simple-hero-api.md +++ b/docs/tutorial/fastapi/simple-hero-api.md @@ -32,6 +32,22 @@ We will start with the **simplest version**, with just heroes (no teams yet). This is almost the same code we have seen up to now in previous examples: +//// tab | Python 3.10+ + +```Python hl_lines="18-19" + +# One line of FastAPI imports here later 👈 +{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py[ln:2]!} + +{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py[ln:5-20]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="20-21" {!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py[ln:1]!} @@ -43,12 +59,26 @@ This is almost the same code we have seen up to now in previous examples: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py!} ``` +//// + /// There's only one change here from the code we have used before, the `check_same_thread` in the `connect_args`. @@ -77,6 +107,22 @@ We will import the `FastAPI` class from `fastapi`. And then create an `app` object that is an instance of that `FastAPI` class: +//// tab | Python 3.10+ + +```Python hl_lines="1 6" +{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py[ln:1-2]!} + +# SQLModel code here omitted 👈 + +{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py[ln:23]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3 8" {!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py[ln:1-4]!} @@ -87,12 +133,26 @@ And then create an `app` object that is an instance of that `FastAPI` class: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py!} ``` +//// + /// ## Create Database and Tables on `startup` @@ -101,6 +161,20 @@ We want to make sure that once the app starts running, the function `create_tabl This should be called only once at startup, not before every request, so we put it in the function to handle the `"startup"` event: +//// tab | Python 3.10+ + +```Python hl_lines="6-8" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py[ln:23-28]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="6-8" # Code above omitted 👆 @@ -109,12 +183,26 @@ This should be called only once at startup, not before every request, so we put # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py!} ``` +//// + /// ## Create Heroes *Path Operation* @@ -129,6 +217,20 @@ Let's create the **path operation** code to create a new hero. It will be called when a user sends a request with a `POST` **operation** to the `/heroes/` **path**: +//// tab | Python 3.10+ + +```Python hl_lines="11-12" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py[ln:23-37]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="11-12" # Code above omitted 👆 @@ -137,12 +239,26 @@ It will be called when a user sends a request with a `POST` **operation** to the # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py!} ``` +//// + /// /// info @@ -177,18 +293,44 @@ We will improve this further later, but for now, it already shows the power of h Now let's add another **path operation** to read all the heroes: +//// tab | Python 3.10+ + +```Python hl_lines="20-24" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py[ln:23-44]!} +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="20-24" # Code above omitted 👆 {!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py[ln:25-46]!} ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py!} ``` +//// + /// This is pretty straightforward. diff --git a/docs/tutorial/fastapi/teams.md b/docs/tutorial/fastapi/teams.md index 0180b9476..9e366f2cc 100644 --- a/docs/tutorial/fastapi/teams.md +++ b/docs/tutorial/fastapi/teams.md @@ -14,22 +14,66 @@ It's the same process we did for heroes, with a base model, a **table model**, a We have a `TeamBase` **data model**, and from it, we inherit with a `Team` **table model**. -Then we also inherit from the `TeamBase` for the `TeamCreate` and `TeamRead` **data models**. +Then we also inherit from the `TeamBase` for the `TeamCreate` and `TeamPublic` **data models**. And we also create a `TeamUpdate` **data model**. +//// tab | Python 3.10+ + +```Python hl_lines="5-7 10-13 16-17 20-21 24-26" +{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py[ln:1-26]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="7-9 12-15 18-19 22-23 26-28" +{!./docs_src/tutorial/fastapi/teams/tutorial001_py39.py[ln:1-28]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="7-9 12-15 18-19 22-23 26-28" {!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:1-28]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/teams/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/teams/tutorial001.py!} ``` +//// + /// We now also have **relationship attributes**. 🎉 @@ -38,7 +82,33 @@ Let's now update the `Hero` models too. ## Update Hero Models -```Python hl_lines="3-8 11-15 17-18 21-22 25-29" +//// tab | Python 3.10+ + +```Python hl_lines="3-8 11-14 17-18 21-22 25-29" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py[ln:29-55]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3-8 11-14 17-18 21-22 25-29" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py39.py[ln:31-57]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + +```Python hl_lines="3-8 11-14 17-18 21-22 25-29" # Code above omitted 👆 {!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:31-57]!} @@ -46,12 +116,34 @@ Let's now update the `Hero` models too. # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/teams/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/teams/tutorial001.py!} ``` +//// + /// We now have a `team_id` in the hero models. @@ -64,6 +156,32 @@ And even though the `HeroBase` is *not* a **table model**, we can declare `team_ Notice that the **relationship attributes**, the ones with `Relationship()`, are **only** in the **table models**, as those are the ones that are handled by **SQLModel** with SQLAlchemy and that can have the automatic fetching of data from the database when we access them. +//// tab | Python 3.10+ + +```Python hl_lines="11 38" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py[ln:5-55]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="11 38" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py39.py[ln:7-57]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="11 38" # Code above omitted 👆 @@ -72,12 +190,34 @@ Notice that the **relationship attributes**, the ones with `Relationship()`, are # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/teams/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/teams/tutorial001.py!} ``` +//// + /// ## Path Operations for Teams @@ -86,6 +226,32 @@ Let's now add the **path operations** for teams. These are equivalent and very similar to the **path operations** for the **heroes** we had before, so we don't have to go over the details for each one, let's check the code. +//// tab | Python 3.10+ + +```Python hl_lines="3-9 12-20 23-28 31-47 50-57" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py[ln:136-190]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3-9 12-20 23-28 31-47 50-57" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/teams/tutorial001_py39.py[ln:138-192]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3-9 12-20 23-28 31-47 50-57" # Code above omitted 👆 @@ -94,12 +260,34 @@ These are equivalent and very similar to the **path operations** for the **heroe # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/teams/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/teams/tutorial001.py!} ``` +//// + /// ## Using Relationships Attributes diff --git a/docs/tutorial/fastapi/update-extra-data.md b/docs/tutorial/fastapi/update-extra-data.md new file mode 100644 index 000000000..6bbd72ad3 --- /dev/null +++ b/docs/tutorial/fastapi/update-extra-data.md @@ -0,0 +1,465 @@ +# Update with Extra Data (Hashed Passwords) with FastAPI + +In the previous chapter I explained to you how to update data in the database from input data coming from a **FastAPI** *path operation*. + +Now I'll explain to you how to add **extra data**, additional to the input data, when updating or creating a model object. + +This is particularly useful when you need to **generate some data** in your code that is **not coming from the client**, but you need to store it in the database. For example, to store a **hashed password**. + +## Password Hashing + +Let's imagine that each hero in our system also has a **password**. + +We should never store the password in plain text in the database, we should only stored a **hashed version** of it. + +"**Hashing**" means converting some content (a password in this case) into a sequence of bytes (just a string) that looks like gibberish. + +Whenever you pass exactly the same content (exactly the same password) you get exactly the same gibberish. + +But you **cannot convert** from the gibberish **back to the password**. + +### Why use Password Hashing + +If your database is stolen, the thief won't have your users' **plaintext passwords**, only the hashes. + +So, the thief won't be able to try to use that password in another system (as many users use the same password everywhere, this would be dangerous). + +/// tip + +You could use passlib to hash passwords. + +In this example we will use a fake hashing function to focus on the data changes. 🤡 + +/// + +## Update Models with Extra Data + +The `Hero` table model will now store a new field `hashed_password`. + +And the data models for `HeroCreate` and `HeroUpdate` will also have a new field `password` that will contain the plain text password sent by clients. + +//// tab | Python 3.10+ + +```Python hl_lines="11 15 26" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial002_py310.py[ln:5-28]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="11 15 26" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial002_py39.py[ln:7-30]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + +```Python hl_lines="11 15 26" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial002.py[ln:7-30]!} + +# Code below omitted 👇 +``` + +//// + +/// details | 👀 Full file preview + +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial002.py!} +``` + +//// + +/// + +When a client is creating a new hero, they will send the `password` in the request body. + +And when they are updating a hero, they could also send the `password` in the request body to update it. + +## Hash the Password + +The app will receive the data from the client using the `HeroCreate` model. + +This contains the `password` field with the plain text password, and we cannot use that one. So we need to generate a hash from it. + +//// tab | Python 3.10+ + +```Python hl_lines="11" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial002_py310.py[ln:42-44]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/update/tutorial002_py310.py[ln:55-57]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="11" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial002_py39.py[ln:44-46]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/update/tutorial002_py39.py[ln:57-59]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + +```Python hl_lines="11" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial002.py[ln:44-46]!} + +# Code here omitted 👈 + +{!./docs_src/tutorial/fastapi/update/tutorial002.py[ln:57-59]!} + +# Code below omitted 👇 +``` + +//// + +/// details | 👀 Full file preview + +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial002.py!} +``` + +//// + +/// + +## Create an Object with Extra Data + +Now we need to create the database hero. + +In previous examples, we have used something like: + +```Python +db_hero = Hero.model_validate(hero) +``` + +This creates a `Hero` (which is a *table model*) object from the `HeroCreate` (which is a *data model*) object that we received in the request. + +And this is all good... but as `Hero` doesn't have a field `password`, it won't be extracted from the object `HeroCreate` that has it. + +`Hero` actually has a `hashed_password`, but we are not providing it. We need a way to provide it... + +### Dictionary Update + +Let's pause for a second to check this, when working with dictionaries, there's a way to `update` a dictionary with extra data from another dictionary, something like this: + +```Python hl_lines="14" +db_user_dict = { + "name": "Deadpond", + "secret_name": "Dive Wilson", + "age": None, +} + +hashed_password = "fakehashedpassword" + +extra_data = { + "hashed_password": hashed_password, + "age": 32, +} + +db_user_dict.update(extra_data) + +print(db_user_dict) + +# { +# "name": "Deadpond", +# "secret_name": "Dive Wilson", +# "age": 32, +# "hashed_password": "fakehashedpassword", +# } +``` + +This `update` method allows us to add and override things in the original dictionary with the data from another dictionary. + +So now, `db_user_dict` has the updated `age` field with `32` instead of `None` and more importantly, **it has the new `hashed_password` field**. + +### Create a Model Object with Extra Data + +Similar to how dictionaries have an `update` method, **SQLModel** models have a parameter `update` in `Hero.model_validate()` that takes a dictionary with extra data, or data that should take precedence: + +//// tab | Python 3.10+ + +```Python hl_lines="8" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial002_py310.py[ln:55-64]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="8" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial002_py39.py[ln:57-66]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + +```Python hl_lines="8" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial002.py[ln:57-66]!} + +# Code below omitted 👇 +``` + +//// + +/// details | 👀 Full file preview + +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial002.py!} +``` + +//// + +/// + +Now, `db_hero` (which is a *table model* `Hero`) will extract its values from `hero` (which is a *data model* `HeroCreate`), and then it will **`update`** its values with the extra data from the dictionary `extra_data`. + +It will only take the fields defined in `Hero`, so **it will not take the `password`** from `HeroCreate`. And it will also **take its values** from the **dictionary passed to the `update`** parameter, in this case, the `hashed_password`. + +If there's a field in both `hero` and the `extra_data`, **the value from the `extra_data` passed to `update` will take precedence**. + +## Update with Extra Data + +Now let's say we want to **update a hero** that already exists in the database. + +The same way as before, to avoid removing existing data, we will use `exclude_unset=True` when calling `hero.model_dump()`, to get a dictionary with only the data sent by the client. + +//// tab | Python 3.10+ + +```Python hl_lines="9" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial002_py310.py[ln:83-89]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="9" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial002_py39.py[ln:85-91]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + +```Python hl_lines="9" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial002.py[ln:85-91]!} + +# Code below omitted 👇 +``` + +//// + +/// details | 👀 Full file preview + +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial002.py!} +``` + +//// + +/// + +Now, this `hero_data` dictionary could contain a `password`. We need to check it, and if it's there, we need to generate the `hashed_password`. + +Then we can put that `hashed_password` in a dictionary. + +And then we can update the `db_hero` object using the method `db_hero.sqlmodel_update()`. + +It takes a model object or dictionary with the data to update the object and also an **additional `update` argument** with extra data. + +//// tab | Python 3.10+ + +```Python hl_lines="15" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial002_py310.py[ln:83-99]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="15" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial002_py39.py[ln:85-101]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + +```Python hl_lines="15" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial002.py[ln:85-101]!} + +# Code below omitted 👇 +``` + +//// + +/// details | 👀 Full file preview + +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial002.py!} +``` + +//// + +/// + +/// tip + +The method `db_hero.sqlmodel_update()` was added in SQLModel 0.0.16. 😎 + +/// + +## Recap + +You can use the `update` parameter in `Hero.model_validate()` to provide extra data when creating a new object and `Hero.sqlmodel_update()` to provide extra data when updating an existing object. 🤓 diff --git a/docs/tutorial/fastapi/update.md b/docs/tutorial/fastapi/update.md index cfcf8a98e..6b2411bea 100644 --- a/docs/tutorial/fastapi/update.md +++ b/docs/tutorial/fastapi/update.md @@ -22,6 +22,32 @@ Because each field is **actually different** (we just change it to `Optional`, b So, let's create this new `HeroUpdate` model: +//// tab | Python 3.10+ + +```Python hl_lines="21-24" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial001_py310.py[ln:5-26]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="21-24" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial001_py39.py[ln:7-28]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="21-24" # Code above omitted 👆 @@ -30,12 +56,34 @@ So, let's create this new `HeroUpdate` model: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/update/tutorial001.py!} ``` +//// + /// This is almost the same as `HeroBase`, but all the fields are optional, so we can't simply inherit from `HeroBase`. @@ -46,6 +94,32 @@ Now let's use this model in the *path operation* to update a hero. We will use a `PATCH` HTTP operation. This is used to **partially update data**, which is what we are doing. +//// tab | Python 3.10+ + +```Python hl_lines="3-4" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial001_py310.py[ln:74-89]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3-4" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial001_py39.py[ln:76-91]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3-4" # Code above omitted 👆 @@ -54,12 +128,34 @@ We will use a `PATCH` HTTP operation. This is used to **partially update data**, # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/update/tutorial001.py!} ``` +//// + /// We also read the `hero_id` from the *path parameter* and the request body, a `HeroUpdate`. @@ -70,6 +166,32 @@ We take a `hero_id` with the **ID** of the hero **we want to update**. So, we need to read the hero from the database, with the **same logic** we used to **read a single hero**, checking if it exists, possibly raising an error for the client if it doesn't exist, etc. +//// tab | Python 3.10+ + +```Python hl_lines="6-8" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial001_py310.py[ln:74-89]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="6-8" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial001_py39.py[ln:76-91]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="6-8" # Code above omitted 👆 @@ -78,12 +200,34 @@ So, we need to read the hero from the database, with the **same logic** we used # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/update/tutorial001.py!} ``` +//// + /// ### Get the New Data @@ -136,6 +280,32 @@ Then the dictionary we would get in Python using `hero.model_dump(exclude_unset= Then we use that to get the data that was actually sent by the client: +//// tab | Python 3.10+ + +```Python hl_lines="9" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial001_py310.py[ln:74-89]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="9" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial001_py39.py[ln:76-91]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="9" # Code above omitted 👆 @@ -144,22 +314,71 @@ Then we use that to get the data that was actually sent by the client: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/fastapi/update/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/fastapi/update/tutorial001.py!} ``` +//// + /// /// tip Before SQLModel 0.0.14, the method was called `hero.dict(exclude_unset=True)`, but it was renamed to `hero.model_dump(exclude_unset=True)` to be consistent with Pydantic v2. +/// ## Update the Hero in the Database -Now that we have a **dictionary with the data sent by the client**, we can iterate for each one of the keys and the values, and then we set them in the database hero model `db_hero` using `setattr()`. +Now that we have a **dictionary with the data sent by the client**, we can use the method `db_hero.sqlmodel_update()` to update the object `db_hero`. -```Python hl_lines="10-11" +//// tab | Python 3.10+ + +```Python hl_lines="10" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial001_py310.py[ln:74-89]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="10" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/update/tutorial001_py39.py[ln:76-91]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + +```Python hl_lines="10" # Code above omitted 👆 {!./docs_src/tutorial/fastapi/update/tutorial001.py[ln:76-91]!} @@ -167,28 +386,48 @@ Now that we have a **dictionary with the data sent by the client**, we can itera # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + ```Python -{!./docs_src/tutorial/fastapi/update/tutorial001.py!} +{!./docs_src/tutorial/fastapi/update/tutorial001_py310.py!} ``` -/// - -If you are not familiar with that `setattr()`, it takes an object, like the `db_hero`, then an attribute name (`key`), that in our case could be `"name"`, and a value (`value`). And then it **sets the attribute with that name to the value**. +//// -So, if `key` was `"name"` and `value` was `"Deadpuddle"`, then this code: +//// tab | Python 3.9+ ```Python -setattr(db_hero, key, value) +{!./docs_src/tutorial/fastapi/update/tutorial001_py39.py!} ``` -...would be more or less equivalent to: +//// + +//// tab | Python 3.7+ ```Python -db_hero.name = "Deadpuddle" +{!./docs_src/tutorial/fastapi/update/tutorial001.py!} ``` +//// + +/// + +/// tip + +The method `db_hero.sqlmodel_update()` was added in SQLModel 0.0.16. 🤓 + +Before that, you would need to manually get the values and set them using `setattr()`. + +/// + +The method `db_hero.sqlmodel_update()` takes an argument with another model object or a dictionary. + +For each of the fields in the **original** model object (`db_hero` in this example), it checks if the field is available in the **argument** (`hero_data` in this example) and then updates it with the provided value. + ## Remove Fields Here's a bonus. 🎁 diff --git a/docs/tutorial/indexes.md b/docs/tutorial/indexes.md index a7c6028e8..d0854720c 100644 --- a/docs/tutorial/indexes.md +++ b/docs/tutorial/indexes.md @@ -22,10 +22,22 @@ Fine, in that case, you can **sneak peek** the final code to create indexes here /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python hl_lines="8 10" +{!./docs_src/tutorial/indexes/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="8 10" {!./docs_src/tutorial/indexes/tutorial002.py!} ``` +//// + /// ..but if you are not an expert, **continue reading**, this will probably be useful. 🤓 @@ -263,34 +275,86 @@ The change in code is underwhelming, it's very simple. 😆 Here's the `Hero` model we had before: +//// tab | Python 3.10+ + +```Python hl_lines="6" +{!./docs_src/tutorial/where/tutorial001_py310.py[ln:1-8]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="8" {!./docs_src/tutorial/where/tutorial001.py[ln:1-10]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/where/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/where/tutorial001.py!} ``` +//// + /// Let's now update it to tell **SQLModel** to create an index for the `name` field when creating the table: +//// tab | Python 3.10+ + +```Python hl_lines="6" +{!./docs_src/tutorial/indexes/tutorial001_py310.py[ln:1-8]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="8" {!./docs_src/tutorial/indexes/tutorial001.py[ln:1-10]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/indexes/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/indexes/tutorial001.py!} ``` +//// + /// We use the same `Field()` again as we did before, and set `index=True`. That's it! 🚀 @@ -313,6 +377,20 @@ The SQL database will figure it out **automatically**. ✨ This is great because it means that indexes are very **simple to use**. But it might also feel counterintuitive at first, as you are **not doing anything** explicitly in the code to make it obvious that the index is useful, it all happens in the database behind the scenes. +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/indexes/tutorial001_py310.py[ln:34-39]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -321,12 +399,26 @@ This is great because it means that indexes are very **simple to use**. But it m # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/indexes/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/indexes/tutorial001.py!} ``` +//// + /// This is exactly the same code as we had before, but now the database will **use the index** underneath. @@ -370,18 +462,44 @@ secret_name='Dive Wilson' age=None id=1 name='Deadpond' We are going to query the `hero` table doing comparisons on the `age` field too, so we should **define an index** for that one as well: +//// tab | Python 3.10+ + +```Python hl_lines="8" +{!./docs_src/tutorial/indexes/tutorial002_py310.py[ln:1-8]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="10" {!./docs_src/tutorial/indexes/tutorial002.py[ln:1-10]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/indexes/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/indexes/tutorial002.py!} ``` +//// + /// In this case, we want the default value of `age` to continue being `None`, so we set `default=None` when using `Field()`. diff --git a/docs/tutorial/insert.md b/docs/tutorial/insert.md index 9b01db339..9439cc377 100644 --- a/docs/tutorial/insert.md +++ b/docs/tutorial/insert.md @@ -25,6 +25,22 @@ We will continue from where we left of in the last chapter. This is the code we had to create the database and table, nothing new here: +//// tab | Python 3.10+ + +```{.python .annotate hl_lines="20" } +{!./docs_src/tutorial/create_db_and_table/tutorial003_py310.py[ln:1-18]!} + +# More code here later 👈 + +{!./docs_src/tutorial/create_db_and_table/tutorial003_py310.py[ln:21-22]!} +``` + +{!./docs_src/tutorial/create_db_and_table/annotations/en/tutorial003.md!} + +//// + +//// tab | Python 3.7+ + ```{.python .annotate hl_lines="22" } {!./docs_src/tutorial/create_db_and_table/tutorial003.py[ln:1-20]!} @@ -35,6 +51,8 @@ This is the code we had to create the database and table, nothing new here: {!./docs_src/tutorial/create_db_and_table/annotations/en/tutorial003.md!} +//// + Now that we can create the database and the table, we will continue from this point and add more code on the same file to create the data. ## Create Data with SQL @@ -127,6 +145,20 @@ So, the first step is to simply create an instance of `Hero`. We'll create 3 right away, for the 3 heroes: +//// tab | Python 3.10+ + +```Python +# Code above omitted 👆 + +{!./docs_src/tutorial/insert/tutorial002_py310.py[ln:21-24]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python # Code above omitted 👆 @@ -135,12 +167,26 @@ We'll create 3 right away, for the 3 heroes: # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/insert/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/insert/tutorial002.py!} ``` +//// + /// /// tip @@ -173,22 +219,62 @@ We would re-use the same **engine** in all the code, everywhere in the applicati The first step is to import the `Session` class: +//// tab | Python 3.10+ + +```Python hl_lines="1" +{!./docs_src/tutorial/insert/tutorial001_py310.py[ln:1]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3" {!./docs_src/tutorial/insert/tutorial001.py[ln:1-3]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/insert/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/insert/tutorial001.py!} ``` +//// + /// Then we can create a new session: +//// tab | Python 3.10+ + +```Python hl_lines="8" +# Code above omitted 👆 + +{!./docs_src/tutorial/insert/tutorial001_py310.py[ln:21-26]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="8" # Code above omitted 👆 @@ -197,12 +283,26 @@ Then we can create a new session: # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/insert/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/insert/tutorial001.py!} ``` +//// + /// The new `Session` takes an `engine` as a parameter. And it will use the **engine** underneath. @@ -217,6 +317,19 @@ We will see a better way to create a **session** using a `with` block later. Now that we have some hero model instances (some objects in memory) and a **session**, the next step is to add them to the session: +//// tab | Python 3.10+ + +```Python hl_lines="9-11" +# Code above omitted 👆 +{!./docs_src/tutorial/insert/tutorial001_py310.py[ln:21-30]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="9-11" # Code above omitted 👆 {!./docs_src/tutorial/insert/tutorial001.py[ln:23-32]!} @@ -224,12 +337,26 @@ Now that we have some hero model instances (some objects in memory) and a **sess # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/insert/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/insert/tutorial001.py!} ``` +//// + /// By this point, our heroes are *not* stored in the database yet. @@ -254,6 +381,19 @@ This ensures that the data is saved in a single batch, and that it will all succ Now that we have the heroes in the **session** and that we are ready to save all that to the database, we can **commit** the changes: +//// tab | Python 3.10+ + +```Python hl_lines="13" +# Code above omitted 👆 +{!./docs_src/tutorial/insert/tutorial001_py310.py[ln:21-32]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="13" # Code above omitted 👆 {!./docs_src/tutorial/insert/tutorial001.py[ln:23-34]!} @@ -261,12 +401,26 @@ Now that we have the heroes in the **session** and that we are ready to save all # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/insert/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/insert/tutorial001.py!} ``` +//// + /// Once this line is executed, the **session** will use the **engine** to save all the data in the database by sending the corresponding SQL. @@ -294,6 +448,19 @@ if __name__ == "__main__": But to keep things a bit more organized, let's instead create a new function `main()` that will contain all the code that should be executed when called as an independent script, and we can put there the previous function `create_db_and_tables()`, and add the new function `create_heroes()`: +//// tab | Python 3.10+ + +```Python hl_lines="2 4" +# Code above omitted 👆 +{!./docs_src/tutorial/insert/tutorial002_py310.py[ln:34-36]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="2 4" # Code above omitted 👆 {!./docs_src/tutorial/insert/tutorial002.py[ln:36-38]!} @@ -301,27 +468,66 @@ But to keep things a bit more organized, let's instead create a new function `ma # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/insert/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/insert/tutorial002.py!} ``` +//// + /// And then we can call that single `main()` function from that main block: +//// tab | Python 3.10+ + +```Python hl_lines="8" +# Code above omitted 👆 +{!./docs_src/tutorial/insert/tutorial002_py310.py[ln:34-40]!} +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="8" # Code above omitted 👆 {!./docs_src/tutorial/insert/tutorial002.py[ln:36-42]!} ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/insert/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/insert/tutorial002.py!} ``` +//// + /// By having everything that should happen when called as a script in a single function, we can easily add more code later on. @@ -377,6 +583,20 @@ The **session** holds some resources, like connections from the engine. So once we are done with the session, we should **close** it to make it release those resources and finish its cleanup: +//// tab | Python 3.10+ + +```Python hl_lines="16" +# Code above omitted 👆 + +{!./docs_src/tutorial/insert/tutorial001_py310.py[ln:21-34]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="16" # Code above omitted 👆 @@ -385,12 +605,26 @@ So once we are done with the session, we should **close** it to make it release # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/insert/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/insert/tutorial001.py!} ``` +//// + /// But what happens if we forget to close the session? @@ -405,17 +639,42 @@ It's good to know how the `Session` works and how to create and close it manuall But there's a better way to handle the session, using a `with` block: +//// tab | Python 3.10+ + +```Python hl_lines="7-12" +# Code above omitted 👆 +{!./docs_src/tutorial/insert/tutorial002_py310.py[ln:21-31]!} +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="7-12" # Code above omitted 👆 {!./docs_src/tutorial/insert/tutorial002.py[ln:23-33]!} ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/insert/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/insert/tutorial002.py!} ``` +//// + /// This is the same as creating the session manually and then manually closing it. But here, using a `with` block, it will be automatically created when **starting** the `with` block and assigned to the variable `session`, and it will be automatically closed after the `with` block is **finished**. @@ -430,12 +689,26 @@ You already know all the first part creating the `Hero` model class, the **engin Let's focus on the new code: +//// tab | Python 3.10+ + +```{.python .annotate } +{!./docs_src/tutorial/insert/tutorial003_py310.py!} +``` + +{!./docs_src/tutorial/insert/annotations/en/tutorial003.md!} + +//// + +//// tab | Python 3.7+ + ```{.python .annotate } {!./docs_src/tutorial/insert/tutorial003.py!} ``` {!./docs_src/tutorial/insert/annotations/en/tutorial003.md!} +//// + /// tip Review what each line does by clicking each number bubble in the code. 👆 diff --git a/docs/tutorial/limit-and-offset.md b/docs/tutorial/limit-and-offset.md index b3fd1514a..aa0d659a3 100644 --- a/docs/tutorial/limit-and-offset.md +++ b/docs/tutorial/limit-and-offset.md @@ -14,6 +14,20 @@ We will continue with the same code as before, but we'll modify it a little the Again, we will create several heroes to have some data to select from: +//// tab | Python 3.10+ + +```Python hl_lines="4-10" +# Code above omitted 👆 + +{!./docs_src/tutorial/offset_and_limit/tutorial001_py310.py[ln:21-39]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="4-10" # Code above omitted 👆 @@ -22,18 +36,46 @@ Again, we will create several heroes to have some data to select from: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/offset_and_limit/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/offset_and_limit/tutorial001.py!} ``` +//// + /// ## Review Select All This is the code we had to select all the heroes in the `select()` examples: +//// tab | Python 3.10+ + +```Python hl_lines="3-8" +# Code above omitted 👆 + +{!./docs_src/tutorial/select/tutorial003_py310.py[ln:34-39]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3-8" # Code above omitted 👆 @@ -42,12 +84,26 @@ This is the code we had to select all the heroes in the `select()` examples: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/select/tutorial003_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/select/tutorial003.py!} ``` +//// + /// But this would get us **all** the heroes at the same time, in a database that could have thousands, that could be problematic. @@ -56,6 +112,20 @@ But this would get us **all** the heroes at the same time, in a database that co We currently have 7 heroes in the database. But we could as well have thousands, so let's limit the results to get only the first 3: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/offset_and_limit/tutorial001_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -64,12 +134,26 @@ We currently have 7 heroes in the database. But we could as well have thousands, # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/offset_and_limit/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/offset_and_limit/tutorial001.py!} ``` +//// + /// The special **select** object we get from `select()` also has a method `.limit()` that we can use to limit the results to a certain number. @@ -133,6 +217,20 @@ How do we get the next 3? We can use `.offset()`: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/offset_and_limit/tutorial002_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -141,12 +239,26 @@ We can use `.offset()`: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/offset_and_limit/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/offset_and_limit/tutorial002.py!} ``` +//// + /// The way this works is that the special **select** object we get from `select()` has methods like `.where()`, `.offset()` and `.limit()`. @@ -186,6 +298,20 @@ INFO Engine [no key 0.00020s] (3, 3) Then to get the next batch of 3 rows we would offset all the ones we already saw, the first 6: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/offset_and_limit/tutorial003_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -194,12 +320,26 @@ Then to get the next batch of 3 rows we would offset all the ones we already saw # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/offset_and_limit/tutorial003_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/offset_and_limit/tutorial003.py!} ``` +//// + /// The database right now has **only 7 rows**, so this query can only get 1 row. @@ -255,6 +395,20 @@ If you try that in **DB Browser for SQLite**, you will get the same result: Of course, you can also combine `.limit()` and `.offset()` with `.where()` and other methods you will learn about later: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/offset_and_limit/tutorial004_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -263,12 +417,26 @@ Of course, you can also combine `.limit()` and `.offset()` with `.where()` and o # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/offset_and_limit/tutorial004_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/offset_and_limit/tutorial004.py!} ``` +//// + /// ## Run the Program with Limit, Offset, and Where on the Command Line diff --git a/docs/tutorial/many-to-many/create-data.md b/docs/tutorial/many-to-many/create-data.md index 1925316a0..bcd876299 100644 --- a/docs/tutorial/many-to-many/create-data.md +++ b/docs/tutorial/many-to-many/create-data.md @@ -10,16 +10,62 @@ We'll continue from where we left off with the previous code. /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial001.py!} ``` +//// + /// ## Create Heroes As we have done before, we'll create a function `create_heroes()` and we'll create some teams and heroes in it: +//// tab | Python 3.10+ + +```Python hl_lines="11" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py[ln:36-54]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="11" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py[ln:42-60]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="11" # Code above omitted 👆 @@ -28,12 +74,34 @@ As we have done before, we'll create a function `create_heroes()` and we'll crea # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial001.py!} ``` +//// + /// This is very similar to what we have done before. @@ -48,6 +116,32 @@ See how **Deadpond** now belongs to the two teams? Now let's do as we have done before, `commit` the **session**, `refresh` the data, and print it: +//// tab | Python 3.10+ + +```Python hl_lines="22-25 27-29 31-36" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py[ln:36-69]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="22-25 27-29 31-36" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py[ln:42-75]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="22-25 27-29 31-36" # Code above omitted 👆 @@ -56,19 +150,67 @@ Now let's do as we have done before, `commit` the **session**, `refresh` the dat # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial001.py!} ``` +//// + /// ## Add to Main As before, add the `create_heroes()` function to the `main()` function to make sure it is called when running this program from the command line: -```Python hl_lines="22-25 27-29 31-36" +//// tab | Python 3.10+ + +```Python +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py[ln:72-74]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py[ln:78-80]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + +```Python # Code above omitted 👆 {!./docs_src/tutorial/many_to_many/tutorial001.py[ln:78-80]!} @@ -76,12 +218,34 @@ As before, add the `create_heroes()` function to the `main()` function to make s # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial001.py!} ``` +//// + /// ## Run the Program diff --git a/docs/tutorial/many-to-many/create-models-with-link.md b/docs/tutorial/many-to-many/create-models-with-link.md index 8ad06d84b..308c678c3 100644 --- a/docs/tutorial/many-to-many/create-models-with-link.md +++ b/docs/tutorial/many-to-many/create-models-with-link.md @@ -12,18 +12,62 @@ As we want to support a **many-to-many** relationship, now we need a **link tabl We can create it just as any other **SQLModel**: +//// tab | Python 3.10+ + +```Python hl_lines="4-6" +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py[ln:1-6]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="6-12" +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py[ln:1-12]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="6-12" {!./docs_src/tutorial/many_to_many/tutorial001.py[ln:1-12]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial001.py!} ``` +//// + /// This is a **SQLModel** class model table like any other. @@ -38,6 +82,32 @@ And **both fields are primary keys**. We hadn't used this before. 🤓 Let's see the `Team` model, it's almost identical as before, but with a little change: +//// tab | Python 3.10+ + +```Python hl_lines="8" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py[ln:9-14]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="8" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py[ln:15-20]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="8" # Code above omitted 👆 @@ -46,12 +116,34 @@ Let's see the `Team` model, it's almost identical as before, but with a little c # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial001.py!} ``` +//// + /// The **relationship attribute `heroes`** is still a list of heroes, annotated as `List["Hero"]`. Again, we use `"Hero"` in quotes because we haven't declared that class yet by this point in the code (but as you know, editors and **SQLModel** understand that). @@ -66,6 +158,32 @@ And here's the important part to allow the **many-to-many** relationship, we use Let's see the other side, here's the `Hero` model: +//// tab | Python 3.10+ + +```Python hl_lines="9" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py[ln:17-23]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="9" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py[ln:23-29]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="9" # Code above omitted 👆 @@ -74,12 +192,34 @@ Let's see the other side, here's the `Hero` model: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial001.py!} ``` +//// + /// We **removed** the previous `team_id` field (column) because now the relationship is done via the link table. 🔥 @@ -98,6 +238,32 @@ And now we have a **`link_model=HeroTeamLink`**. ✨ The same as before, we will have the rest of the code to create the **engine**, and a function to create all the tables `create_db_and_tables()`. +//// tab | Python 3.10+ + +```Python hl_lines="9" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py[ln:26-33]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="9" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py[ln:32-39]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="9" # Code above omitted 👆 @@ -106,17 +272,67 @@ The same as before, we will have the rest of the code to create the **engine**, # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial001.py!} ``` +//// + /// And as in previous examples, we will add that function to a function `main()`, and we will call that `main()` function in the main block: +//// tab | Python 3.10+ + +```Python hl_lines="4" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py[ln:72-73]!} + # We will do more stuff here later 👈 + +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py[ln:77-78]!} +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="4" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py[ln:78-79]!} + # We will do more stuff here later 👈 + +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py[ln:83-84]!} +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="4" # Code above omitted 👆 @@ -126,12 +342,34 @@ And as in previous examples, we will add that function to a function `main()`, a {!./docs_src/tutorial/many_to_many/tutorial001.py[ln:83-84]!} ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial001.py!} ``` +//// + /// diff --git a/docs/tutorial/many-to-many/link-with-extra-fields.md b/docs/tutorial/many-to-many/link-with-extra-fields.md index b7e06c2b4..653ef223d 100644 --- a/docs/tutorial/many-to-many/link-with-extra-fields.md +++ b/docs/tutorial/many-to-many/link-with-extra-fields.md @@ -32,6 +32,32 @@ We will add a new field `is_training`. And we will also add two **relationship attributes**, for the linked `team` and `hero`: +//// tab | Python 3.10+ + +```Python hl_lines="6 8-9" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial003_py310.py[ln:4-10]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="10 12-13" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial003_py39.py[ln:6-16]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="10 12-13" # Code above omitted 👆 @@ -40,12 +66,34 @@ And we will also add two **relationship attributes**, for the linked `team` and # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial003_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial003_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial003.py!} ``` +//// + /// The new **relationship attributes** have their own `back_populates` pointing to new relationship attributes we will create in the `Hero` and `Team` models: @@ -67,6 +115,32 @@ Now let's update the `Team` model. We no longer have the `heroes` relationship attribute, and instead we have the new `hero_links` attribute: +//// tab | Python 3.10+ + +```Python hl_lines="8" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial003_py310.py[ln:13-18]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="8" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial003_py39.py[ln:19-24]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="8" # Code above omitted 👆 @@ -75,12 +149,34 @@ We no longer have the `heroes` relationship attribute, and instead we have the n # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial003_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial003_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial003.py!} ``` +//// + /// ## Update Hero Model @@ -89,6 +185,32 @@ The same with the `Hero` model. We change the `teams` relationship attribute for `team_links`: +//// tab | Python 3.10+ + +```Python hl_lines="9" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial003_py310.py[ln:21-27]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="9" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial003_py39.py[ln:27-33]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="9" # Code above omitted 👆 @@ -97,12 +219,34 @@ We change the `teams` relationship attribute for `team_links`: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial003_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial003_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial003.py!} ``` +//// + /// ## Create Relationships @@ -111,6 +255,32 @@ Now the process to create relationships is very similar. But now we create the **explicit link models** manually, pointing to their hero and team instances, and specifying the additional link data (`is_training`): +//// tab | Python 3.10+ + +```Python hl_lines="21-30 32-35" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial003_py310.py[ln:40-79]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="21-30 32-35" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial003_py39.py[ln:46-85]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="21-30 32-35" # Code above omitted 👆 @@ -119,12 +289,34 @@ But now we create the **explicit link models** manually, pointing to their hero # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial003_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial003_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial003.py!} ``` +//// + /// We are just adding the link model instances to the session, because the link model instances are connected to the heroes and teams, they will be also automatically included in the session when we commit. @@ -223,6 +415,32 @@ Now, to add a new relationship, we have to create a new `HeroTeamLink` instance Here we do that in the `update_heroes()` function: +//// tab | Python 3.10+ + +```Python hl_lines="10-15" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial003_py310.py[ln:82-97]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="10-15" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial003_py39.py[ln:88-103]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="10-15" # Code above omitted 👆 @@ -231,12 +449,34 @@ Here we do that in the `update_heroes()` function: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial003_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial003_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial003.py!} ``` +//// + /// ## Run the Program with the New Relationship @@ -318,6 +558,40 @@ So now we want to update the status of `is_training` to `False`. We can do that by iterating on the links: +//// tab | Python 3.10+ + +```Python hl_lines="8-10" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial003_py310.py[ln:82-83]!} + + # Code here omitted 👈 + +{!./docs_src/tutorial/many_to_many/tutorial003_py310.py[ln:99-107]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="8-10" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial003_py39.py[ln:88-89]!} + + # Code here omitted 👈 + +{!./docs_src/tutorial/many_to_many/tutorial003_py39.py[ln:105-113]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="8-10" # Code above omitted 👆 @@ -330,12 +604,34 @@ We can do that by iterating on the links: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial003_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial003_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial003.py!} ``` +//// + /// ## Run the Program with the Updated Relationships diff --git a/docs/tutorial/many-to-many/update-remove-relationships.md b/docs/tutorial/many-to-many/update-remove-relationships.md index 0e83e24d2..555289a0e 100644 --- a/docs/tutorial/many-to-many/update-remove-relationships.md +++ b/docs/tutorial/many-to-many/update-remove-relationships.md @@ -6,10 +6,30 @@ We'll continue from where we left off with the previous code. /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial001.py!} ``` +//// + /// ## Get Data to Update @@ -22,6 +42,36 @@ As you already know how these goes, I'll use the **short version** and get the d And because we are now using `select()`, we also have to import it. +//// tab | Python 3.10+ + +```Python hl_lines="1 5-10" +{!./docs_src/tutorial/many_to_many/tutorial002_py310.py[ln:1]!} + +# Some code here omitted 👈 + +{!./docs_src/tutorial/many_to_many/tutorial002_py310.py[ln:72-77]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3 7-12" +{!./docs_src/tutorial/many_to_many/tutorial002_py39.py[ln:1-3]!} + +# Some code here omitted 👈 + +{!./docs_src/tutorial/many_to_many/tutorial002_py39.py[ln:78-83]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3 7-12" {!./docs_src/tutorial/many_to_many/tutorial002.py[ln:1-3]!} @@ -32,28 +82,94 @@ And because we are now using `select()`, we also have to import it. # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial002.py!} ``` +//// + /// And of course, we have to add `update_heroes()` to our `main()` function: +//// tab | Python 3.10+ + +```Python hl_lines="6" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial002_py310.py[ln:94-101]!} +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="6" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial002_py39.py[ln:100-107]!} +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="6" # Code above omitted 👆 {!./docs_src/tutorial/many_to_many/tutorial002.py[ln:100-107]!} ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial002.py!} ``` +//// + /// ## Add Many-to-Many Relationships @@ -62,6 +178,32 @@ Now let's imagine that **Spider-Boy** thinks that the **Z-Force** team is super We can use the same **relationship attributes** to include `hero_spider_boy` in the `team_z_force.heroes`. +//// tab | Python 3.10+ + +```Python hl_lines="10-12 14-15" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial002_py310.py[ln:72-84]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="10-12 14-15" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial002_py39.py[ln:78-90]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="10-12 14-15" # Code above omitted 👆 @@ -70,12 +212,34 @@ We can use the same **relationship attributes** to include `hero_spider_boy` in # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial002.py!} ``` +//// + /// /// tip @@ -161,6 +325,32 @@ Because `hero_spider_boy.teams` is just a list (a special list managed by SQLAlc In this case, we use the method `.remove()`, that takes an item and removes it from the list. +//// tab | Python 3.10+ + +```Python hl_lines="17-19 21-22" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial002_py310.py[ln:72-91]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="17-19 21-22" +# Code above omitted 👆 + +{!./docs_src/tutorial/many_to_many/tutorial002_py39.py[ln:78-97]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="17-19 21-22" # Code above omitted 👆 @@ -169,12 +359,34 @@ In this case, we use the method `.remove()`, that takes an item and removes it f # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/many_to_many/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/many_to_many/tutorial002.py!} ``` +//// + /// And this time, just to show again that by using `back_populates` **SQLModel** (actually SQLAlchemy) takes care of connecting the models by their relationships, even though we performed the operation from the `hero_spider_boy` object (modifying `hero_spider_boy.teams`), we are adding `team_z_force` to the **session**. And we commit that, without even add `hero_spider_boy`. diff --git a/docs/tutorial/one.md b/docs/tutorial/one.md index 75a4cc5c7..f374d1b4a 100644 --- a/docs/tutorial/one.md +++ b/docs/tutorial/one.md @@ -16,10 +16,22 @@ We'll continue with the same examples we have been using in the previous chapter /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/indexes/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/indexes/tutorial002.py!} ``` +//// + /// If you already executed the previous examples and have a database with data, **remove the database file** before running each example, that way you won't have duplicate data and you will be able to get the same results. @@ -28,6 +40,20 @@ If you already executed the previous examples and have a database with data, **r We have been iterating over the rows in a `result` object like: +//// tab | Python 3.10+ + +```Python hl_lines="7-8" +# Code above omitted 👆 + +{!./docs_src/tutorial/indexes/tutorial002_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="7-8" # Code above omitted 👆 @@ -36,18 +62,46 @@ We have been iterating over the rows in a `result` object like: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/indexes/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/indexes/tutorial002.py!} ``` +//// + /// But let's say that we are not interested in all the rows, just the **first** one. We can call the `.first()` method on the `results` object to get the first row: +//// tab | Python 3.10+ + +```Python hl_lines="7" +# Code above omitted 👆 + +{!./docs_src/tutorial/one/tutorial001_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="7" # Code above omitted 👆 @@ -56,12 +110,26 @@ We can call the `.first()` method on the `results` object to get the first row: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/one/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/one/tutorial001.py!} ``` +//// + /// This will return the first object in the `results` (if there was any). @@ -103,6 +171,20 @@ It would be possible that the SQL query doesn't find any row. In that case, `.first()` will return `None`: +//// tab | Python 3.10+ + +```Python hl_lines="5 7" +# Code above omitted 👆 + +{!./docs_src/tutorial/one/tutorial002_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5 7" # Code above omitted 👆 @@ -111,12 +193,26 @@ In that case, `.first()` will return `None`: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/one/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/one/tutorial002.py!} ``` +//// + /// In this case, as there's no hero with an age less than 25, `.first()` will return `None`. @@ -150,6 +246,20 @@ And if there was more than one, it would mean that there's an error in the syste In that case, instead of `.first()` we can use `.one()`: +//// tab | Python 3.10+ + +```Python hl_lines="7" +# Code above omitted 👆 + +{!./docs_src/tutorial/one/tutorial003_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="7" # Code above omitted 👆 @@ -158,12 +268,26 @@ In that case, instead of `.first()` we can use `.one()`: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/one/tutorial003_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/one/tutorial003.py!} ``` +//// + /// Here we know that there's only one `"Deadpond"`, and there shouldn't be any more than one. @@ -220,6 +344,20 @@ sqlalchemy.exc.MultipleResultsFound: Multiple rows were found when exactly one w Of course, even if we don't duplicate the data, we could get the same error if we send a query that finds more than one row and expect exactly one with `.one()`: +//// tab | Python 3.10+ + +```Python hl_lines="5 7" +# Code above omitted 👆 + +{!./docs_src/tutorial/one/tutorial004_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5 7" # Code above omitted 👆 @@ -228,12 +366,26 @@ Of course, even if we don't duplicate the data, we could get the same error if w # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/one/tutorial004_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/one/tutorial004.py!} ``` +//// + /// That would find 2 rows, and would end up with the same error. @@ -242,6 +394,20 @@ That would find 2 rows, and would end up with the same error. And also, if we get no rows at all with `.one()`, it will also raise an error: +//// tab | Python 3.10+ + +```Python hl_lines="5 7" +# Code above omitted 👆 + +{!./docs_src/tutorial/one/tutorial005_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5 7" # Code above omitted 👆 @@ -250,12 +416,26 @@ And also, if we get no rows at all with `.one()`, it will also raise an error: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/one/tutorial005_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/one/tutorial005.py!} ``` +//// + /// In this case, as there are no heroes with an age less than 25, `.one()` will raise an error. @@ -289,6 +469,20 @@ sqlalchemy.exc.NoResultFound: No row was found when one was required Of course, with `.first()` and `.one()` you would also probably write all that in a more compact form most of the time, all in a single line (or at least a single Python statement): +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/one/tutorial006_py310.py[ln:42-45]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -297,12 +491,26 @@ Of course, with `.first()` and `.one()` you would also probably write all that i # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/one/tutorial006_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/one/tutorial006.py!} ``` +//// + /// That would result in the same as some examples above. @@ -313,6 +521,20 @@ In many cases you might want to select a single row by its Id column with the ** You could do it the same way we have been doing with a `.where()` and then getting the first item with `.first()`: +//// tab | Python 3.10+ + +```Python hl_lines="5 7" +# Code above omitted 👆 + +{!./docs_src/tutorial/one/tutorial007_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5 7" # Code above omitted 👆 @@ -321,12 +543,26 @@ You could do it the same way we have been doing with a `.where()` and then getti # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/one/tutorial007_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/one/tutorial007.py!} ``` +//// + /// That would work correctly, as expected. But there's a shorter version. 👇 @@ -335,6 +571,20 @@ That would work correctly, as expected. But there's a shorter version. 👇 As selecting a single row by its Id column with the **primary key** is a common operation, there's a shortcut for it: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/one/tutorial008_py310.py[ln:42-45]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -343,12 +593,26 @@ As selecting a single row by its Id column with the **primary key** is a common # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/one/tutorial008_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/one/tutorial008.py!} ``` +//// + /// `session.get(Hero, 1)` is an equivalent to creating a `select()`, then filtering by Id using `.where()`, and then getting the first item with `.first()`. @@ -378,6 +642,20 @@ Hero: secret_name='Dive Wilson' age=None id=1 name='Deadpond' `.get()` behaves similar to `.first()`, if there's no data it will simply return `None` (instead of raising an error): +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/one/tutorial009_py310.py[ln:42-45]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -386,12 +664,26 @@ Hero: secret_name='Dive Wilson' age=None id=1 name='Deadpond' # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/one/tutorial009_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/one/tutorial009.py!} ``` +//// + /// Running that will output: diff --git a/docs/tutorial/relationship-attributes/back-populates.md b/docs/tutorial/relationship-attributes/back-populates.md index 0c48bd790..0eaa7bb65 100644 --- a/docs/tutorial/relationship-attributes/back-populates.md +++ b/docs/tutorial/relationship-attributes/back-populates.md @@ -20,18 +20,62 @@ Let's understand that better with an example. Let's see how that works by writing an **incomplete** version first, without `back_populates`: +//// tab | Python 3.10+ + +```Python hl_lines="9 19" +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py310.py[ln:1-19]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="11 21" +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py39.py[ln:1-21]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="11 21" {!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py[ln:1-21]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py!} ``` +//// + /// ## Read Data Objects @@ -40,6 +84,32 @@ Now, we will get the **Spider-Boy** hero and, *independently*, the **Preventers* As you already know how this works, I won't separate that in a select `statement`, `results`, etc. Let's use the shorter form in a single call: +//// tab | Python 3.10+ + +```Python hl_lines="5-7 9-11" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py310.py[ln:103-111]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="5-7 9-11" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py39.py[ln:105-113]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5-7 9-11" # Code above omitted 👆 @@ -48,12 +118,34 @@ As you already know how this works, I won't separate that in a select `statement # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py!} ``` +//// + /// /// tip @@ -66,6 +158,32 @@ When writing your own code, this is probably the style you will use most often, Now, let's print the current **Spider-Boy**, the current **Preventers** team, and particularly, the current **Preventers** list of heroes: +//// tab | Python 3.10+ + +```Python hl_lines="13-15" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py310.py[ln:103-115]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="13-15" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py39.py[ln:105-117]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="13-15" # Code above omitted 👆 @@ -74,12 +192,34 @@ Now, let's print the current **Spider-Boy**, the current **Preventers** team, an # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py!} ``` +//// + /// Up to this point, it's all good. 😊 @@ -102,6 +242,40 @@ Notice that we have **Spider-Boy** there. Now let's update **Spider-Boy**, removing him from the team by setting `hero_spider_boy.team = None` and then let's print this object again: +//// tab | Python 3.10+ + +```Python hl_lines="8 12" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py310.py[ln:103-104]!} + + # Code here omitted 👈 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py310.py[ln:117-121]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="8 12" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py39.py[ln:105-106]!} + + # Code here omitted 👈 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py39.py[ln:119-123]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="8 12" # Code above omitted 👆 @@ -114,12 +288,34 @@ Now let's update **Spider-Boy**, removing him from the team by setting `hero_spi # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py!} ``` +//// + /// The first important thing is, we *haven't committed* the hero yet, so accessing the list of heroes would not trigger an automatic refresh. @@ -160,6 +356,40 @@ Oh, no! 😱 **Spider-Boy** is still listed there! Now, if we commit it and print again: +//// tab | Python 3.10+ + +```Python hl_lines="8-9 15" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py310.py[ln:103-104]!} + + # Code here omitted 👈 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py310.py[ln:123-130]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="8-9 15" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py39.py[ln:105-106]!} + + # Code here omitted 👈 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py39.py[ln:125-132]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="8-9 15" # Code above omitted 👆 @@ -172,12 +402,34 @@ Now, if we commit it and print again: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py!} ``` +//// + /// When we access `preventers_team.heroes` after the `commit`, that triggers a refresh, so we get the latest list, without **Spider-Boy**, so that's fine again: @@ -210,22 +462,100 @@ That's what `back_populates` is for. ✨ Let's add it back: +//// tab | Python 3.10+ + +```Python hl_lines="9 19" +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py310.py[ln:1-19]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="11 21" +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py39.py[ln:1-21]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="11 21" {!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py[ln:1-21]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py!} ``` +//// + /// And we can keep the rest of the code the same: +//// tab | Python 3.10+ + +```Python hl_lines="8 12" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py310.py[ln:103-104]!} + + # Code here omitted 👈 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py310.py[ln:117-121]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="8 12" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py39.py[ln:105-106]!} + + # Code here omitted 👈 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py39.py[ln:119-123]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="8 12" # Code above omitted 👆 @@ -238,12 +568,34 @@ And we can keep the rest of the code the same: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py!} ``` +//// + /// /// tip @@ -277,18 +629,62 @@ Now that you know why `back_populates` is there, let's review the exact value ag It's quite simple code, it's just a string, but it might be confusing to think exactly *what* string should go there: +//// tab | Python 3.10+ + +```Python hl_lines="9 19" +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py310.py[ln:1-19]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="11 21" +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py39.py[ln:1-21]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="11 21" {!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py[ln:1-21]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py!} ``` +//// + /// The string in `back_populates` is the name of the attribute *in the other* model, that will reference *the current* model. @@ -297,6 +693,32 @@ The string in `back_populates` is the name of the attribute *in the other* model So, in the class `Team`, we have an attribute `heroes` and we declare it with `Relationship(back_populates="team")`. +//// tab | Python 3.10+ + +```Python hl_lines="8" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py310.py[ln:4-9]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="8" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py39.py[ln:6-11]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="8" # Code above omitted 👆 @@ -305,12 +727,34 @@ So, in the class `Team`, we have an attribute `heroes` and we declare it with `R # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py!} ``` +//// + /// The string in `back_populates="team"` refers to the attribute `team` in the class `Hero` (the other class). @@ -319,6 +763,32 @@ And, in the class `Hero`, we declare an attribute `team`, and we declare it with So, the string `"heroes"` refers to the attribute `heroes` in the class `Team`. +//// tab | Python 3.10+ + +```Python hl_lines="10" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py310.py[ln:12-19]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="10" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py39.py[ln:14-21]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="10" # Code above omitted 👆 @@ -327,12 +797,34 @@ So, the string `"heroes"` refers to the attribute `heroes` in the class `Team`. # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py!} ``` +//// + /// /// tip @@ -358,6 +850,32 @@ So, `back_populates` would most probably be something like `"hero"` or `"heroes" +//// tab | Python 3.10+ + +```Python hl_lines="3 10 13 15" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial003_py310.py[ln:27-39]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3 10 13 15" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial003_py39.py[ln:29-41]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3 10 13 15" # Code above omitted 👆 @@ -366,10 +884,32 @@ So, `back_populates` would most probably be something like `"hero"` or `"heroes" # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial003_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial003_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/back_populates/tutorial003.py!} ``` +//// + /// diff --git a/docs/tutorial/relationship-attributes/create-and-update-relationships.md b/docs/tutorial/relationship-attributes/create-and-update-relationships.md index e93914119..8b34f0cb5 100644 --- a/docs/tutorial/relationship-attributes/create-and-update-relationships.md +++ b/docs/tutorial/relationship-attributes/create-and-update-relationships.md @@ -6,6 +6,20 @@ Let's see now how to create data with relationships using these new **relationsh Let's check the old code we used to create some heroes and teams: +//// tab | Python 3.10+ + +```Python hl_lines="9 12 18 24" +# Code above omitted 👆 + +{!./docs_src/tutorial/connect/insert/tutorial001_py310.py[ln:29-58]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="9 12 18 24" # Code above omitted 👆 @@ -14,12 +28,26 @@ Let's check the old code we used to create some heroes and teams: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/insert/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/insert/tutorial001.py!} ``` +//// + /// There are several things to **notice** here. @@ -40,6 +68,32 @@ This is the first area where these **relationship attributes** can help. 🤓 Now let's do all that, but this time using the new, shiny `Relationship` attributes: +//// tab | Python 3.10+ + +```Python hl_lines="9 12 18" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001_py310.py[ln:32-55]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="9 12 18" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001_py39.py[ln:34-57]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="9 12 18" # Code above omitted 👆 @@ -48,12 +102,34 @@ Now let's do all that, but this time using the new, shiny `Relationship` attribu # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py!} ``` +//// + /// Now we can create the `Team` instances and pass them directly to the new `team` argument when creating the `Hero` instances, as `team=team_preventers` instead of `team_id=team_preventers.id`. @@ -70,6 +146,40 @@ And then, as you can see, we only have to do one `commit()`. The same way we could assign an integer with a `team.id` to a `hero.team_id`, we can also assign the `Team` instance to the `hero.team`: +//// tab | Python 3.10+ + +```Python hl_lines="8" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py310.py[ln:32-33]!} + + # Previous code here omitted 👈 + +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py310.py[ln:57-61]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="8" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py39.py[ln:34-35]!} + + # Previous code here omitted 👈 + +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py39.py[ln:59-63]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="8" # Code above omitted 👆 @@ -82,12 +192,34 @@ The same way we could assign an integer with a `team.id` to a `hero.team_id`, we # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001.py!} ``` +//// + /// ## Create a Team with Heroes @@ -96,6 +228,40 @@ Before, we created some `Team` instances and passed them in the `team=` argument We could also create the `Hero` instances first, and then pass them in the `heroes=` argument that takes a list, when creating a `Team` instance: +//// tab | Python 3.10+ + +```Python hl_lines="13 15-16" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py310.py[ln:32-33]!} + + # Previous code here omitted 👈 + +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py310.py[ln:63-73]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="13 15-16" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py39.py[ln:34-35]!} + + # Previous code here omitted 👈 + +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py39.py[ln:65-75]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="13 15-16" # Code above omitted 👆 @@ -108,12 +274,34 @@ We could also create the `Hero` instances first, and then pass them in the `hero # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001.py!} ``` +//// + /// Here we create two heroes first, **Black Lion** and **Princess Sure-E**, and then we pass them in the `heroes` argument. @@ -130,6 +318,40 @@ As the attribute `team.heroes` behaves like a list, we can simply append to it. Let's create some more heroes and add them to the `team_preventers.heroes` list attribute: +//// tab | Python 3.10+ + +```Python hl_lines="14-18" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py310.py[ln:32-33]!} + + # Previous code here omitted 👈 + +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py310.py[ln:75-91]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="14-18" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py39.py[ln:34-35]!} + + # Previous code here omitted 👈 + +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py39.py[ln:77-93]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="14-18" # Code above omitted 👆 @@ -142,12 +364,34 @@ Let's create some more heroes and add them to the `team_preventers.heroes` list # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001.py!} ``` +//// + /// The attribute `team_preventers.heroes` behaves like a list. But it's a special type of list, because when we modify it adding heroes to it, **SQLModel** (actually SQLAlchemy) **keeps track of the necessary changes** to be done in the database. diff --git a/docs/tutorial/relationship-attributes/define-relationships-attributes.md b/docs/tutorial/relationship-attributes/define-relationships-attributes.md index a5363ab02..adbcc7df7 100644 --- a/docs/tutorial/relationship-attributes/define-relationships-attributes.md +++ b/docs/tutorial/relationship-attributes/define-relationships-attributes.md @@ -41,18 +41,44 @@ Now that you know how these tables work underneath and how the model classes rep Up to now, we have only used the `team_id` column to connect the tables when querying with `select()`: +//// tab | Python 3.10+ + +```Python hl_lines="16" +{!./docs_src/tutorial/connect/insert/tutorial001_py310.py[ln:1-16]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="18" {!./docs_src/tutorial/connect/insert/tutorial001.py[ln:1-18]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/connect/insert/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/connect/insert/tutorial001.py!} ``` +//// + /// This is a **plain field** like all the others, all representing a **column in the table**. @@ -61,34 +87,122 @@ But now let's add a couple of new special attributes to these model classes, let First, import `Relationship` from `sqlmodel`: +//// tab | Python 3.10+ + +```Python hl_lines="1" +{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001_py310.py[ln:1]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3" +{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001_py39.py[ln:1-3]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3" {!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py[ln:1-3]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py!} ``` +//// + /// Next, use that `Relationship` to declare a new attribute in the model classes: +//// tab | Python 3.10+ + +```Python hl_lines="9 19" +{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001_py310.py[ln:1-19]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="11 21" +{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001_py39.py[ln:1-21]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="11 21" {!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py[ln:1-21]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py!} ``` +//// + /// ## What Are These Relationship Attributes diff --git a/docs/tutorial/relationship-attributes/read-relationships.md b/docs/tutorial/relationship-attributes/read-relationships.md index e4a049d2b..fe9e5ae02 100644 --- a/docs/tutorial/relationship-attributes/read-relationships.md +++ b/docs/tutorial/relationship-attributes/read-relationships.md @@ -6,6 +6,40 @@ Now that we know how to connect data using **relationship Attributes**, let's se First, add a function `select_heroes()` where we get a hero to start working with, and add that function to the `main()` function: +//// tab | Python 3.10+ + +```Python hl_lines="3-7 14" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001_py310.py[ln:94-98]!} + +# Previous code here omitted 👈 + +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001_py310.py[ln:108-111]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="3-7 14" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001_py39.py[ln:96-100]!} + +# Previous code here omitted 👈 + +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001_py39.py[ln:110-113]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3-7 14" # Code above omitted 👆 @@ -18,12 +52,34 @@ First, add a function `select_heroes()` where we get a hero to start working wit # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001.py!} ``` +//// + /// ## Select the Related Team - Old Way @@ -32,6 +88,32 @@ Now that we have a hero, we can get the team this hero belongs to. With what we have learned **up to now**, we could use a `select()` statement, then execute it with `session.exec()`, and then get the `.first()` result, for example: +//// tab | Python 3.10+ + +```Python hl_lines="9-12" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001_py310.py[ln:94-103]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="9-12" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001_py39.py[ln:96-105]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="9-12" # Code above omitted 👆 @@ -40,12 +122,34 @@ With what we have learned **up to now**, we could use a `select()` statement, th # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001.py!} ``` +//// + /// ## Get Relationship Team - New Way @@ -54,6 +158,40 @@ But now that we have the **relationship attributes**, we can just access them, a So, the highlighted block above, has the same results as the block below: +//// tab | Python 3.10+ + +```Python hl_lines="11" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001_py310.py[ln:94-98]!} + + # Code from the previous example omitted 👈 + +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001_py310.py[ln:105]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="11" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001_py39.py[ln:96-100]!} + + # Code from the previous example omitted 👈 + +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001_py39.py[ln:107]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="11" # Code above omitted 👆 @@ -66,12 +204,34 @@ So, the highlighted block above, has the same results as the block below: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001.py!} ``` +//// + /// /// tip @@ -86,6 +246,32 @@ For example, here, **inside** a `with` block with a `Session` object. And the same way, when we are working on the **many** side of the **one-to-many** relationship, we can get a list of of the related objects just by accessing the relationship attribute: +//// tab | Python 3.10+ + +```Python hl_lines="9" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002_py310.py[ln:94-100]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="9" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002_py39.py[ln:96-102]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="9" # Code above omitted 👆 @@ -94,12 +280,34 @@ And the same way, when we are working on the **many** side of the **one-to-many* # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002.py!} ``` +//// + /// That would print a list with all the heroes in the Preventers team: diff --git a/docs/tutorial/relationship-attributes/remove-relationships.md b/docs/tutorial/relationship-attributes/remove-relationships.md index 982e316c2..56745850b 100644 --- a/docs/tutorial/relationship-attributes/remove-relationships.md +++ b/docs/tutorial/relationship-attributes/remove-relationships.md @@ -8,6 +8,32 @@ And then for some reason needs to leave the **Preventers** for some years. 😭 We can remove the relationship by setting it to `None`, the same as with the `team_id`, it also works with the new relationship attribute `.team`: +//// tab | Python 3.10+ + +```Python hl_lines="9" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002_py310.py[ln:103-114]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="9" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002_py39.py[ln:105-116]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="9" # Code above omitted 👆 @@ -16,16 +42,64 @@ We can remove the relationship by setting it to `None`, the same as with the `te # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002.py!} ``` +//// + /// And of course, we should remember to add this `update_heroes()` function to `main()` so that it runs when we call this program from the command line: +//// tab | Python 3.10+ + +```Python hl_lines="7" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002_py310.py[ln:117-121]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="7" +# Code above omitted 👆 + +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002_py39.py[ln:119-123]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="7" # Code above omitted 👆 @@ -34,12 +108,34 @@ And of course, we should remember to add this `update_heroes()` function to `mai # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002.py!} ``` +//// + /// ## Recap diff --git a/docs/tutorial/relationship-attributes/type-annotation-strings.md b/docs/tutorial/relationship-attributes/type-annotation-strings.md index 780da6a96..a798af9aa 100644 --- a/docs/tutorial/relationship-attributes/type-annotation-strings.md +++ b/docs/tutorial/relationship-attributes/type-annotation-strings.md @@ -2,18 +2,62 @@ In the first Relationship attribute, we declare it with `List["Hero"]`, putting the `Hero` in quotes instead of just normally there: +//// tab | Python 3.10+ + +```Python hl_lines="9" +{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001_py310.py[ln:1-19]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="11" +{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001_py39.py[ln:1-21]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="11" {!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py[ln:1-21]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python +{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001_py39.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py!} ``` +//// + /// What's that about? Can't we just write it normally as `List[Hero]`? diff --git a/docs/tutorial/select.md b/docs/tutorial/select.md index 5be5e8a0b..47c80a266 100644 --- a/docs/tutorial/select.md +++ b/docs/tutorial/select.md @@ -25,10 +25,22 @@ Let's continue from the last code we used to create some data. /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/insert/tutorial002_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/insert/tutorial002.py!} ``` +//// + /// We are creating a **SQLModel** `Hero` class model and creating some records. @@ -166,6 +178,20 @@ The first step is to create a **Session**, the same way we did when creating the We will start with that in a new function `select_heroes()`: +//// tab | Python 3.10+ + +```Python hl_lines="3-4" +# Code above omitted 👆 + +{!./docs_src/tutorial/select/tutorial001_py310.py[ln:34-35]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3-4" # Code above omitted 👆 @@ -174,12 +200,26 @@ We will start with that in a new function `select_heroes()`: # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/select/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/select/tutorial001.py!} ``` +//// + /// ## Create a `select` Statement @@ -188,22 +228,64 @@ Next, pretty much the same way we wrote a SQL `SELECT` statement above, now we'l First we have to import `select` from `sqlmodel` at the top of the file: +//// tab | Python 3.10+ + +```Python hl_lines="1" +{!./docs_src/tutorial/select/tutorial001_py310.py[ln:1]!} + +# More code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3" {!./docs_src/tutorial/select/tutorial001.py[ln:1-3]!} # More code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/select/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/select/tutorial001.py!} ``` +//// + /// And then we will use it to create a `SELECT` statement in Python code: +//// tab | Python 3.10+ + +```Python hl_lines="7" +{!./docs_src/tutorial/select/tutorial001_py310.py[ln:1]!} + +# More code here omitted 👈 + +{!./docs_src/tutorial/select/tutorial001_py310.py[ln:34-36]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="9" {!./docs_src/tutorial/select/tutorial001.py[ln:1-3]!} @@ -214,12 +296,26 @@ And then we will use it to create a `SELECT` statement in Python code: # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/select/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/select/tutorial001.py!} ``` +//// + /// It's a very simple line of code that conveys a lot of information: @@ -251,6 +347,20 @@ I'll tell you about that in the next chapters. Now that we have the `select` statement, we can execute it with the **session**: +//// tab | Python 3.10+ + +```Python hl_lines="6" +# Code above omitted 👆 + +{!./docs_src/tutorial/select/tutorial001_py310.py[ln:34-37]!} + +# More code here later 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="6" # Code above omitted 👆 @@ -259,12 +369,26 @@ Now that we have the `select` statement, we can execute it with the **session**: # More code here later 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/select/tutorial001_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/select/tutorial001.py!} ``` +//// + /// This will tell the **session** to go ahead and use the **engine** to execute that `SELECT` statement in the database and bring the results back. @@ -303,6 +427,20 @@ The `results` object is an ` to get the rows where a column is **more than** a value: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/where/tutorial003_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -589,12 +755,26 @@ Now let's use `>` to get the rows where a column is **more than** a value: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/where/tutorial003_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/where/tutorial003.py!} ``` +//// + /// That would output: @@ -615,6 +795,20 @@ Notice that it didn't select `Black Lion`, because the age is not *strictly* gre Let's do that again, but with `>=` to get the rows where a column is **more than or equal** to a value: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/where/tutorial004_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -623,12 +817,26 @@ Let's do that again, but with `>=` to get the rows where a column is **more than # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/where/tutorial004_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/where/tutorial004.py!} ``` +//// + /// Because we are using `>=`, the age `35` will be included in the output: @@ -650,6 +858,20 @@ This time we got `Black Lion` too because although the age is not *strictly* gre Similarly, we can use `<` to get the rows where a column is **less than** a value: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/where/tutorial005_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -658,12 +880,26 @@ Similarly, we can use `<` to get the rows where a column is **less than** a valu # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/where/tutorial005_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/where/tutorial005.py!} ``` +//// + /// And we get the younger one with an age in the database: @@ -682,6 +918,20 @@ We could imagine that **Spider-Boy** is even **younger**. But because we don't k Finally, we can use `<=` to get the rows where a column is **less than or equal** to a value: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/where/tutorial006_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -690,12 +940,26 @@ Finally, we can use `<=` to get the rows where a column is **less than or equal* # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/where/tutorial006_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/where/tutorial006.py!} ``` +//// + /// And we get the younger ones, `35` and below: @@ -721,6 +985,20 @@ We can use the same standard Python comparison operators like `<`, `<=`, `>`, `> Because `.where()` returns the same special select object back, we can add more `.where()` calls to it: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/where/tutorial007_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -729,12 +1007,26 @@ Because `.where()` returns the same special select object back, we can add more # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/where/tutorial007_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/where/tutorial007.py!} ``` +//// + /// This will select the rows `WHERE` the `age` is **greater than or equal** to `35`, `AND` also the `age` is **less than** `40`. @@ -776,6 +1068,20 @@ age=36 id=6 name='Dr. Weird' secret_name='Steve Weird' As an alternative to using multiple `.where()` we can also pass several expressions to a single `.where()`: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/where/tutorial008_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -784,12 +1090,26 @@ As an alternative to using multiple `.where()` we can also pass several expressi # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/where/tutorial008_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/where/tutorial008.py!} ``` +//// + /// This is the same as the above, and will result in the same output with the two heroes: @@ -807,24 +1127,64 @@ But we can also combine expressions using `OR`. Which means that **any** (but no To do it, you can import `or_`: +//// tab | Python 3.10+ + +```Python hl_lines="1" +{!./docs_src/tutorial/where/tutorial009_py310.py[ln:1]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3" {!./docs_src/tutorial/where/tutorial009.py[ln:1-3]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/where/tutorial009_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/where/tutorial009.py!} ``` +//// + /// And then pass both expressions to `or_()` and put it inside `.where()`. For example, here we select the heroes that are the youngest OR the oldest: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/where/tutorial009_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -833,12 +1193,26 @@ For example, here we select the heroes that are the youngest OR the oldest: # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/where/tutorial009_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/where/tutorial009.py!} ``` +//// + /// When we run it, this generates the output: @@ -890,22 +1264,62 @@ We can tell the editor that this class attribute is actually a special **SQLMode To do that, we can import `col()` (as short for "column"): +//// tab | Python 3.10+ + +```Python hl_lines="1" +{!./docs_src/tutorial/where/tutorial011_py310.py[ln:1]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="3" {!./docs_src/tutorial/where/tutorial011.py[ln:1-3]!} # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/where/tutorial011_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/where/tutorial011.py!} ``` +//// + /// And then put the **class attribute** inside `col()` when using it in a `.where()`: +//// tab | Python 3.10+ + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/where/tutorial011_py310.py[ln:42-47]!} + +# Code below omitted 👇 +``` + +//// + +//// tab | Python 3.7+ + ```Python hl_lines="5" # Code above omitted 👆 @@ -914,12 +1328,26 @@ And then put the **class attribute** inside `col()` when using it in a `.where() # Code below omitted 👇 ``` +//// + /// details | 👀 Full file preview +//// tab | Python 3.10+ + +```Python +{!./docs_src/tutorial/where/tutorial011_py310.py!} +``` + +//// + +//// tab | Python 3.7+ + ```Python {!./docs_src/tutorial/where/tutorial011.py!} ``` +//// + /// So, now the comparison is not: diff --git a/docs_src/tutorial/fastapi/app_testing/tutorial001/main.py b/docs_src/tutorial/fastapi/app_testing/tutorial001/main.py index 7014a7391..f46d8b078 100644 --- a/docs_src/tutorial/fastapi/app_testing/tutorial001/main.py +++ b/docs_src/tutorial/fastapi/app_testing/tutorial001/main.py @@ -18,7 +18,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -52,7 +52,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): db_hero = Hero.model_validate(hero) session.add(db_hero) @@ -61,7 +61,7 @@ def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=List[HeroRead]) +@app.get("/heroes/", response_model=List[HeroPublic]) def read_heroes( *, session: Session = Depends(get_session), @@ -72,7 +72,7 @@ def read_heroes( return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(*, session: Session = Depends(get_session), hero_id: int): hero = session.get(Hero, hero_id) if not hero: @@ -80,7 +80,7 @@ def read_hero(*, session: Session = Depends(get_session), hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero( *, session: Session = Depends(get_session), hero_id: int, hero: HeroUpdate ): diff --git a/docs_src/tutorial/fastapi/app_testing/tutorial001_py310/main.py b/docs_src/tutorial/fastapi/app_testing/tutorial001_py310/main.py index cf1bbb713..702eba722 100644 --- a/docs_src/tutorial/fastapi/app_testing/tutorial001_py310/main.py +++ b/docs_src/tutorial/fastapi/app_testing/tutorial001_py310/main.py @@ -16,7 +16,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -50,7 +50,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): db_hero = Hero.model_validate(hero) session.add(db_hero) @@ -59,7 +59,7 @@ def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes( *, session: Session = Depends(get_session), @@ -70,7 +70,7 @@ def read_heroes( return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(*, session: Session = Depends(get_session), hero_id: int): hero = session.get(Hero, hero_id) if not hero: @@ -78,7 +78,7 @@ def read_hero(*, session: Session = Depends(get_session), hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero( *, session: Session = Depends(get_session), hero_id: int, hero: HeroUpdate ): diff --git a/docs_src/tutorial/fastapi/app_testing/tutorial001_py39/main.py b/docs_src/tutorial/fastapi/app_testing/tutorial001_py39/main.py index 9f428ab3e..efdfd8ee6 100644 --- a/docs_src/tutorial/fastapi/app_testing/tutorial001_py39/main.py +++ b/docs_src/tutorial/fastapi/app_testing/tutorial001_py39/main.py @@ -18,7 +18,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -52,7 +52,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): db_hero = Hero.model_validate(hero) session.add(db_hero) @@ -61,7 +61,7 @@ def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes( *, session: Session = Depends(get_session), @@ -72,7 +72,7 @@ def read_heroes( return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(*, session: Session = Depends(get_session), hero_id: int): hero = session.get(Hero, hero_id) if not hero: @@ -80,7 +80,7 @@ def read_hero(*, session: Session = Depends(get_session), hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero( *, session: Session = Depends(get_session), hero_id: int, hero: HeroUpdate ): diff --git a/docs_src/tutorial/fastapi/delete/tutorial001.py b/docs_src/tutorial/fastapi/delete/tutorial001.py index 532817360..1871ab170 100644 --- a/docs_src/tutorial/fastapi/delete/tutorial001.py +++ b/docs_src/tutorial/fastapi/delete/tutorial001.py @@ -18,7 +18,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -47,7 +47,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -57,14 +57,14 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=List[HeroRead]) +@app.get("/heroes/", response_model=List[HeroPublic]) def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): with Session(engine) as session: heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(hero_id: int): with Session(engine) as session: hero = session.get(Hero, hero_id) @@ -73,7 +73,7 @@ def read_hero(hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero(hero_id: int, hero: HeroUpdate): with Session(engine) as session: db_hero = session.get(Hero, hero_id) diff --git a/docs_src/tutorial/fastapi/delete/tutorial001_py310.py b/docs_src/tutorial/fastapi/delete/tutorial001_py310.py index 45e2e1d51..0dd0c889f 100644 --- a/docs_src/tutorial/fastapi/delete/tutorial001_py310.py +++ b/docs_src/tutorial/fastapi/delete/tutorial001_py310.py @@ -16,7 +16,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -45,7 +45,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -55,14 +55,14 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): with Session(engine) as session: heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(hero_id: int): with Session(engine) as session: hero = session.get(Hero, hero_id) @@ -71,7 +71,7 @@ def read_hero(hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero(hero_id: int, hero: HeroUpdate): with Session(engine) as session: db_hero = session.get(Hero, hero_id) diff --git a/docs_src/tutorial/fastapi/delete/tutorial001_py39.py b/docs_src/tutorial/fastapi/delete/tutorial001_py39.py index 12f6bc3f9..9ab3056c8 100644 --- a/docs_src/tutorial/fastapi/delete/tutorial001_py39.py +++ b/docs_src/tutorial/fastapi/delete/tutorial001_py39.py @@ -18,7 +18,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -47,7 +47,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -57,14 +57,14 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): with Session(engine) as session: heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(hero_id: int): with Session(engine) as session: hero = session.get(Hero, hero_id) @@ -73,7 +73,7 @@ def read_hero(hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero(hero_id: int, hero: HeroUpdate): with Session(engine) as session: db_hero = session.get(Hero, hero_id) diff --git a/docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py b/docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py index 2352f3902..ccf3d7703 100644 --- a/docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py +++ b/docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py @@ -18,7 +18,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -41,7 +41,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -51,14 +51,14 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=List[HeroRead]) +@app.get("/heroes/", response_model=List[HeroPublic]) def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): with Session(engine) as session: heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(hero_id: int): with Session(engine) as session: hero = session.get(Hero, hero_id) diff --git a/docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py310.py b/docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py310.py index ad8ff95e3..3402d4045 100644 --- a/docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py310.py +++ b/docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py310.py @@ -16,7 +16,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -39,7 +39,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -49,14 +49,14 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): with Session(engine) as session: heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(hero_id: int): with Session(engine) as session: hero = session.get(Hero, hero_id) diff --git a/docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py39.py b/docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py39.py index b1f7cdcb6..3d223f329 100644 --- a/docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py39.py +++ b/docs_src/tutorial/fastapi/limit_and_offset/tutorial001_py39.py @@ -18,7 +18,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -41,7 +41,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -51,14 +51,14 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): with Session(engine) as session: heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(hero_id: int): with Session(engine) as session: hero = session.get(Hero, hero_id) diff --git a/docs_src/tutorial/fastapi/multiple_models/tutorial001.py b/docs_src/tutorial/fastapi/multiple_models/tutorial001.py index 7f59ac6a1..42ac8051b 100644 --- a/docs_src/tutorial/fastapi/multiple_models/tutorial001.py +++ b/docs_src/tutorial/fastapi/multiple_models/tutorial001.py @@ -17,7 +17,7 @@ class HeroCreate(SQLModel): age: Optional[int] = None -class HeroRead(SQLModel): +class HeroPublic(SQLModel): id: int name: str secret_name: str @@ -43,7 +43,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -53,7 +53,7 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=List[HeroRead]) +@app.get("/heroes/", response_model=List[HeroPublic]) def read_heroes(): with Session(engine) as session: heroes = session.exec(select(Hero)).all() diff --git a/docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py b/docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py index ff12eff55..b8dc44d98 100644 --- a/docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py +++ b/docs_src/tutorial/fastapi/multiple_models/tutorial001_py310.py @@ -15,7 +15,7 @@ class HeroCreate(SQLModel): age: int | None = None -class HeroRead(SQLModel): +class HeroPublic(SQLModel): id: int name: str secret_name: str @@ -41,7 +41,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -51,7 +51,7 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes(): with Session(engine) as session: heroes = session.exec(select(Hero)).all() diff --git a/docs_src/tutorial/fastapi/multiple_models/tutorial001_py39.py b/docs_src/tutorial/fastapi/multiple_models/tutorial001_py39.py index 977a1ac8d..c6be23d44 100644 --- a/docs_src/tutorial/fastapi/multiple_models/tutorial001_py39.py +++ b/docs_src/tutorial/fastapi/multiple_models/tutorial001_py39.py @@ -17,7 +17,7 @@ class HeroCreate(SQLModel): age: Optional[int] = None -class HeroRead(SQLModel): +class HeroPublic(SQLModel): id: int name: str secret_name: str @@ -43,7 +43,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -53,7 +53,7 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes(): with Session(engine) as session: heroes = session.exec(select(Hero)).all() diff --git a/docs_src/tutorial/fastapi/multiple_models/tutorial002.py b/docs_src/tutorial/fastapi/multiple_models/tutorial002.py index fffbe7249..79c71f1a2 100644 --- a/docs_src/tutorial/fastapi/multiple_models/tutorial002.py +++ b/docs_src/tutorial/fastapi/multiple_models/tutorial002.py @@ -18,7 +18,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -41,7 +41,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -51,7 +51,7 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=List[HeroRead]) +@app.get("/heroes/", response_model=List[HeroPublic]) def read_heroes(): with Session(engine) as session: heroes = session.exec(select(Hero)).all() diff --git a/docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py b/docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py index 7373edff5..79e7447b5 100644 --- a/docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py +++ b/docs_src/tutorial/fastapi/multiple_models/tutorial002_py310.py @@ -16,7 +16,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -39,7 +39,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -49,7 +49,7 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes(): with Session(engine) as session: heroes = session.exec(select(Hero)).all() diff --git a/docs_src/tutorial/fastapi/multiple_models/tutorial002_py39.py b/docs_src/tutorial/fastapi/multiple_models/tutorial002_py39.py index 1b4a51252..77093bcc7 100644 --- a/docs_src/tutorial/fastapi/multiple_models/tutorial002_py39.py +++ b/docs_src/tutorial/fastapi/multiple_models/tutorial002_py39.py @@ -18,7 +18,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -41,7 +41,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -51,7 +51,7 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes(): with Session(engine) as session: heroes = session.exec(select(Hero)).all() diff --git a/docs_src/tutorial/fastapi/read_one/tutorial001.py b/docs_src/tutorial/fastapi/read_one/tutorial001.py index f18426e74..a39945173 100644 --- a/docs_src/tutorial/fastapi/read_one/tutorial001.py +++ b/docs_src/tutorial/fastapi/read_one/tutorial001.py @@ -18,7 +18,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -41,7 +41,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -51,14 +51,14 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=List[HeroRead]) +@app.get("/heroes/", response_model=List[HeroPublic]) def read_heroes(): with Session(engine) as session: heroes = session.exec(select(Hero)).all() return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(hero_id: int): with Session(engine) as session: hero = session.get(Hero, hero_id) diff --git a/docs_src/tutorial/fastapi/read_one/tutorial001_py310.py b/docs_src/tutorial/fastapi/read_one/tutorial001_py310.py index e8c7d49b9..1a4628137 100644 --- a/docs_src/tutorial/fastapi/read_one/tutorial001_py310.py +++ b/docs_src/tutorial/fastapi/read_one/tutorial001_py310.py @@ -16,7 +16,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -39,7 +39,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -49,14 +49,14 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes(): with Session(engine) as session: heroes = session.exec(select(Hero)).all() return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(hero_id: int): with Session(engine) as session: hero = session.get(Hero, hero_id) diff --git a/docs_src/tutorial/fastapi/read_one/tutorial001_py39.py b/docs_src/tutorial/fastapi/read_one/tutorial001_py39.py index 4dc5702fb..9ac0a6508 100644 --- a/docs_src/tutorial/fastapi/read_one/tutorial001_py39.py +++ b/docs_src/tutorial/fastapi/read_one/tutorial001_py39.py @@ -18,7 +18,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -41,7 +41,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -51,14 +51,14 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes(): with Session(engine) as session: heroes = session.exec(select(Hero)).all() return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(hero_id: int): with Session(engine) as session: hero = session.get(Hero, hero_id) diff --git a/docs_src/tutorial/fastapi/relationships/tutorial001.py b/docs_src/tutorial/fastapi/relationships/tutorial001.py index 51339e2a2..ac8a557cf 100644 --- a/docs_src/tutorial/fastapi/relationships/tutorial001.py +++ b/docs_src/tutorial/fastapi/relationships/tutorial001.py @@ -19,7 +19,7 @@ class TeamCreate(TeamBase): pass -class TeamRead(TeamBase): +class TeamPublic(TeamBase): id: int @@ -43,7 +43,7 @@ class Hero(HeroBase, table=True): team: Optional[Team] = Relationship(back_populates="heroes") -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -58,12 +58,12 @@ class HeroUpdate(SQLModel): team_id: Optional[int] = None -class HeroReadWithTeam(HeroRead): - team: Optional[TeamRead] = None +class HeroPublicWithTeam(HeroPublic): + team: Optional[TeamPublic] = None -class TeamReadWithHeroes(TeamRead): - heroes: List[HeroRead] = [] +class TeamPublicWithHeroes(TeamPublic): + heroes: List[HeroPublic] = [] sqlite_file_name = "database.db" @@ -90,7 +90,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): db_hero = Hero.model_validate(hero) session.add(db_hero) @@ -99,7 +99,7 @@ def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=List[HeroRead]) +@app.get("/heroes/", response_model=List[HeroPublic]) def read_heroes( *, session: Session = Depends(get_session), @@ -110,7 +110,7 @@ def read_heroes( return heroes -@app.get("/heroes/{hero_id}", response_model=HeroReadWithTeam) +@app.get("/heroes/{hero_id}", response_model=HeroPublicWithTeam) def read_hero(*, session: Session = Depends(get_session), hero_id: int): hero = session.get(Hero, hero_id) if not hero: @@ -118,7 +118,7 @@ def read_hero(*, session: Session = Depends(get_session), hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero( *, session: Session = Depends(get_session), hero_id: int, hero: HeroUpdate ): @@ -144,7 +144,7 @@ def delete_hero(*, session: Session = Depends(get_session), hero_id: int): return {"ok": True} -@app.post("/teams/", response_model=TeamRead) +@app.post("/teams/", response_model=TeamPublic) def create_team(*, session: Session = Depends(get_session), team: TeamCreate): db_team = Team.model_validate(team) session.add(db_team) @@ -153,7 +153,7 @@ def create_team(*, session: Session = Depends(get_session), team: TeamCreate): return db_team -@app.get("/teams/", response_model=List[TeamRead]) +@app.get("/teams/", response_model=List[TeamPublic]) def read_teams( *, session: Session = Depends(get_session), @@ -164,7 +164,7 @@ def read_teams( return teams -@app.get("/teams/{team_id}", response_model=TeamReadWithHeroes) +@app.get("/teams/{team_id}", response_model=TeamPublicWithHeroes) def read_team(*, team_id: int, session: Session = Depends(get_session)): team = session.get(Team, team_id) if not team: @@ -172,7 +172,7 @@ def read_team(*, team_id: int, session: Session = Depends(get_session)): return team -@app.patch("/teams/{team_id}", response_model=TeamRead) +@app.patch("/teams/{team_id}", response_model=TeamPublic) def update_team( *, session: Session = Depends(get_session), diff --git a/docs_src/tutorial/fastapi/relationships/tutorial001_py310.py b/docs_src/tutorial/fastapi/relationships/tutorial001_py310.py index 35257bd51..5110b158b 100644 --- a/docs_src/tutorial/fastapi/relationships/tutorial001_py310.py +++ b/docs_src/tutorial/fastapi/relationships/tutorial001_py310.py @@ -17,7 +17,7 @@ class TeamCreate(TeamBase): pass -class TeamRead(TeamBase): +class TeamPublic(TeamBase): id: int @@ -41,7 +41,7 @@ class Hero(HeroBase, table=True): team: Team | None = Relationship(back_populates="heroes") -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -56,12 +56,12 @@ class HeroUpdate(SQLModel): team_id: int | None = None -class HeroReadWithTeam(HeroRead): - team: TeamRead | None = None +class HeroPublicWithTeam(HeroPublic): + team: TeamPublic | None = None -class TeamReadWithHeroes(TeamRead): - heroes: list[HeroRead] = [] +class TeamPublicWithHeroes(TeamPublic): + heroes: list[HeroPublic] = [] sqlite_file_name = "database.db" @@ -88,7 +88,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): db_hero = Hero.model_validate(hero) session.add(db_hero) @@ -97,7 +97,7 @@ def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes( *, session: Session = Depends(get_session), @@ -108,7 +108,7 @@ def read_heroes( return heroes -@app.get("/heroes/{hero_id}", response_model=HeroReadWithTeam) +@app.get("/heroes/{hero_id}", response_model=HeroPublicWithTeam) def read_hero(*, session: Session = Depends(get_session), hero_id: int): hero = session.get(Hero, hero_id) if not hero: @@ -116,7 +116,7 @@ def read_hero(*, session: Session = Depends(get_session), hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero( *, session: Session = Depends(get_session), hero_id: int, hero: HeroUpdate ): @@ -142,7 +142,7 @@ def delete_hero(*, session: Session = Depends(get_session), hero_id: int): return {"ok": True} -@app.post("/teams/", response_model=TeamRead) +@app.post("/teams/", response_model=TeamPublic) def create_team(*, session: Session = Depends(get_session), team: TeamCreate): db_team = Team.model_validate(team) session.add(db_team) @@ -151,7 +151,7 @@ def create_team(*, session: Session = Depends(get_session), team: TeamCreate): return db_team -@app.get("/teams/", response_model=list[TeamRead]) +@app.get("/teams/", response_model=list[TeamPublic]) def read_teams( *, session: Session = Depends(get_session), @@ -162,7 +162,7 @@ def read_teams( return teams -@app.get("/teams/{team_id}", response_model=TeamReadWithHeroes) +@app.get("/teams/{team_id}", response_model=TeamPublicWithHeroes) def read_team(*, team_id: int, session: Session = Depends(get_session)): team = session.get(Team, team_id) if not team: @@ -170,7 +170,7 @@ def read_team(*, team_id: int, session: Session = Depends(get_session)): return team -@app.patch("/teams/{team_id}", response_model=TeamRead) +@app.patch("/teams/{team_id}", response_model=TeamPublic) def update_team( *, session: Session = Depends(get_session), diff --git a/docs_src/tutorial/fastapi/relationships/tutorial001_py39.py b/docs_src/tutorial/fastapi/relationships/tutorial001_py39.py index 6ceae130a..a4e953c4d 100644 --- a/docs_src/tutorial/fastapi/relationships/tutorial001_py39.py +++ b/docs_src/tutorial/fastapi/relationships/tutorial001_py39.py @@ -19,7 +19,7 @@ class TeamCreate(TeamBase): pass -class TeamRead(TeamBase): +class TeamPublic(TeamBase): id: int @@ -43,7 +43,7 @@ class Hero(HeroBase, table=True): team: Optional[Team] = Relationship(back_populates="heroes") -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -58,12 +58,12 @@ class HeroUpdate(SQLModel): team_id: Optional[int] = None -class HeroReadWithTeam(HeroRead): - team: Optional[TeamRead] = None +class HeroPublicWithTeam(HeroPublic): + team: Optional[TeamPublic] = None -class TeamReadWithHeroes(TeamRead): - heroes: list[HeroRead] = [] +class TeamPublicWithHeroes(TeamPublic): + heroes: list[HeroPublic] = [] sqlite_file_name = "database.db" @@ -90,7 +90,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): db_hero = Hero.model_validate(hero) session.add(db_hero) @@ -99,7 +99,7 @@ def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes( *, session: Session = Depends(get_session), @@ -110,7 +110,7 @@ def read_heroes( return heroes -@app.get("/heroes/{hero_id}", response_model=HeroReadWithTeam) +@app.get("/heroes/{hero_id}", response_model=HeroPublicWithTeam) def read_hero(*, session: Session = Depends(get_session), hero_id: int): hero = session.get(Hero, hero_id) if not hero: @@ -118,7 +118,7 @@ def read_hero(*, session: Session = Depends(get_session), hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero( *, session: Session = Depends(get_session), hero_id: int, hero: HeroUpdate ): @@ -144,7 +144,7 @@ def delete_hero(*, session: Session = Depends(get_session), hero_id: int): return {"ok": True} -@app.post("/teams/", response_model=TeamRead) +@app.post("/teams/", response_model=TeamPublic) def create_team(*, session: Session = Depends(get_session), team: TeamCreate): db_team = Team.model_validate(team) session.add(db_team) @@ -153,7 +153,7 @@ def create_team(*, session: Session = Depends(get_session), team: TeamCreate): return db_team -@app.get("/teams/", response_model=list[TeamRead]) +@app.get("/teams/", response_model=list[TeamPublic]) def read_teams( *, session: Session = Depends(get_session), @@ -164,7 +164,7 @@ def read_teams( return teams -@app.get("/teams/{team_id}", response_model=TeamReadWithHeroes) +@app.get("/teams/{team_id}", response_model=TeamPublicWithHeroes) def read_team(*, team_id: int, session: Session = Depends(get_session)): team = session.get(Team, team_id) if not team: @@ -172,7 +172,7 @@ def read_team(*, team_id: int, session: Session = Depends(get_session)): return team -@app.patch("/teams/{team_id}", response_model=TeamRead) +@app.patch("/teams/{team_id}", response_model=TeamPublic) def update_team( *, session: Session = Depends(get_session), diff --git a/docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py b/docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py index 7014a7391..f46d8b078 100644 --- a/docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py +++ b/docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py @@ -18,7 +18,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -52,7 +52,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): db_hero = Hero.model_validate(hero) session.add(db_hero) @@ -61,7 +61,7 @@ def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=List[HeroRead]) +@app.get("/heroes/", response_model=List[HeroPublic]) def read_heroes( *, session: Session = Depends(get_session), @@ -72,7 +72,7 @@ def read_heroes( return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(*, session: Session = Depends(get_session), hero_id: int): hero = session.get(Hero, hero_id) if not hero: @@ -80,7 +80,7 @@ def read_hero(*, session: Session = Depends(get_session), hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero( *, session: Session = Depends(get_session), hero_id: int, hero: HeroUpdate ): diff --git a/docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py b/docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py index cf1bbb713..702eba722 100644 --- a/docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py +++ b/docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py310.py @@ -16,7 +16,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -50,7 +50,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): db_hero = Hero.model_validate(hero) session.add(db_hero) @@ -59,7 +59,7 @@ def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes( *, session: Session = Depends(get_session), @@ -70,7 +70,7 @@ def read_heroes( return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(*, session: Session = Depends(get_session), hero_id: int): hero = session.get(Hero, hero_id) if not hero: @@ -78,7 +78,7 @@ def read_hero(*, session: Session = Depends(get_session), hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero( *, session: Session = Depends(get_session), hero_id: int, hero: HeroUpdate ): diff --git a/docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py b/docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py index 9f428ab3e..efdfd8ee6 100644 --- a/docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py +++ b/docs_src/tutorial/fastapi/session_with_dependency/tutorial001_py39.py @@ -18,7 +18,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -52,7 +52,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): db_hero = Hero.model_validate(hero) session.add(db_hero) @@ -61,7 +61,7 @@ def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes( *, session: Session = Depends(get_session), @@ -72,7 +72,7 @@ def read_heroes( return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(*, session: Session = Depends(get_session), hero_id: int): hero = session.get(Hero, hero_id) if not hero: @@ -80,7 +80,7 @@ def read_hero(*, session: Session = Depends(get_session), hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero( *, session: Session = Depends(get_session), hero_id: int, hero: HeroUpdate ): diff --git a/docs_src/tutorial/fastapi/teams/tutorial001.py b/docs_src/tutorial/fastapi/teams/tutorial001.py index 785c52591..4289221ff 100644 --- a/docs_src/tutorial/fastapi/teams/tutorial001.py +++ b/docs_src/tutorial/fastapi/teams/tutorial001.py @@ -19,7 +19,7 @@ class TeamCreate(TeamBase): pass -class TeamRead(TeamBase): +class TeamPublic(TeamBase): id: int @@ -42,7 +42,7 @@ class Hero(HeroBase, table=True): team: Optional[Team] = Relationship(back_populates="heroes") -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -81,7 +81,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): db_hero = Hero.model_validate(hero) session.add(db_hero) @@ -90,7 +90,7 @@ def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=List[HeroRead]) +@app.get("/heroes/", response_model=List[HeroPublic]) def read_heroes( *, session: Session = Depends(get_session), @@ -101,7 +101,7 @@ def read_heroes( return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(*, session: Session = Depends(get_session), hero_id: int): hero = session.get(Hero, hero_id) if not hero: @@ -109,7 +109,7 @@ def read_hero(*, session: Session = Depends(get_session), hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero( *, session: Session = Depends(get_session), hero_id: int, hero: HeroUpdate ): @@ -135,7 +135,7 @@ def delete_hero(*, session: Session = Depends(get_session), hero_id: int): return {"ok": True} -@app.post("/teams/", response_model=TeamRead) +@app.post("/teams/", response_model=TeamPublic) def create_team(*, session: Session = Depends(get_session), team: TeamCreate): db_team = Team.model_validate(team) session.add(db_team) @@ -144,7 +144,7 @@ def create_team(*, session: Session = Depends(get_session), team: TeamCreate): return db_team -@app.get("/teams/", response_model=List[TeamRead]) +@app.get("/teams/", response_model=List[TeamPublic]) def read_teams( *, session: Session = Depends(get_session), @@ -155,7 +155,7 @@ def read_teams( return teams -@app.get("/teams/{team_id}", response_model=TeamRead) +@app.get("/teams/{team_id}", response_model=TeamPublic) def read_team(*, team_id: int, session: Session = Depends(get_session)): team = session.get(Team, team_id) if not team: @@ -163,7 +163,7 @@ def read_team(*, team_id: int, session: Session = Depends(get_session)): return team -@app.patch("/teams/{team_id}", response_model=TeamRead) +@app.patch("/teams/{team_id}", response_model=TeamPublic) def update_team( *, session: Session = Depends(get_session), diff --git a/docs_src/tutorial/fastapi/teams/tutorial001_py310.py b/docs_src/tutorial/fastapi/teams/tutorial001_py310.py index dea4bd8a9..630ae4f98 100644 --- a/docs_src/tutorial/fastapi/teams/tutorial001_py310.py +++ b/docs_src/tutorial/fastapi/teams/tutorial001_py310.py @@ -17,7 +17,7 @@ class TeamCreate(TeamBase): pass -class TeamRead(TeamBase): +class TeamPublic(TeamBase): id: int @@ -40,7 +40,7 @@ class Hero(HeroBase, table=True): team: Team | None = Relationship(back_populates="heroes") -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -79,7 +79,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): db_hero = Hero.model_validate(hero) session.add(db_hero) @@ -88,7 +88,7 @@ def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes( *, session: Session = Depends(get_session), @@ -99,7 +99,7 @@ def read_heroes( return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(*, session: Session = Depends(get_session), hero_id: int): hero = session.get(Hero, hero_id) if not hero: @@ -107,7 +107,7 @@ def read_hero(*, session: Session = Depends(get_session), hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero( *, session: Session = Depends(get_session), hero_id: int, hero: HeroUpdate ): @@ -133,7 +133,7 @@ def delete_hero(*, session: Session = Depends(get_session), hero_id: int): return {"ok": True} -@app.post("/teams/", response_model=TeamRead) +@app.post("/teams/", response_model=TeamPublic) def create_team(*, session: Session = Depends(get_session), team: TeamCreate): db_team = Team.model_validate(team) session.add(db_team) @@ -142,7 +142,7 @@ def create_team(*, session: Session = Depends(get_session), team: TeamCreate): return db_team -@app.get("/teams/", response_model=list[TeamRead]) +@app.get("/teams/", response_model=list[TeamPublic]) def read_teams( *, session: Session = Depends(get_session), @@ -153,7 +153,7 @@ def read_teams( return teams -@app.get("/teams/{team_id}", response_model=TeamRead) +@app.get("/teams/{team_id}", response_model=TeamPublic) def read_team(*, team_id: int, session: Session = Depends(get_session)): team = session.get(Team, team_id) if not team: @@ -161,7 +161,7 @@ def read_team(*, team_id: int, session: Session = Depends(get_session)): return team -@app.patch("/teams/{team_id}", response_model=TeamRead) +@app.patch("/teams/{team_id}", response_model=TeamPublic) def update_team( *, session: Session = Depends(get_session), diff --git a/docs_src/tutorial/fastapi/teams/tutorial001_py39.py b/docs_src/tutorial/fastapi/teams/tutorial001_py39.py index cc6429adc..661e43537 100644 --- a/docs_src/tutorial/fastapi/teams/tutorial001_py39.py +++ b/docs_src/tutorial/fastapi/teams/tutorial001_py39.py @@ -19,7 +19,7 @@ class TeamCreate(TeamBase): pass -class TeamRead(TeamBase): +class TeamPublic(TeamBase): id: int @@ -42,7 +42,7 @@ class Hero(HeroBase, table=True): team: Optional[Team] = Relationship(back_populates="heroes") -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -81,7 +81,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): db_hero = Hero.model_validate(hero) session.add(db_hero) @@ -90,7 +90,7 @@ def create_hero(*, session: Session = Depends(get_session), hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes( *, session: Session = Depends(get_session), @@ -101,7 +101,7 @@ def read_heroes( return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(*, session: Session = Depends(get_session), hero_id: int): hero = session.get(Hero, hero_id) if not hero: @@ -109,7 +109,7 @@ def read_hero(*, session: Session = Depends(get_session), hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero( *, session: Session = Depends(get_session), hero_id: int, hero: HeroUpdate ): @@ -135,7 +135,7 @@ def delete_hero(*, session: Session = Depends(get_session), hero_id: int): return {"ok": True} -@app.post("/teams/", response_model=TeamRead) +@app.post("/teams/", response_model=TeamPublic) def create_team(*, session: Session = Depends(get_session), team: TeamCreate): db_team = Team.model_validate(team) session.add(db_team) @@ -144,7 +144,7 @@ def create_team(*, session: Session = Depends(get_session), team: TeamCreate): return db_team -@app.get("/teams/", response_model=list[TeamRead]) +@app.get("/teams/", response_model=list[TeamPublic]) def read_teams( *, session: Session = Depends(get_session), @@ -155,7 +155,7 @@ def read_teams( return teams -@app.get("/teams/{team_id}", response_model=TeamRead) +@app.get("/teams/{team_id}", response_model=TeamPublic) def read_team(*, team_id: int, session: Session = Depends(get_session)): team = session.get(Team, team_id) if not team: @@ -163,7 +163,7 @@ def read_team(*, team_id: int, session: Session = Depends(get_session)): return team -@app.patch("/teams/{team_id}", response_model=TeamRead) +@app.patch("/teams/{team_id}", response_model=TeamPublic) def update_team( *, session: Session = Depends(get_session), diff --git a/docs_src/tutorial/fastapi/update/tutorial001.py b/docs_src/tutorial/fastapi/update/tutorial001.py index 5639638d5..6a02f6c01 100644 --- a/docs_src/tutorial/fastapi/update/tutorial001.py +++ b/docs_src/tutorial/fastapi/update/tutorial001.py @@ -18,7 +18,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -47,7 +47,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -57,14 +57,14 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=List[HeroRead]) +@app.get("/heroes/", response_model=List[HeroPublic]) def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): with Session(engine) as session: heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(hero_id: int): with Session(engine) as session: hero = session.get(Hero, hero_id) @@ -73,15 +73,14 @@ def read_hero(hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero(hero_id: int, hero: HeroUpdate): with Session(engine) as session: db_hero = session.get(Hero, hero_id) if not db_hero: raise HTTPException(status_code=404, detail="Hero not found") hero_data = hero.model_dump(exclude_unset=True) - for key, value in hero_data.items(): - setattr(db_hero, key, value) + db_hero.sqlmodel_update(hero_data) session.add(db_hero) session.commit() session.refresh(db_hero) diff --git a/docs_src/tutorial/fastapi/update/tutorial001_py310.py b/docs_src/tutorial/fastapi/update/tutorial001_py310.py index 4faf266f8..a98ee68fb 100644 --- a/docs_src/tutorial/fastapi/update/tutorial001_py310.py +++ b/docs_src/tutorial/fastapi/update/tutorial001_py310.py @@ -16,7 +16,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -45,7 +45,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -55,14 +55,14 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): with Session(engine) as session: heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(hero_id: int): with Session(engine) as session: hero = session.get(Hero, hero_id) @@ -71,15 +71,14 @@ def read_hero(hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero(hero_id: int, hero: HeroUpdate): with Session(engine) as session: db_hero = session.get(Hero, hero_id) if not db_hero: raise HTTPException(status_code=404, detail="Hero not found") hero_data = hero.model_dump(exclude_unset=True) - for key, value in hero_data.items(): - setattr(db_hero, key, value) + db_hero.sqlmodel_update(hero_data) session.add(db_hero) session.commit() session.refresh(db_hero) diff --git a/docs_src/tutorial/fastapi/update/tutorial001_py39.py b/docs_src/tutorial/fastapi/update/tutorial001_py39.py index b0daa8788..b6d62bf81 100644 --- a/docs_src/tutorial/fastapi/update/tutorial001_py39.py +++ b/docs_src/tutorial/fastapi/update/tutorial001_py39.py @@ -18,7 +18,7 @@ class HeroCreate(HeroBase): pass -class HeroRead(HeroBase): +class HeroPublic(HeroBase): id: int @@ -47,7 +47,7 @@ def on_startup(): create_db_and_tables() -@app.post("/heroes/", response_model=HeroRead) +@app.post("/heroes/", response_model=HeroPublic) def create_hero(hero: HeroCreate): with Session(engine) as session: db_hero = Hero.model_validate(hero) @@ -57,14 +57,14 @@ def create_hero(hero: HeroCreate): return db_hero -@app.get("/heroes/", response_model=list[HeroRead]) +@app.get("/heroes/", response_model=list[HeroPublic]) def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): with Session(engine) as session: heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes -@app.get("/heroes/{hero_id}", response_model=HeroRead) +@app.get("/heroes/{hero_id}", response_model=HeroPublic) def read_hero(hero_id: int): with Session(engine) as session: hero = session.get(Hero, hero_id) @@ -73,15 +73,14 @@ def read_hero(hero_id: int): return hero -@app.patch("/heroes/{hero_id}", response_model=HeroRead) +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) def update_hero(hero_id: int, hero: HeroUpdate): with Session(engine) as session: db_hero = session.get(Hero, hero_id) if not db_hero: raise HTTPException(status_code=404, detail="Hero not found") hero_data = hero.model_dump(exclude_unset=True) - for key, value in hero_data.items(): - setattr(db_hero, key, value) + db_hero.sqlmodel_update(hero_data) session.add(db_hero) session.commit() session.refresh(db_hero) diff --git a/docs_src/tutorial/fastapi/update/tutorial002.py b/docs_src/tutorial/fastapi/update/tutorial002.py new file mode 100644 index 000000000..c8838aeff --- /dev/null +++ b/docs_src/tutorial/fastapi/update/tutorial002.py @@ -0,0 +1,101 @@ +from typing import List, Optional + +from fastapi import FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class HeroBase(SQLModel): + name: str = Field(index=True) + secret_name: str + age: Optional[int] = Field(default=None, index=True) + + +class Hero(HeroBase, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + hashed_password: str = Field() + + +class HeroCreate(HeroBase): + password: str + + +class HeroPublic(HeroBase): + id: int + + +class HeroUpdate(SQLModel): + name: Optional[str] = None + secret_name: Optional[str] = None + age: Optional[int] = None + password: Optional[str] = None + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, echo=True, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def hash_password(password: str) -> str: + # Use something like passlib here + return f"not really hashed {password} hehehe" + + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/", response_model=HeroPublic) +def create_hero(hero: HeroCreate): + hashed_password = hash_password(hero.password) + with Session(engine) as session: + extra_data = {"hashed_password": hashed_password} + db_hero = Hero.model_validate(hero, update=extra_data) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + + +@app.get("/heroes/", response_model=List[HeroPublic]) +def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): + with Session(engine) as session: + 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): + with Session(engine) as 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): + with Session(engine) as session: + db_hero = session.get(Hero, hero_id) + if not db_hero: + raise HTTPException(status_code=404, detail="Hero not found") + hero_data = hero.model_dump(exclude_unset=True) + extra_data = {} + if "password" in hero_data: + password = hero_data["password"] + hashed_password = hash_password(password) + extra_data["hashed_password"] = hashed_password + db_hero.sqlmodel_update(hero_data, update=extra_data) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero diff --git a/docs_src/tutorial/fastapi/update/tutorial002_py310.py b/docs_src/tutorial/fastapi/update/tutorial002_py310.py new file mode 100644 index 000000000..d250fecf3 --- /dev/null +++ b/docs_src/tutorial/fastapi/update/tutorial002_py310.py @@ -0,0 +1,99 @@ +from fastapi import FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class HeroBase(SQLModel): + name: str = Field(index=True) + secret_name: str + age: int | None = Field(default=None, index=True) + + +class Hero(HeroBase, table=True): + id: int | None = Field(default=None, primary_key=True) + hashed_password: str = Field() + + +class HeroCreate(HeroBase): + password: str + + +class HeroPublic(HeroBase): + id: int + + +class HeroUpdate(SQLModel): + name: str | None = None + secret_name: str | None = None + age: int | None = None + password: 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, echo=True, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def hash_password(password: str) -> str: + # Use something like passlib here + return f"not really hashed {password} hehehe" + + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/", response_model=HeroPublic) +def create_hero(hero: HeroCreate): + hashed_password = hash_password(hero.password) + with Session(engine) as session: + extra_data = {"hashed_password": hashed_password} + db_hero = Hero.model_validate(hero, update=extra_data) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + + +@app.get("/heroes/", response_model=list[HeroPublic]) +def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): + with Session(engine) as session: + 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): + with Session(engine) as 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): + with Session(engine) as session: + db_hero = session.get(Hero, hero_id) + if not db_hero: + raise HTTPException(status_code=404, detail="Hero not found") + hero_data = hero.model_dump(exclude_unset=True) + extra_data = {} + if "password" in hero_data: + password = hero_data["password"] + hashed_password = hash_password(password) + extra_data["hashed_password"] = hashed_password + db_hero.sqlmodel_update(hero_data, update=extra_data) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero diff --git a/docs_src/tutorial/fastapi/update/tutorial002_py39.py b/docs_src/tutorial/fastapi/update/tutorial002_py39.py new file mode 100644 index 000000000..14ad1b482 --- /dev/null +++ b/docs_src/tutorial/fastapi/update/tutorial002_py39.py @@ -0,0 +1,101 @@ +from typing import Optional + +from fastapi import FastAPI, HTTPException, Query +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class HeroBase(SQLModel): + name: str = Field(index=True) + secret_name: str + age: Optional[int] = Field(default=None, index=True) + + +class Hero(HeroBase, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + hashed_password: str = Field() + + +class HeroCreate(HeroBase): + password: str + + +class HeroPublic(HeroBase): + id: int + + +class HeroUpdate(SQLModel): + name: Optional[str] = None + secret_name: Optional[str] = None + age: Optional[int] = None + password: Optional[str] = None + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +connect_args = {"check_same_thread": False} +engine = create_engine(sqlite_url, echo=True, connect_args=connect_args) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def hash_password(password: str) -> str: + # Use something like passlib here + return f"not really hashed {password} hehehe" + + +app = FastAPI() + + +@app.on_event("startup") +def on_startup(): + create_db_and_tables() + + +@app.post("/heroes/", response_model=HeroPublic) +def create_hero(hero: HeroCreate): + hashed_password = hash_password(hero.password) + with Session(engine) as session: + extra_data = {"hashed_password": hashed_password} + db_hero = Hero.model_validate(hero, update=extra_data) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero + + +@app.get("/heroes/", response_model=list[HeroPublic]) +def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): + with Session(engine) as session: + 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): + with Session(engine) as 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): + with Session(engine) as session: + db_hero = session.get(Hero, hero_id) + if not db_hero: + raise HTTPException(status_code=404, detail="Hero not found") + hero_data = hero.model_dump(exclude_unset=True) + extra_data = {} + if "password" in hero_data: + password = hero_data["password"] + hashed_password = hash_password(password) + extra_data["hashed_password"] = hashed_password + db_hero.sqlmodel_update(hero_data, update=extra_data) + session.add(db_hero) + session.commit() + session.refresh(db_hero) + return db_hero diff --git a/mkdocs.yml b/mkdocs.yml index ce98f1524..fa85062a8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -90,6 +90,7 @@ nav: - tutorial/fastapi/read-one.md - tutorial/fastapi/limit-and-offset.md - tutorial/fastapi/update.md + - tutorial/fastapi/update-extra-data.md - tutorial/fastapi/delete.md - tutorial/fastapi/session-with-dependency.md - tutorial/fastapi/teams.md diff --git a/pdm_build.py b/pdm_build.py new file mode 100644 index 000000000..232467015 --- /dev/null +++ b/pdm_build.py @@ -0,0 +1,39 @@ +import os +from typing import Any, Dict, List + +from pdm.backend.hooks import Context + +TIANGOLO_BUILD_PACKAGE = os.getenv("TIANGOLO_BUILD_PACKAGE", "sqlmodel") + + +def pdm_build_initialize(context: Context) -> None: + metadata = context.config.metadata + # Get custom config for the current package, from the env var + config: Dict[str, Any] = context.config.data["tool"]["tiangolo"][ + "_internal-slim-build" + ]["packages"][TIANGOLO_BUILD_PACKAGE] + project_config: Dict[str, Any] = config["project"] + # Get main optional dependencies, extras + optional_dependencies: Dict[str, List[str]] = metadata.get( + "optional-dependencies", {} + ) + # Get custom optional dependencies name to always include in this (non-slim) package + include_optional_dependencies: List[str] = config.get( + "include-optional-dependencies", [] + ) + # Override main [project] configs with custom configs for this package + for key, value in project_config.items(): + metadata[key] = value + # Get custom build config for the current package + build_config: Dict[str, Any] = ( + config.get("tool", {}).get("pdm", {}).get("build", {}) + ) + # Override PDM build config with custom build config for this package + for key, value in build_config.items(): + context.config.build_config[key] = value + # Get main dependencies + dependencies: List[str] = metadata.get("dependencies", []) + # Add optional dependencies to the default dependencies for this (non-slim) package + for include_optional in include_optional_dependencies: + optional_dependencies_group = optional_dependencies.get(include_optional, []) + dependencies.extend(optional_dependencies_group) diff --git a/pyproject.toml b/pyproject.toml index 10d73793d..ade520ab4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,17 @@ -[tool.poetry] +[build-system] +requires = ["pdm-backend"] +build-backend = "pdm.backend" + +[project] name = "sqlmodel" -version = "0" +dynamic = ["version"] description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." -authors = ["Sebastián Ramírez "] readme = "README.md" -homepage = "https://github.com/tiangolo/sqlmodel" -documentation = "https://sqlmodel.tiangolo.com" -repository = "https://github.com/tiangolo/sqlmodel" -license = "MIT" -exclude = ["sqlmodel/sql/expression.py.jinja2"] +requires-python = ">=3.7" +authors = [ + { name = "Sebastián Ramírez", email = "tiangolo@gmail.com" }, +] + classifiers = [ "Development Status :: 4 - Beta", "Framework :: AsyncIO", @@ -31,36 +34,40 @@ classifiers = [ "Typing :: Typed", ] -[tool.poetry.dependencies] -python = "^3.7" -SQLAlchemy = ">=2.0.0,<2.1.0" -pydantic = ">=1.10.13,<3.0.0" - -[tool.poetry.group.dev.dependencies] -pytest = "^7.0.1" -mypy = "1.4.1" -# Needed by the code generator using templates -black = ">=22.10,<24.0" -mkdocs-material = "9.2.7" -pillow = "^9.3.0" -cairosvg = "^2.5.2" -mdx-include = "^1.4.1" -coverage = {extras = ["toml"], version = ">=6.2,<8.0"} -fastapi = "^0.103.2" -ruff = "^0.1.2" -# For FastAPI tests -httpx = "0.24.1" -# TODO: upgrade when deprecating Python 3.7 -dirty-equals = "^0.6.0" -typer-cli = "^0.0.13" -mkdocs-markdownextradata-plugin = ">=0.1.7,<0.3.0" +dependencies = [ + "SQLAlchemy >=2.0.0,<2.1.0", + "pydantic >=1.10.13,<3.0.0", +] -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +[project.urls] +Homepage = "https://github.com/tiangolo/sqlmodel" +Documentation = "https://sqlmodel.tiangolo.com" +Repository = "https://github.com/tiangolo/sqlmodel" + +[tool.pdm] +version = { source = "file", path = "sqlmodel/__init__.py" } +distribution = true + +[tool.pdm.build] +source-includes = [ + "tests/", + "docs_src/", + "requirements*.txt", + "scripts/", + "sqlmodel/sql/expression.py.jinja2", + ] + +[tool.tiangolo._internal-slim-build.packages.sqlmodel-slim.project] +name = "sqlmodel-slim" + +[tool.tiangolo._internal-slim-build.packages.sqlmodel] +# include-optional-dependencies = ["standard"] + +[tool.tiangolo._internal-slim-build.packages.sqlmodel.project] +optional-dependencies = {} -[tool.poetry-version-plugin] -source = "init" +# [tool.tiangolo._internal-slim-build.packages.sqlmodel.project.scripts] +# sqlmodel = "sqlmodel.cli:main" [tool.coverage.run] parallel = true @@ -92,14 +99,14 @@ disallow_incomplete_defs = false disallow_untyped_defs = false disallow_untyped_calls = false -[tool.ruff] +[tool.ruff.lint] select = [ "E", # pycodestyle errors "W", # pycodestyle warnings "F", # pyflakes "I", # isort - "C", # flake8-comprehensions "B", # flake8-bugbear + "C4", # flake8-comprehensions "UP", # pyupgrade ] ignore = [ @@ -109,12 +116,12 @@ ignore = [ "W191", # indentation contains tabs ] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] # "__init__.py" = ["F401"] -[tool.ruff.isort] +[tool.ruff.lint.isort] known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"] -[tool.ruff.pyupgrade] +[tool.ruff.lint.pyupgrade] # Preserve types, even if a file imports `from __future__ import annotations`. keep-runtime-typing = true diff --git a/requirements-docs-tests.txt b/requirements-docs-tests.txt new file mode 100644 index 000000000..28f1ad1be --- /dev/null +++ b/requirements-docs-tests.txt @@ -0,0 +1,2 @@ +# For mkdocstrings and code generator using templates +black >=22.10,<24.0 diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 000000000..cacb5dc2a --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,18 @@ +-e . +-r requirements-docs-tests.txt +mkdocs-material==9.4.7 +mdx-include >=1.4.1,<2.0.0 +mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0 +mkdocs-redirects>=1.2.1,<1.3.0 +pyyaml >=5.3.1,<7.0.0 +# For Material for MkDocs, Chinese search +jieba==0.42.1 +# For image processing by Material for MkDocs +pillow==10.1.0 +# For image processing by Material for MkDocs +cairosvg==2.7.0 +mkdocstrings[python]==0.23.0 +griffe-typingdoc==0.2.2 +# For griffe, it formats with black +black==23.3.0 +typer == 0.12.3 diff --git a/requirements-tests.txt b/requirements-tests.txt new file mode 100644 index 000000000..648f99b1c --- /dev/null +++ b/requirements-tests.txt @@ -0,0 +1,12 @@ +-e . +-r requirements-docs-tests.txt +pytest >=7.0.1,<8.0.0 +coverage[toml] >=6.2,<8.0 +mypy ==1.4.1 +ruff ==0.2.0 +# For FastAPI tests +fastapi >=0.103.2 +httpx ==0.24.1 +# TODO: upgrade when deprecating Python 3.7 +dirty-equals ==0.6.0 +jinja2 ==3.1.3 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..1e21c5d2f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +-e . + +-r requirements-tests.txt +-r requirements-docs.txt + +pre-commit >=2.17.0,<4.0.0 diff --git a/scripts/docs-live.sh b/scripts/docs-live.sh deleted file mode 100755 index b6071fdb9..000000000 --- a/scripts/docs-live.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -set -e - -export DYLD_FALLBACK_LIBRARY_PATH="/opt/homebrew/lib" - -LINENUMS="true" mkdocs serve --dev-addr 127.0.0.1:8008 diff --git a/scripts/format.sh b/scripts/format.sh index 70c12e579..841438158 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -1,5 +1,5 @@ #!/bin/sh -e set -x -ruff sqlmodel tests docs_src scripts --fix +ruff check sqlmodel tests docs_src scripts --fix ruff format sqlmodel tests docs_src scripts diff --git a/scripts/lint.sh b/scripts/lint.sh index f66882239..4b279b5fc 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -4,5 +4,5 @@ set -e set -x mypy sqlmodel -ruff sqlmodel tests docs_src scripts +ruff check sqlmodel tests docs_src scripts ruff format sqlmodel tests docs_src --check diff --git a/scripts/publish.sh b/scripts/publish.sh deleted file mode 100755 index 7a9a12710..000000000 --- a/scripts/publish.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -set -e - -python -m poetry publish --build diff --git a/scripts/test-files.sh b/scripts/test-files.sh deleted file mode 100755 index 36579ce7b..000000000 --- a/scripts/test-files.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -x - -# Check README.md is up to date -diff --brief docs/index.md README.md diff --git a/scripts/zip-docs.sh b/scripts/zip-docs.sh deleted file mode 100644 index 69315f5dd..000000000 --- a/scripts/zip-docs.sh +++ /dev/null @@ -1,11 +0,0 @@ -#! /usr/bin/env bash - -set -x -set -e - -cd ./site - -if [ -f docs.zip ]; then - rm -rf docs.zip -fi -zip -r docs.zip ./ diff --git a/sqlmodel/__init__.py b/sqlmodel/__init__.py index 2df3afb51..397c07f5d 100644 --- a/sqlmodel/__init__.py +++ b/sqlmodel/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.0.14" +__version__ = "0.0.18" # Re-export from SQLAlchemy from sqlalchemy.engine import create_engine as create_engine diff --git a/sqlmodel/_compat.py b/sqlmodel/_compat.py index 2a2caca3e..72ec8330f 100644 --- a/sqlmodel/_compat.py +++ b/sqlmodel/_compat.py @@ -6,6 +6,7 @@ TYPE_CHECKING, AbstractSet, Any, + Callable, Dict, ForwardRef, Generator, @@ -17,10 +18,13 @@ Union, ) -from pydantic import VERSION as PYDANTIC_VERSION +from pydantic import VERSION as P_VERSION +from pydantic import BaseModel from pydantic.fields import FieldInfo from typing_extensions import get_args, get_origin +# Reassign variable to make it reexported for mypy +PYDANTIC_VERSION = P_VERSION IS_PYDANTIC_V2 = PYDANTIC_VERSION.startswith("2.") @@ -46,9 +50,11 @@ class ObjectWithUpdateWrapper: update: Dict[str, Any] def __getattribute__(self, __name: str) -> Any: - if __name in self.update: - return self.update[__name] - return getattr(self.obj, __name) + update = super().__getattribute__("update") + obj = super().__getattribute__("obj") + if __name in update: + return update[__name] + return getattr(obj, __name) def _is_union_type(t: Any) -> bool: @@ -94,13 +100,18 @@ def set_config_value( ) -> None: model.model_config[parameter] = value # type: ignore[literal-required] - def get_model_fields(model: InstanceOrType["SQLModel"]) -> Dict[str, "FieldInfo"]: + def get_model_fields(model: InstanceOrType[BaseModel]) -> Dict[str, "FieldInfo"]: return model.model_fields - def set_fields_set( - new_object: InstanceOrType["SQLModel"], fields: Set["FieldInfo"] - ) -> None: - object.__setattr__(new_object, "__pydantic_fields_set__", fields) + def get_fields_set( + object: InstanceOrType["SQLModel"], + ) -> Union[Set[str], Callable[[BaseModel], Set[str]]]: + return object.model_fields_set + + def init_pydantic_private_attrs(new_object: InstanceOrType["SQLModel"]) -> None: + object.__setattr__(new_object, "__pydantic_fields_set__", set()) + object.__setattr__(new_object, "__pydantic_extra__", None) + object.__setattr__(new_object, "__pydantic_private__", None) def get_annotations(class_dict: Dict[str, Any]) -> Dict[str, Any]: return class_dict.get("__annotations__", {}) @@ -384,13 +395,16 @@ def set_config_value( ) -> None: setattr(model.__config__, parameter, value) # type: ignore - def get_model_fields(model: InstanceOrType["SQLModel"]) -> Dict[str, "FieldInfo"]: + def get_model_fields(model: InstanceOrType[BaseModel]) -> Dict[str, "FieldInfo"]: return model.__fields__ # type: ignore - def set_fields_set( - new_object: InstanceOrType["SQLModel"], fields: Set["FieldInfo"] - ) -> None: - object.__setattr__(new_object, "__fields_set__", fields) + def get_fields_set( + object: InstanceOrType["SQLModel"], + ) -> Union[Set[str], Callable[[BaseModel], Set[str]]]: + return object.__fields_set__ + + def init_pydantic_private_attrs(new_object: InstanceOrType["SQLModel"]) -> None: + object.__setattr__(new_object, "__fields_set__", set()) def get_annotations(class_dict: Dict[str, Any]) -> Dict[str, Any]: return resolve_annotations( # type: ignore[no-any-return] diff --git a/sqlmodel/main.py b/sqlmodel/main.py index 10064c711..a16428b19 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -6,6 +6,7 @@ from enum import Enum from pathlib import Path from typing import ( + TYPE_CHECKING, AbstractSet, Any, Callable, @@ -55,6 +56,7 @@ from ._compat import ( # type: ignore[attr-defined] IS_PYDANTIC_V2, + PYDANTIC_VERSION, BaseConfig, ModelField, ModelMetaclass, @@ -70,16 +72,22 @@ get_model_fields, get_relationship_to, get_type_from_field, + init_pydantic_private_attrs, is_field_noneable, is_table_model_class, post_init_field_info, set_config_value, - set_fields_set, sqlmodel_init, sqlmodel_validate, ) from .sql.sqltypes import GUID, AutoString +if TYPE_CHECKING: + from pydantic._internal._model_construction import ModelMetaclass as ModelMetaclass + from pydantic._internal._repr import Representation as Representation + from pydantic_core import PydanticUndefined as Undefined + from pydantic_core import PydanticUndefinedType as UndefinedType + _T = TypeVar("_T") NoArgAnyCallable = Callable[[], Any] IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any], None] @@ -686,12 +694,12 @@ class Config: def __new__(cls, *args: Any, **kwargs: Any) -> Any: new_object = super().__new__(cls) - # SQLAlchemy doesn't call __init__ on the base class + # SQLAlchemy doesn't call __init__ on the base class when querying from DB # Ref: https://docs.sqlalchemy.org/en/14/orm/constructors.html # Set __fields_set__ here, that would have been set when calling __init__ # in the Pydantic model so that when SQLAlchemy sets attributes that are # added (e.g. when querying from DB) to the __fields_set__, this already exists - set_fields_set(new_object, set()) + init_pydantic_private_attrs(new_object) return new_object def __init__(__pydantic_self__, **data: Any) -> None: @@ -758,20 +766,28 @@ def model_validate( update=update, ) - # TODO: remove when deprecating Pydantic v1, only for compatibility def model_dump( self, *, mode: Union[Literal["json", "python"], str] = "python", include: IncEx = None, exclude: IncEx = None, + context: Union[Dict[str, Any], None] = None, by_alias: bool = False, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, round_trip: bool = False, - warnings: bool = True, + warnings: Union[bool, Literal["none", "warn", "error"]] = True, + serialize_as_any: bool = False, ) -> Dict[str, Any]: + if PYDANTIC_VERSION >= "2.7.0": + extra_kwargs: Dict[str, Any] = { + "context": context, + "serialize_as_any": serialize_as_any, + } + else: + extra_kwargs = {} if IS_PYDANTIC_V2: return super().model_dump( mode=mode, @@ -783,6 +799,7 @@ def model_dump( exclude_none=exclude_none, round_trip=round_trip, warnings=warnings, + **extra_kwargs, ) else: return super().dict( @@ -869,3 +886,32 @@ def _calculate_keys( exclude_unset=exclude_unset, update=update, ) + + def sqlmodel_update( + self: _TSQLModel, + obj: Union[Dict[str, Any], BaseModel], + *, + update: Union[Dict[str, Any], None] = None, + ) -> _TSQLModel: + use_update = (update or {}).copy() + if isinstance(obj, dict): + for key, value in {**obj, **use_update}.items(): + if key in get_model_fields(self): + setattr(self, key, value) + elif isinstance(obj, BaseModel): + for key in get_model_fields(obj): + if key in use_update: + value = use_update.pop(key) + else: + value = getattr(obj, key) + setattr(self, key, value) + for remaining_key in use_update: + if remaining_key in get_model_fields(self): + value = use_update.pop(remaining_key) + setattr(self, remaining_key, value) + else: + raise ValueError( + "Can't use sqlmodel_update() with something that " + f"is not a dict or SQLModel or Pydantic model: {obj}" + ) + return self diff --git a/tests/test_fields_set.py b/tests/test_fields_set.py index 56f4ad014..e0bd8cba7 100644 --- a/tests/test_fields_set.py +++ b/tests/test_fields_set.py @@ -1,6 +1,7 @@ from datetime import datetime, timedelta from sqlmodel import Field, SQLModel +from sqlmodel._compat import get_fields_set def test_fields_set(): @@ -10,12 +11,12 @@ class User(SQLModel): last_updated: datetime = Field(default_factory=datetime.now) user = User(username="bob") - assert user.__fields_set__ == {"username"} + assert get_fields_set(user) == {"username"} user = User(username="bob", email="bob@test.com") - assert user.__fields_set__ == {"username", "email"} + assert get_fields_set(user) == {"username", "email"} user = User( username="bob", email="bob@test.com", last_updated=datetime.now() - timedelta(days=1), ) - assert user.__fields_set__ == {"username", "email", "last_updated"} + assert get_fields_set(user) == {"username", "email", "last_updated"} diff --git a/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001.py index 706cc8aed..f293199b4 100644 --- a/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001.py @@ -99,7 +99,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -136,7 +136,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -172,7 +172,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -244,7 +244,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -297,8 +297,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001_py310.py b/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001_py310.py index 46c8c42dd..2757c878b 100644 --- a/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001_py310.py @@ -102,7 +102,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -139,7 +139,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -175,7 +175,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -247,7 +247,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -300,8 +300,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001_py39.py b/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001_py39.py index e2874c109..3299086bd 100644 --- a/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001_py39.py +++ b/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001_py39.py @@ -102,7 +102,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -139,7 +139,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -175,7 +175,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -247,7 +247,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -300,8 +300,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001.py index d177c80c4..4047539f0 100644 --- a/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001.py @@ -104,7 +104,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -141,7 +141,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -177,7 +177,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -230,8 +230,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001_py310.py b/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001_py310.py index 03086996c..480b92a12 100644 --- a/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001_py310.py @@ -107,7 +107,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -144,7 +144,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -180,7 +180,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -233,8 +233,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001_py39.py b/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001_py39.py index f7e42e4e2..0a9d5c9ef 100644 --- a/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001_py39.py +++ b/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001_py39.py @@ -107,7 +107,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -144,7 +144,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -180,7 +180,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -233,8 +233,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001.py index 2ebfc0c0d..276a021c5 100644 --- a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001.py @@ -74,7 +74,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -101,7 +101,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -154,8 +154,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["id", "name", "secret_name"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001_py310.py b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001_py310.py index c17e48292..b6f082a0f 100644 --- a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001_py310.py @@ -76,7 +76,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -103,7 +103,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -156,8 +156,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["id", "name", "secret_name"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001_py39.py b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001_py39.py index 258b3a4e5..82365ced6 100644 --- a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001_py39.py +++ b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001_py39.py @@ -77,7 +77,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -104,7 +104,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -157,8 +157,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["id", "name", "secret_name"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002.py b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002.py index 47f2e6415..8327c6d56 100644 --- a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002.py +++ b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002.py @@ -74,7 +74,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -101,7 +101,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -154,8 +154,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002_py310.py b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002_py310.py index c09b15bd5..30edc4dea 100644 --- a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002_py310.py +++ b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002_py310.py @@ -77,7 +77,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -104,7 +104,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -157,8 +157,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002_py39.py b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002_py39.py index 8ad0f271e..2b86d3fac 100644 --- a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002_py39.py +++ b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002_py39.py @@ -77,7 +77,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -104,7 +104,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -157,8 +157,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001.py index 62fbb25a9..9b1d52756 100644 --- a/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001.py @@ -59,7 +59,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -86,7 +86,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -122,7 +122,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -175,8 +175,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001_py310.py b/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001_py310.py index 913d09888..f18b0d65c 100644 --- a/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001_py310.py @@ -62,7 +62,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -89,7 +89,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -125,7 +125,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -178,8 +178,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001_py39.py b/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001_py39.py index 9bedf5c62..4423d1a71 100644 --- a/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001_py39.py +++ b/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001_py39.py @@ -62,7 +62,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -89,7 +89,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -125,7 +125,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -178,8 +178,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001.py index b301697db..4b4f47b76 100644 --- a/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001.py @@ -147,7 +147,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -184,7 +184,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -220,7 +220,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroReadWithTeam" + "$ref": "#/components/schemas/HeroPublicWithTeam" } } }, @@ -292,7 +292,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -346,7 +346,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Teams Teams Get", "type": "array", "items": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" }, } } @@ -383,7 +383,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" } } }, @@ -419,7 +419,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamReadWithHeroes" + "$ref": "#/components/schemas/TeamPublicWithHeroes" } } }, @@ -491,7 +491,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" } } }, @@ -554,8 +554,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { @@ -584,8 +584,8 @@ def test_tutorial(clear_sqlmodel): "id": {"title": "Id", "type": "integer"}, }, }, - "HeroReadWithTeam": { - "title": "HeroReadWithTeam", + "HeroPublicWithTeam": { + "title": "HeroPublicWithTeam", "required": ["name", "secret_name", "id"], "type": "object", "properties": { @@ -615,14 +615,14 @@ def test_tutorial(clear_sqlmodel): "team": IsDict( { "anyOf": [ - {"$ref": "#/components/schemas/TeamRead"}, + {"$ref": "#/components/schemas/TeamPublic"}, {"type": "null"}, ] } ) | IsDict( # TODO: remove when deprecating Pydantic v1 - {"$ref": "#/components/schemas/TeamRead"} + {"$ref": "#/components/schemas/TeamPublic"} ), }, }, @@ -681,8 +681,8 @@ def test_tutorial(clear_sqlmodel): "headquarters": {"title": "Headquarters", "type": "string"}, }, }, - "TeamRead": { - "title": "TeamRead", + "TeamPublic": { + "title": "TeamPublic", "required": ["name", "headquarters", "id"], "type": "object", "properties": { @@ -691,8 +691,8 @@ def test_tutorial(clear_sqlmodel): "id": {"title": "Id", "type": "integer"}, }, }, - "TeamReadWithHeroes": { - "title": "TeamReadWithHeroes", + "TeamPublicWithHeroes": { + "title": "TeamPublicWithHeroes", "required": ["name", "headquarters", "id"], "type": "object", "properties": { @@ -702,7 +702,7 @@ def test_tutorial(clear_sqlmodel): "heroes": { "title": "Heroes", "type": "array", - "items": {"$ref": "#/components/schemas/HeroRead"}, + "items": {"$ref": "#/components/schemas/HeroPublic"}, "default": [], }, }, diff --git a/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001_py310.py b/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001_py310.py index 4d310a87e..dcb78f597 100644 --- a/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001_py310.py @@ -150,7 +150,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -187,7 +187,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -223,7 +223,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroReadWithTeam" + "$ref": "#/components/schemas/HeroPublicWithTeam" } } }, @@ -295,7 +295,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -349,7 +349,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Teams Teams Get", "type": "array", "items": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" }, } } @@ -386,7 +386,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" } } }, @@ -422,7 +422,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamReadWithHeroes" + "$ref": "#/components/schemas/TeamPublicWithHeroes" } } }, @@ -494,7 +494,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" } } }, @@ -557,8 +557,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { @@ -587,8 +587,8 @@ def test_tutorial(clear_sqlmodel): "id": {"title": "Id", "type": "integer"}, }, }, - "HeroReadWithTeam": { - "title": "HeroReadWithTeam", + "HeroPublicWithTeam": { + "title": "HeroPublicWithTeam", "required": ["name", "secret_name", "id"], "type": "object", "properties": { @@ -618,14 +618,14 @@ def test_tutorial(clear_sqlmodel): "team": IsDict( { "anyOf": [ - {"$ref": "#/components/schemas/TeamRead"}, + {"$ref": "#/components/schemas/TeamPublic"}, {"type": "null"}, ] } ) | IsDict( # TODO: remove when deprecating Pydantic v1 - {"$ref": "#/components/schemas/TeamRead"} + {"$ref": "#/components/schemas/TeamPublic"} ), }, }, @@ -684,8 +684,8 @@ def test_tutorial(clear_sqlmodel): "headquarters": {"title": "Headquarters", "type": "string"}, }, }, - "TeamRead": { - "title": "TeamRead", + "TeamPublic": { + "title": "TeamPublic", "required": ["name", "headquarters", "id"], "type": "object", "properties": { @@ -694,8 +694,8 @@ def test_tutorial(clear_sqlmodel): "id": {"title": "Id", "type": "integer"}, }, }, - "TeamReadWithHeroes": { - "title": "TeamReadWithHeroes", + "TeamPublicWithHeroes": { + "title": "TeamPublicWithHeroes", "required": ["name", "headquarters", "id"], "type": "object", "properties": { @@ -705,7 +705,7 @@ def test_tutorial(clear_sqlmodel): "heroes": { "title": "Heroes", "type": "array", - "items": {"$ref": "#/components/schemas/HeroRead"}, + "items": {"$ref": "#/components/schemas/HeroPublic"}, "default": [], }, }, diff --git a/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001_py39.py b/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001_py39.py index 0603739c4..5ef7338d4 100644 --- a/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001_py39.py +++ b/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001_py39.py @@ -150,7 +150,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -187,7 +187,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -223,7 +223,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroReadWithTeam" + "$ref": "#/components/schemas/HeroPublicWithTeam" } } }, @@ -295,7 +295,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -349,7 +349,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Teams Teams Get", "type": "array", "items": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" }, } } @@ -386,7 +386,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" } } }, @@ -422,7 +422,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamReadWithHeroes" + "$ref": "#/components/schemas/TeamPublicWithHeroes" } } }, @@ -494,7 +494,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" } } }, @@ -557,8 +557,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { @@ -587,8 +587,8 @@ def test_tutorial(clear_sqlmodel): "id": {"title": "Id", "type": "integer"}, }, }, - "HeroReadWithTeam": { - "title": "HeroReadWithTeam", + "HeroPublicWithTeam": { + "title": "HeroPublicWithTeam", "required": ["name", "secret_name", "id"], "type": "object", "properties": { @@ -618,14 +618,14 @@ def test_tutorial(clear_sqlmodel): "team": IsDict( { "anyOf": [ - {"$ref": "#/components/schemas/TeamRead"}, + {"$ref": "#/components/schemas/TeamPublic"}, {"type": "null"}, ] } ) | IsDict( # TODO: remove when deprecating Pydantic v1 - {"$ref": "#/components/schemas/TeamRead"} + {"$ref": "#/components/schemas/TeamPublic"} ), }, }, @@ -684,8 +684,8 @@ def test_tutorial(clear_sqlmodel): "headquarters": {"title": "Headquarters", "type": "string"}, }, }, - "TeamRead": { - "title": "TeamRead", + "TeamPublic": { + "title": "TeamPublic", "required": ["name", "headquarters", "id"], "type": "object", "properties": { @@ -694,8 +694,8 @@ def test_tutorial(clear_sqlmodel): "id": {"title": "Id", "type": "integer"}, }, }, - "TeamReadWithHeroes": { - "title": "TeamReadWithHeroes", + "TeamPublicWithHeroes": { + "title": "TeamPublicWithHeroes", "required": ["name", "headquarters", "id"], "type": "object", "properties": { @@ -705,7 +705,7 @@ def test_tutorial(clear_sqlmodel): "heroes": { "title": "Heroes", "type": "array", - "items": {"$ref": "#/components/schemas/HeroRead"}, + "items": {"$ref": "#/components/schemas/HeroPublic"}, "default": [], }, }, diff --git a/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001.py index 441cc42b2..388cfa9b2 100644 --- a/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001.py @@ -99,7 +99,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -136,7 +136,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -172,7 +172,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -244,7 +244,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -297,8 +297,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001_py310.py b/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001_py310.py index 7c427a1c6..65bab4773 100644 --- a/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001_py310.py @@ -104,7 +104,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -141,7 +141,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -177,7 +177,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -249,7 +249,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -302,8 +302,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001_py39.py b/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001_py39.py index ea63f52c4..cdab85df1 100644 --- a/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001_py39.py +++ b/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001_py39.py @@ -104,7 +104,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -141,7 +141,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -177,7 +177,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -249,7 +249,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -302,8 +302,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001.py index a532625d4..25daadf74 100644 --- a/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001.py @@ -134,7 +134,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -171,7 +171,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -207,7 +207,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -279,7 +279,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -333,7 +333,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Teams Teams Get", "type": "array", "items": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" }, } } @@ -370,7 +370,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" } } }, @@ -406,7 +406,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" } } }, @@ -478,7 +478,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" } } }, @@ -541,8 +541,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { @@ -626,8 +626,8 @@ def test_tutorial(clear_sqlmodel): "headquarters": {"title": "Headquarters", "type": "string"}, }, }, - "TeamRead": { - "title": "TeamRead", + "TeamPublic": { + "title": "TeamPublic", "required": ["name", "headquarters", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001_py310.py b/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001_py310.py index 33029f6b6..63f8a1d70 100644 --- a/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001_py310.py @@ -137,7 +137,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -174,7 +174,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -210,7 +210,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -282,7 +282,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -336,7 +336,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Teams Teams Get", "type": "array", "items": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" }, } } @@ -373,7 +373,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" } } }, @@ -409,7 +409,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" } } }, @@ -481,7 +481,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" } } }, @@ -544,8 +544,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { @@ -629,8 +629,8 @@ def test_tutorial(clear_sqlmodel): "headquarters": {"title": "Headquarters", "type": "string"}, }, }, - "TeamRead": { - "title": "TeamRead", + "TeamPublic": { + "title": "TeamPublic", "required": ["name", "headquarters", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001_py39.py b/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001_py39.py index 66705e17c..30b68e0ed 100644 --- a/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001_py39.py +++ b/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001_py39.py @@ -137,7 +137,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -174,7 +174,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -210,7 +210,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -282,7 +282,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -336,7 +336,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Teams Teams Get", "type": "array", "items": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" }, } } @@ -373,7 +373,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" } } }, @@ -409,7 +409,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" } } }, @@ -481,7 +481,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TeamRead" + "$ref": "#/components/schemas/TeamPublic" } } }, @@ -544,8 +544,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { @@ -629,8 +629,8 @@ def test_tutorial(clear_sqlmodel): "headquarters": {"title": "Headquarters", "type": "string"}, }, }, - "TeamRead": { - "title": "TeamRead", + "TeamPublic": { + "title": "TeamPublic", "required": ["name", "headquarters", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_update/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_update/test_tutorial001.py index 973ab2db0..0856b2467 100644 --- a/tests/test_tutorial/test_fastapi/test_update/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_update/test_tutorial001.py @@ -106,7 +106,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -143,7 +143,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -179,7 +179,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -223,7 +223,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -276,8 +276,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_update/test_tutorial001_py310.py b/tests/test_tutorial/test_fastapi/test_update/test_tutorial001_py310.py index 090af8c60..d79b2ecea 100644 --- a/tests/test_tutorial/test_fastapi/test_update/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_fastapi/test_update/test_tutorial001_py310.py @@ -109,7 +109,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -146,7 +146,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -182,7 +182,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -226,7 +226,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -279,8 +279,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_update/test_tutorial001_py39.py b/tests/test_tutorial/test_fastapi/test_update/test_tutorial001_py39.py index 22dfb8f26..1be81dec2 100644 --- a/tests/test_tutorial/test_fastapi/test_update/test_tutorial001_py39.py +++ b/tests/test_tutorial/test_fastapi/test_update/test_tutorial001_py39.py @@ -109,7 +109,7 @@ def test_tutorial(clear_sqlmodel): "title": "Response Read Heroes Heroes Get", "type": "array", "items": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" }, } } @@ -146,7 +146,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -182,7 +182,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -226,7 +226,7 @@ def test_tutorial(clear_sqlmodel): "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HeroRead" + "$ref": "#/components/schemas/HeroPublic" } } }, @@ -279,8 +279,8 @@ def test_tutorial(clear_sqlmodel): ), }, }, - "HeroRead": { - "title": "HeroRead", + "HeroPublic": { + "title": "HeroPublic", "required": ["name", "secret_name", "id"], "type": "object", "properties": { diff --git a/tests/test_tutorial/test_fastapi/test_update/test_tutorial002.py b/tests/test_tutorial/test_fastapi/test_update/test_tutorial002.py new file mode 100644 index 000000000..32e343ed6 --- /dev/null +++ b/tests/test_tutorial/test_fastapi/test_update/test_tutorial002.py @@ -0,0 +1,427 @@ +from dirty_equals import IsDict +from fastapi.testclient import TestClient +from sqlmodel import Session, create_engine +from sqlmodel.pool import StaticPool + + +def test_tutorial(clear_sqlmodel): + from docs_src.tutorial.fastapi.update import tutorial002 as mod + + mod.sqlite_url = "sqlite://" + mod.engine = create_engine( + mod.sqlite_url, connect_args=mod.connect_args, poolclass=StaticPool + ) + + with TestClient(mod.app) as client: + hero1_data = { + "name": "Deadpond", + "secret_name": "Dive Wilson", + "password": "chimichanga", + } + hero2_data = { + "name": "Spider-Boy", + "secret_name": "Pedro Parqueador", + "id": 9000, + "password": "auntmay", + } + hero3_data = { + "name": "Rusty-Man", + "secret_name": "Tommy Sharp", + "age": 48, + "password": "bestpreventer", + } + response = client.post("/heroes/", json=hero1_data) + assert response.status_code == 200, response.text + hero1 = response.json() + assert "password" not in hero1 + assert "hashed_password" not in hero1 + hero1_id = hero1["id"] + response = client.post("/heroes/", json=hero2_data) + assert response.status_code == 200, response.text + hero2 = response.json() + hero2_id = hero2["id"] + response = client.post("/heroes/", json=hero3_data) + assert response.status_code == 200, response.text + hero3 = response.json() + hero3_id = hero3["id"] + response = client.get(f"/heroes/{hero2_id}") + assert response.status_code == 200, response.text + fetched_hero2 = response.json() + assert "password" not in fetched_hero2 + assert "hashed_password" not in fetched_hero2 + response = client.get("/heroes/9000") + assert response.status_code == 404, response.text + response = client.get("/heroes/") + assert response.status_code == 200, response.text + data = response.json() + assert len(data) == 3 + for response_hero in data: + assert "password" not in response_hero + assert "hashed_password" not in response_hero + + # Test hashed passwords + with Session(mod.engine) as session: + hero1_db = session.get(mod.Hero, hero1_id) + assert hero1_db + assert not hasattr(hero1_db, "password") + assert hero1_db.hashed_password == "not really hashed chimichanga hehehe" + hero2_db = session.get(mod.Hero, hero2_id) + assert hero2_db + assert not hasattr(hero2_db, "password") + assert hero2_db.hashed_password == "not really hashed auntmay hehehe" + hero3_db = session.get(mod.Hero, hero3_id) + assert hero3_db + assert not hasattr(hero3_db, "password") + assert hero3_db.hashed_password == "not really hashed bestpreventer hehehe" + + response = client.patch( + f"/heroes/{hero2_id}", json={"secret_name": "Spider-Youngster"} + ) + data = response.json() + assert response.status_code == 200, response.text + assert data["name"] == hero2_data["name"], "The name should not be set to none" + assert ( + data["secret_name"] == "Spider-Youngster" + ), "The secret name should be updated" + assert "password" not in data + assert "hashed_password" not in data + with Session(mod.engine) as session: + hero2b_db = session.get(mod.Hero, hero2_id) + assert hero2b_db + assert not hasattr(hero2b_db, "password") + assert hero2b_db.hashed_password == "not really hashed auntmay hehehe" + + response = client.patch(f"/heroes/{hero3_id}", json={"age": None}) + data = response.json() + assert response.status_code == 200, response.text + assert data["name"] == hero3_data["name"] + assert ( + data["age"] is None + ), "A field should be updatable to None, even if that's the default" + assert "password" not in data + assert "hashed_password" not in data + with Session(mod.engine) as session: + hero3b_db = session.get(mod.Hero, hero3_id) + assert hero3b_db + assert not hasattr(hero3b_db, "password") + assert hero3b_db.hashed_password == "not really hashed bestpreventer hehehe" + + # Test update dict, hashed_password + response = client.patch( + f"/heroes/{hero3_id}", json={"password": "philantroplayboy"} + ) + data = response.json() + assert response.status_code == 200, response.text + assert data["name"] == hero3_data["name"] + assert data["age"] is None + assert "password" not in data + assert "hashed_password" not in data + with Session(mod.engine) as session: + hero3b_db = session.get(mod.Hero, hero3_id) + assert hero3b_db + assert not hasattr(hero3b_db, "password") + assert ( + hero3b_db.hashed_password == "not really hashed philantroplayboy hehehe" + ) + + response = client.patch("/heroes/9001", json={"name": "Dragon Cube X"}) + assert response.status_code == 404, response.text + + 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": { + "/heroes/": { + "get": { + "summary": "Read Heroes", + "operationId": "read_heroes_heroes__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Offset", + "type": "integer", + "default": 0, + }, + "name": "offset", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "maximum": 100, + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Heroes Heroes Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/HeroPublic" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "post": { + "summary": "Create Hero", + "operationId": "create_hero_heroes__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroCreate" + } + } + }, + "required": True, + }, + "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" + } + } + }, + }, + }, + }, + }, + "/heroes/{hero_id}": { + "get": { + "summary": "Read Hero", + "operationId": "read_hero_heroes__hero_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "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": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroUpdate" + } + } + }, + "required": True, + }, + "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" + } + } + }, + }, + }, + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "HeroCreate": { + "title": "HeroCreate", + "required": ["name", "secret_name", "password"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"title": "Age", "type": "integer"} + ), + "password": {"type": "string", "title": "Password"}, + }, + }, + "HeroPublic": { + "title": "HeroPublic", + "required": ["name", "secret_name", "id"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"title": "Age", "type": "integer"} + ), + "id": {"title": "Id", "type": "integer"}, + }, + }, + "HeroUpdate": { + "title": "HeroUpdate", + "type": "object", + "properties": { + "name": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Name", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"title": "Name", "type": "string"} + ), + "secret_name": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Secret Name", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"title": "Secret Name", "type": "string"} + ), + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"title": "Age", "type": "integer"} + ), + "password": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Password", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"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"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_fastapi/test_update/test_tutorial002_py310.py b/tests/test_tutorial/test_fastapi/test_update/test_tutorial002_py310.py new file mode 100644 index 000000000..b05f5b213 --- /dev/null +++ b/tests/test_tutorial/test_fastapi/test_update/test_tutorial002_py310.py @@ -0,0 +1,430 @@ +from dirty_equals import IsDict +from fastapi.testclient import TestClient +from sqlmodel import Session, create_engine +from sqlmodel.pool import StaticPool + +from ....conftest import needs_py310 + + +@needs_py310 +def test_tutorial(clear_sqlmodel): + from docs_src.tutorial.fastapi.update import tutorial002_py310 as mod + + mod.sqlite_url = "sqlite://" + mod.engine = create_engine( + mod.sqlite_url, connect_args=mod.connect_args, poolclass=StaticPool + ) + + with TestClient(mod.app) as client: + hero1_data = { + "name": "Deadpond", + "secret_name": "Dive Wilson", + "password": "chimichanga", + } + hero2_data = { + "name": "Spider-Boy", + "secret_name": "Pedro Parqueador", + "id": 9000, + "password": "auntmay", + } + hero3_data = { + "name": "Rusty-Man", + "secret_name": "Tommy Sharp", + "age": 48, + "password": "bestpreventer", + } + response = client.post("/heroes/", json=hero1_data) + assert response.status_code == 200, response.text + hero1 = response.json() + assert "password" not in hero1 + assert "hashed_password" not in hero1 + hero1_id = hero1["id"] + response = client.post("/heroes/", json=hero2_data) + assert response.status_code == 200, response.text + hero2 = response.json() + hero2_id = hero2["id"] + response = client.post("/heroes/", json=hero3_data) + assert response.status_code == 200, response.text + hero3 = response.json() + hero3_id = hero3["id"] + response = client.get(f"/heroes/{hero2_id}") + assert response.status_code == 200, response.text + fetched_hero2 = response.json() + assert "password" not in fetched_hero2 + assert "hashed_password" not in fetched_hero2 + response = client.get("/heroes/9000") + assert response.status_code == 404, response.text + response = client.get("/heroes/") + assert response.status_code == 200, response.text + data = response.json() + assert len(data) == 3 + for response_hero in data: + assert "password" not in response_hero + assert "hashed_password" not in response_hero + + # Test hashed passwords + with Session(mod.engine) as session: + hero1_db = session.get(mod.Hero, hero1_id) + assert hero1_db + assert not hasattr(hero1_db, "password") + assert hero1_db.hashed_password == "not really hashed chimichanga hehehe" + hero2_db = session.get(mod.Hero, hero2_id) + assert hero2_db + assert not hasattr(hero2_db, "password") + assert hero2_db.hashed_password == "not really hashed auntmay hehehe" + hero3_db = session.get(mod.Hero, hero3_id) + assert hero3_db + assert not hasattr(hero3_db, "password") + assert hero3_db.hashed_password == "not really hashed bestpreventer hehehe" + + response = client.patch( + f"/heroes/{hero2_id}", json={"secret_name": "Spider-Youngster"} + ) + data = response.json() + assert response.status_code == 200, response.text + assert data["name"] == hero2_data["name"], "The name should not be set to none" + assert ( + data["secret_name"] == "Spider-Youngster" + ), "The secret name should be updated" + assert "password" not in data + assert "hashed_password" not in data + with Session(mod.engine) as session: + hero2b_db = session.get(mod.Hero, hero2_id) + assert hero2b_db + assert not hasattr(hero2b_db, "password") + assert hero2b_db.hashed_password == "not really hashed auntmay hehehe" + + response = client.patch(f"/heroes/{hero3_id}", json={"age": None}) + data = response.json() + assert response.status_code == 200, response.text + assert data["name"] == hero3_data["name"] + assert ( + data["age"] is None + ), "A field should be updatable to None, even if that's the default" + assert "password" not in data + assert "hashed_password" not in data + with Session(mod.engine) as session: + hero3b_db = session.get(mod.Hero, hero3_id) + assert hero3b_db + assert not hasattr(hero3b_db, "password") + assert hero3b_db.hashed_password == "not really hashed bestpreventer hehehe" + + # Test update dict, hashed_password + response = client.patch( + f"/heroes/{hero3_id}", json={"password": "philantroplayboy"} + ) + data = response.json() + assert response.status_code == 200, response.text + assert data["name"] == hero3_data["name"] + assert data["age"] is None + assert "password" not in data + assert "hashed_password" not in data + with Session(mod.engine) as session: + hero3b_db = session.get(mod.Hero, hero3_id) + assert hero3b_db + assert not hasattr(hero3b_db, "password") + assert ( + hero3b_db.hashed_password == "not really hashed philantroplayboy hehehe" + ) + + response = client.patch("/heroes/9001", json={"name": "Dragon Cube X"}) + assert response.status_code == 404, response.text + + 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": { + "/heroes/": { + "get": { + "summary": "Read Heroes", + "operationId": "read_heroes_heroes__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Offset", + "type": "integer", + "default": 0, + }, + "name": "offset", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "maximum": 100, + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Heroes Heroes Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/HeroPublic" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "post": { + "summary": "Create Hero", + "operationId": "create_hero_heroes__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroCreate" + } + } + }, + "required": True, + }, + "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" + } + } + }, + }, + }, + }, + }, + "/heroes/{hero_id}": { + "get": { + "summary": "Read Hero", + "operationId": "read_hero_heroes__hero_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "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": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroUpdate" + } + } + }, + "required": True, + }, + "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" + } + } + }, + }, + }, + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "HeroCreate": { + "title": "HeroCreate", + "required": ["name", "secret_name", "password"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"title": "Age", "type": "integer"} + ), + "password": {"type": "string", "title": "Password"}, + }, + }, + "HeroPublic": { + "title": "HeroPublic", + "required": ["name", "secret_name", "id"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"title": "Age", "type": "integer"} + ), + "id": {"title": "Id", "type": "integer"}, + }, + }, + "HeroUpdate": { + "title": "HeroUpdate", + "type": "object", + "properties": { + "name": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Name", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"title": "Name", "type": "string"} + ), + "secret_name": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Secret Name", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"title": "Secret Name", "type": "string"} + ), + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"title": "Age", "type": "integer"} + ), + "password": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Password", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"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"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_fastapi/test_update/test_tutorial002_py39.py b/tests/test_tutorial/test_fastapi/test_update/test_tutorial002_py39.py new file mode 100644 index 000000000..807e33421 --- /dev/null +++ b/tests/test_tutorial/test_fastapi/test_update/test_tutorial002_py39.py @@ -0,0 +1,430 @@ +from dirty_equals import IsDict +from fastapi.testclient import TestClient +from sqlmodel import Session, create_engine +from sqlmodel.pool import StaticPool + +from ....conftest import needs_py39 + + +@needs_py39 +def test_tutorial(clear_sqlmodel): + from docs_src.tutorial.fastapi.update import tutorial002_py39 as mod + + mod.sqlite_url = "sqlite://" + mod.engine = create_engine( + mod.sqlite_url, connect_args=mod.connect_args, poolclass=StaticPool + ) + + with TestClient(mod.app) as client: + hero1_data = { + "name": "Deadpond", + "secret_name": "Dive Wilson", + "password": "chimichanga", + } + hero2_data = { + "name": "Spider-Boy", + "secret_name": "Pedro Parqueador", + "id": 9000, + "password": "auntmay", + } + hero3_data = { + "name": "Rusty-Man", + "secret_name": "Tommy Sharp", + "age": 48, + "password": "bestpreventer", + } + response = client.post("/heroes/", json=hero1_data) + assert response.status_code == 200, response.text + hero1 = response.json() + assert "password" not in hero1 + assert "hashed_password" not in hero1 + hero1_id = hero1["id"] + response = client.post("/heroes/", json=hero2_data) + assert response.status_code == 200, response.text + hero2 = response.json() + hero2_id = hero2["id"] + response = client.post("/heroes/", json=hero3_data) + assert response.status_code == 200, response.text + hero3 = response.json() + hero3_id = hero3["id"] + response = client.get(f"/heroes/{hero2_id}") + assert response.status_code == 200, response.text + fetched_hero2 = response.json() + assert "password" not in fetched_hero2 + assert "hashed_password" not in fetched_hero2 + response = client.get("/heroes/9000") + assert response.status_code == 404, response.text + response = client.get("/heroes/") + assert response.status_code == 200, response.text + data = response.json() + assert len(data) == 3 + for response_hero in data: + assert "password" not in response_hero + assert "hashed_password" not in response_hero + + # Test hashed passwords + with Session(mod.engine) as session: + hero1_db = session.get(mod.Hero, hero1_id) + assert hero1_db + assert not hasattr(hero1_db, "password") + assert hero1_db.hashed_password == "not really hashed chimichanga hehehe" + hero2_db = session.get(mod.Hero, hero2_id) + assert hero2_db + assert not hasattr(hero2_db, "password") + assert hero2_db.hashed_password == "not really hashed auntmay hehehe" + hero3_db = session.get(mod.Hero, hero3_id) + assert hero3_db + assert not hasattr(hero3_db, "password") + assert hero3_db.hashed_password == "not really hashed bestpreventer hehehe" + + response = client.patch( + f"/heroes/{hero2_id}", json={"secret_name": "Spider-Youngster"} + ) + data = response.json() + assert response.status_code == 200, response.text + assert data["name"] == hero2_data["name"], "The name should not be set to none" + assert ( + data["secret_name"] == "Spider-Youngster" + ), "The secret name should be updated" + assert "password" not in data + assert "hashed_password" not in data + with Session(mod.engine) as session: + hero2b_db = session.get(mod.Hero, hero2_id) + assert hero2b_db + assert not hasattr(hero2b_db, "password") + assert hero2b_db.hashed_password == "not really hashed auntmay hehehe" + + response = client.patch(f"/heroes/{hero3_id}", json={"age": None}) + data = response.json() + assert response.status_code == 200, response.text + assert data["name"] == hero3_data["name"] + assert ( + data["age"] is None + ), "A field should be updatable to None, even if that's the default" + assert "password" not in data + assert "hashed_password" not in data + with Session(mod.engine) as session: + hero3b_db = session.get(mod.Hero, hero3_id) + assert hero3b_db + assert not hasattr(hero3b_db, "password") + assert hero3b_db.hashed_password == "not really hashed bestpreventer hehehe" + + # Test update dict, hashed_password + response = client.patch( + f"/heroes/{hero3_id}", json={"password": "philantroplayboy"} + ) + data = response.json() + assert response.status_code == 200, response.text + assert data["name"] == hero3_data["name"] + assert data["age"] is None + assert "password" not in data + assert "hashed_password" not in data + with Session(mod.engine) as session: + hero3b_db = session.get(mod.Hero, hero3_id) + assert hero3b_db + assert not hasattr(hero3b_db, "password") + assert ( + hero3b_db.hashed_password == "not really hashed philantroplayboy hehehe" + ) + + response = client.patch("/heroes/9001", json={"name": "Dragon Cube X"}) + assert response.status_code == 404, response.text + + 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": { + "/heroes/": { + "get": { + "summary": "Read Heroes", + "operationId": "read_heroes_heroes__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Offset", + "type": "integer", + "default": 0, + }, + "name": "offset", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "maximum": 100, + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Heroes Heroes Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/HeroPublic" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "post": { + "summary": "Create Hero", + "operationId": "create_hero_heroes__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroCreate" + } + } + }, + "required": True, + }, + "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" + } + } + }, + }, + }, + }, + }, + "/heroes/{hero_id}": { + "get": { + "summary": "Read Hero", + "operationId": "read_hero_heroes__hero_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "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": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroUpdate" + } + } + }, + "required": True, + }, + "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" + } + } + }, + }, + }, + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "HeroCreate": { + "title": "HeroCreate", + "required": ["name", "secret_name", "password"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"title": "Age", "type": "integer"} + ), + "password": {"type": "string", "title": "Password"}, + }, + }, + "HeroPublic": { + "title": "HeroPublic", + "required": ["name", "secret_name", "id"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"title": "Age", "type": "integer"} + ), + "id": {"title": "Id", "type": "integer"}, + }, + }, + "HeroUpdate": { + "title": "HeroUpdate", + "type": "object", + "properties": { + "name": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Name", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"title": "Name", "type": "string"} + ), + "secret_name": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Secret Name", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"title": "Secret Name", "type": "string"} + ), + "age": IsDict( + { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Age", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"title": "Age", "type": "integer"} + ), + "password": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Password", + } + ) + | IsDict( + # TODO: Remove when deprecating Pydantic v1 + {"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"}, + }, + }, + } + }, + }