Compare commits
250 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4590963e88 | ||
|
|
883cbe3a8d | ||
|
|
b560e9deb8 | ||
|
|
e2f646dea5 | ||
|
|
b93dd95125 | ||
|
|
ceac7bc2e8 | ||
|
|
1d43bd8b1e | ||
|
|
9f3af8507e | ||
|
|
d165e4b5ad | ||
|
|
d5cba6e358 | ||
|
|
bd1641c9a2 | ||
|
|
71de44daba | ||
|
|
1b275bd6a7 | ||
|
|
866d9ecb29 | ||
|
|
5bb4cffd49 | ||
|
|
a319952be1 | ||
|
|
662bd641b8 | ||
|
|
dcf4f58e81 | ||
|
|
e4013acc54 | ||
|
|
df0f834227 | ||
|
|
5e592c9a0d | ||
|
|
c13b71056e | ||
|
|
900e0d3371 | ||
|
|
a280b58c10 | ||
|
|
9ebbf255f7 | ||
|
|
39cbf27904 | ||
|
|
28d0e76370 | ||
|
|
0431c5bb26 | ||
|
|
1c4f425f17 | ||
|
|
3ecbeacb46 | ||
|
|
32353eeb07 | ||
|
|
7023896d7c | ||
|
|
570cd9f10c | ||
|
|
0b4989d0b2 | ||
|
|
f67867f974 | ||
|
|
2454694de3 | ||
|
|
6151f23e15 | ||
|
|
1eb40b1f33 | ||
|
|
fa79856a4b | ||
|
|
fce7ee21a2 | ||
|
|
c75743d9d3 | ||
|
|
355f48860c | ||
|
|
51df778420 | ||
|
|
bf51a11dcf | ||
|
|
9d0b8b6a93 | ||
|
|
0e3154a553 | ||
|
|
9141c8a920 | ||
|
|
4c3f242ae2 | ||
|
|
0a80166b81 | ||
|
|
6b562358fc | ||
|
|
3d483921fe | ||
|
|
2abb798a22 | ||
|
|
fa12c5d87b | ||
|
|
7fec884864 | ||
|
|
d8fa545955 | ||
|
|
1b7b3aa668 | ||
|
|
0c7def88b5 | ||
|
|
48b97f3d8e | ||
|
|
fe497adf0c | ||
|
|
8419545a3a | ||
|
|
b892504141 | ||
|
|
bd24013a26 | ||
|
|
fa2f178b8a | ||
|
|
5b733b348d | ||
|
|
6770b9fd89 | ||
|
|
5c0fca1d96 | ||
|
|
909286cc03 | ||
|
|
276bcf788c | ||
|
|
cc11619c67 | ||
|
|
41495e30c7 | ||
|
|
50b0198423 | ||
|
|
853d787923 | ||
|
|
e5fddb97a7 | ||
|
|
4ac87146b1 | ||
|
|
2ecc86275f | ||
|
|
f42314956f | ||
|
|
98739b071c | ||
|
|
d8effcbc5c | ||
|
|
cce30d7546 | ||
|
|
33c5e5c98d | ||
|
|
2b0dfb50c8 | ||
|
|
799d0aa7a6 | ||
|
|
be464fba69 | ||
|
|
a95bd3873d | ||
|
|
71baff6015 | ||
|
|
f18ea03b07 | ||
|
|
65ee2610b6 | ||
|
|
47bcd9df8d | ||
|
|
781a2d6b0a | ||
|
|
a974d9104f | ||
|
|
b1c2f822c9 | ||
|
|
382b1b0cbb | ||
|
|
6d00f6fcbd | ||
|
|
8ed856d322 | ||
|
|
77c6fed305 | ||
|
|
c0294423f4 | ||
|
|
e05eae6a49 | ||
|
|
ed22232dee | ||
|
|
dacc1fa9ca | ||
|
|
0b2d015fc4 | ||
|
|
d8a20d9c21 | ||
|
|
188f7cd172 | ||
|
|
12af94655b | ||
|
|
4bb99763b1 | ||
|
|
9dd11ef74f | ||
|
|
fa5d14e413 | ||
|
|
df1efcfa7f | ||
|
|
fc1d7afae9 | ||
|
|
a2d1b4eeaf | ||
|
|
94f3765fcf | ||
|
|
31ed654dd0 | ||
|
|
cbaf172c63 | ||
|
|
c557cf6d18 | ||
|
|
9632980664 | ||
|
|
6457775a0f | ||
|
|
717594ef13 | ||
|
|
e4e1385eed | ||
|
|
13cc722110 | ||
|
|
7fdfee10a5 | ||
|
|
8d14232538 | ||
|
|
99f8ce3894 | ||
|
|
d05c3ee495 | ||
|
|
596718d93b | ||
|
|
a6ce817ca5 | ||
|
|
dcc4e4c36a | ||
|
|
80fd7e03cf | ||
|
|
1acb683f80 | ||
|
|
9fd7306648 | ||
|
|
9d3ca01dd0 | ||
|
|
6cd086f25f | ||
|
|
376603efb2 | ||
|
|
30d2b30217 | ||
|
|
9b186c89a8 | ||
|
|
fc3120a877 | ||
|
|
6d361e3ffb | ||
|
|
03f295b397 | ||
|
|
403d44ea78 | ||
|
|
a1caaa08d7 | ||
|
|
d192142eb9 | ||
|
|
beb7a24275 | ||
|
|
dee70033b8 | ||
|
|
189059e07e | ||
|
|
475b838c8b | ||
|
|
d6e4f9b9e3 | ||
|
|
1062e1b485 | ||
|
|
8e55ea5125 | ||
|
|
9732c5ac60 | ||
|
|
b8996f0e62 | ||
|
|
9809b5bc83 | ||
|
|
c213f5daf4 | ||
|
|
d1cf613461 | ||
|
|
d281a0fa9f | ||
|
|
9511c4677d | ||
|
|
c25a8cb89b | ||
|
|
d3261cab59 | ||
|
|
40c1af9202 | ||
|
|
b83e848699 | ||
|
|
2676cf2b06 | ||
|
|
56f43904c1 | ||
|
|
7c5894ee75 | ||
|
|
27a81b2112 | ||
|
|
7f72c60ae4 | ||
|
|
065fcdc828 | ||
|
|
9ba3039106 | ||
|
|
840fd08ab2 | ||
|
|
a8a792e3c0 | ||
|
|
8970833b14 | ||
|
|
5231c8b6bb | ||
|
|
1568bad01e | ||
|
|
357417e6d5 | ||
|
|
893d64fd31 | ||
|
|
2799303f6c | ||
|
|
a2e2942aad | ||
|
|
b7d6a0a3c3 | ||
|
|
bdcf11bca6 | ||
|
|
d939b7c45c | ||
|
|
d5219aa3c5 | ||
|
|
aa7169b93b | ||
|
|
89c356cb77 | ||
|
|
088164ef2a | ||
|
|
1e2fd10047 | ||
|
|
30a9c23a34 | ||
|
|
da29f7940d | ||
|
|
40007c80da | ||
|
|
e246ae8864 | ||
|
|
aaa54492c1 | ||
|
|
73fa81af74 | ||
|
|
a21d5c85a3 | ||
|
|
02bd7ebffd | ||
|
|
43a689d369 | ||
|
|
810236c26c | ||
|
|
33e00c3ab3 | ||
|
|
5c9a3b3b21 | ||
|
|
dc44d2e2c4 | ||
|
|
1c294ddeb6 | ||
|
|
c47a54df91 | ||
|
|
624a2142bf | ||
|
|
7b3148c0b4 | ||
|
|
cf36b2d9ba | ||
|
|
5fa9062ed9 | ||
|
|
267cd42fb6 | ||
|
|
497270dc55 | ||
|
|
aa87ff37ea | ||
|
|
0fe1e6d5a3 | ||
|
|
54b5db9842 | ||
|
|
c31bac638c | ||
|
|
203db14e9b | ||
|
|
06bb369bcb | ||
|
|
209791734c | ||
|
|
fbc6f3b536 | ||
|
|
22faa192b9 | ||
|
|
3a9244c69f | ||
|
|
8070b142b8 | ||
|
|
f0aa199611 | ||
|
|
5956940908 | ||
|
|
ea79c47c0e | ||
|
|
7e26dac198 | ||
|
|
bb32178bda | ||
|
|
5ca9375f26 | ||
|
|
e7d8b69b53 | ||
|
|
c6ad5b8109 | ||
|
|
8003c73877 | ||
|
|
b532a4c304 | ||
|
|
29d9721d1a | ||
|
|
f0f6f93e28 | ||
|
|
91d8e38208 | ||
|
|
ccdab8fb24 | ||
|
|
592c877cfc | ||
|
|
fee7ecf619 | ||
|
|
e8f61fb9d0 | ||
|
|
781174e6e9 | ||
|
|
ba76745f43 | ||
|
|
5615a80fc5 | ||
|
|
375e83d068 | ||
|
|
666a9a3557 | ||
|
|
1dde3e0044 | ||
|
|
8b87bf8b40 | ||
|
|
94715b6fa8 | ||
|
|
27a16744f2 | ||
|
|
75ce45588b | ||
|
|
b3e1a66a21 | ||
|
|
c94db7b8a0 | ||
|
|
a67326d358 | ||
|
|
e88b5d3691 | ||
|
|
fdb049bee3 | ||
|
|
ae144e0a39 | ||
|
|
85f5e7fc45 | ||
|
|
b51ebaf658 | ||
|
|
f232166db5 | ||
|
|
4143edd251 |
5
.flake8
@@ -1,5 +0,0 @@
|
||||
[flake8]
|
||||
max-line-length = 88
|
||||
select = C,E,F,W,B,B9
|
||||
ignore = E203, E501, W503
|
||||
exclude = __init__.py
|
||||
@@ -1,5 +1,3 @@
|
||||
name: Question or Problem
|
||||
description: Ask a question or ask about a problem
|
||||
labels: [question]
|
||||
body:
|
||||
- type: markdown
|
||||
@@ -9,9 +7,9 @@ body:
|
||||
|
||||
Please follow these instructions, fill every question, and do every step. 🙏
|
||||
|
||||
I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time.
|
||||
I'm asking this because answering questions and solving problems in GitHub is what consumes most of the time.
|
||||
|
||||
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues.
|
||||
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling questions.
|
||||
|
||||
All that, on top of all the incredible help provided by a bunch of community members that give a lot of their time to come here and help others.
|
||||
|
||||
@@ -21,16 +19,16 @@ body:
|
||||
|
||||
And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
|
||||
|
||||
As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
|
||||
As there are too many questions, I'll have to discard and close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: First Check
|
||||
description: Please confirm and check all the following options.
|
||||
options:
|
||||
- label: I added a very descriptive title to this issue.
|
||||
- label: I added a very descriptive title here.
|
||||
required: true
|
||||
- label: I used the GitHub search to find a similar issue and didn't find it.
|
||||
- label: I used the GitHub search to find a similar question and didn't find it.
|
||||
required: true
|
||||
- label: I searched the SQLModel documentation, with the integrated search.
|
||||
required: true
|
||||
@@ -51,7 +49,7 @@ body:
|
||||
|
||||
* Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
|
||||
* I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
|
||||
* Implement a Pull Request for a confirmed bug.
|
||||
* Review one Pull Request by downloading the code and following all the review process](https://sqlmodel.tiangolo.com/help/#review-pull-requests).
|
||||
|
||||
options:
|
||||
- label: I commit to help with one of those options 👆
|
||||
9
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -2,3 +2,12 @@ blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Security Contact
|
||||
about: Please report security vulnerabilities to security@tiangolo.com
|
||||
- name: Question or Problem
|
||||
about: Ask a question or ask about a problem in GitHub Discussions.
|
||||
url: https://github.com/tiangolo/sqlmodel/discussions/categories/questions
|
||||
- name: Feature Request
|
||||
about: To suggest an idea or ask about a feature, please start with a question saying what you would like to achieve. There might be a way to do it already.
|
||||
url: https://github.com/tiangolo/sqlmodel/discussions/categories/questions
|
||||
- name: Show and tell
|
||||
about: Show what you built with SQLModel or to be used with SQLModel.
|
||||
url: https://github.com/tiangolo/sqlmodel/discussions/categories/show-and-tell
|
||||
|
||||
214
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -1,214 +0,0 @@
|
||||
name: Feature Request
|
||||
description: Suggest an idea or ask for a feature that you would like to have in SQLModel
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for your interest in SQLModel! 🚀
|
||||
|
||||
Please follow these instructions, fill every question, and do every step. 🙏
|
||||
|
||||
I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time.
|
||||
|
||||
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues.
|
||||
|
||||
All that, on top of all the incredible help provided by a bunch of community members that give a lot of their time to come here and help others.
|
||||
|
||||
If more SQLModel users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅).
|
||||
|
||||
By asking questions in a structured way (following this) it will be much easier to help you.
|
||||
|
||||
And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
|
||||
|
||||
As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: First Check
|
||||
description: Please confirm and check all the following options.
|
||||
options:
|
||||
- label: I added a very descriptive title to this issue.
|
||||
required: true
|
||||
- label: I used the GitHub search to find a similar issue and didn't find it.
|
||||
required: true
|
||||
- label: I searched the SQLModel documentation, with the integrated search.
|
||||
required: true
|
||||
- label: I already searched in Google "How to X in SQLModel" and didn't find any information.
|
||||
required: true
|
||||
- label: I already read and followed all the tutorial in the docs and didn't find an answer.
|
||||
required: true
|
||||
- label: I already checked if it is not related to SQLModel but to [Pydantic](https://github.com/samuelcolvin/pydantic).
|
||||
required: true
|
||||
- label: I already checked if it is not related to SQLModel but to [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy).
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: help
|
||||
attributes:
|
||||
label: Commit to Help
|
||||
description: |
|
||||
After submitting this, I commit to one of:
|
||||
|
||||
* Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
|
||||
* I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
|
||||
* Implement a Pull Request for a confirmed bug.
|
||||
|
||||
options:
|
||||
- label: I commit to help with one of those options 👆
|
||||
required: true
|
||||
- type: textarea
|
||||
id: example
|
||||
attributes:
|
||||
label: Example Code
|
||||
description: |
|
||||
Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case.
|
||||
|
||||
If I (or someone) can copy it, run it, and see it right away, there's a much higher chance I (or someone) will be able to help you.
|
||||
|
||||
placeholder: |
|
||||
from typing import Optional
|
||||
|
||||
from sqlmodel import Field, Session, SQLModel, create_engine
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str
|
||||
secret_name: str
|
||||
age: Optional[int] = None
|
||||
|
||||
|
||||
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
|
||||
|
||||
engine = create_engine("sqlite:///database.db")
|
||||
|
||||
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
with Session(engine) as session:
|
||||
session.add(hero_1)
|
||||
session.commit()
|
||||
session.refresh(hero_1)
|
||||
print(hero_1)
|
||||
render: python
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
What is your feature request?
|
||||
|
||||
Write a short description telling me what you are trying to solve and what you are currently doing.
|
||||
placeholder: |
|
||||
* Create a Hero model.
|
||||
* Create a Hero instance.
|
||||
* Save it to a SQLite database.
|
||||
* I would like it to also automatically send me an email with all the SQL code executed.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: wanted-solution
|
||||
attributes:
|
||||
label: Wanted Solution
|
||||
description: |
|
||||
Tell me what's the solution you would like.
|
||||
placeholder: |
|
||||
I would like it to have a `send_email` configuration that defaults to `False`, and can be set to `True` to send me an email.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: wanted-code
|
||||
attributes:
|
||||
label: Wanted Code
|
||||
description: Show me an example of how you would want the code to look like.
|
||||
placeholder: |
|
||||
from typing import Optional
|
||||
|
||||
from sqlmodel import Field, Session, SQLModel, create_engine
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True, send_email=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str
|
||||
secret_name: str
|
||||
age: Optional[int] = None
|
||||
|
||||
|
||||
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
|
||||
|
||||
engine = create_engine("sqlite:///database.db")
|
||||
|
||||
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
with Session(engine) as session:
|
||||
session.add(hero_1)
|
||||
session.commit()
|
||||
session.refresh(hero_1)
|
||||
print(hero_1)
|
||||
|
||||
|
||||
render: python
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives
|
||||
description: |
|
||||
Tell me about alternatives you've considered.
|
||||
placeholder: |
|
||||
To hire someone to look at the logs, write the SQL in paper, and then send me a letter by post with it.
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: What operating system are you on?
|
||||
multiple: true
|
||||
options:
|
||||
- Linux
|
||||
- Windows
|
||||
- macOS
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: os-details
|
||||
attributes:
|
||||
label: Operating System Details
|
||||
description: You can add more details about your operating system here, in particular if you chose "Other".
|
||||
- type: input
|
||||
id: sqlmodel-version
|
||||
attributes:
|
||||
label: SQLModel Version
|
||||
description: |
|
||||
What SQLModel version are you using?
|
||||
|
||||
You can find the SQLModel version with:
|
||||
|
||||
```bash
|
||||
python -c "import sqlmodel; print(sqlmodel.__version__)"
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: python-version
|
||||
attributes:
|
||||
label: Python Version
|
||||
description: |
|
||||
What Python version are you using?
|
||||
|
||||
You can find the Python version with:
|
||||
|
||||
```bash
|
||||
python --version
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Add any additional context information or screenshots you think are useful.
|
||||
22
.github/ISSUE_TEMPLATE/privileged.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Privileged
|
||||
description: You are @tiangolo or he asked you directly to create an issue here. If not, check the other options. 👇
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for your interest in SQLModel! 🚀
|
||||
|
||||
If you are not @tiangolo or he didn't ask you directly to create an issue here, please start the conversation in a [Question in GitHub Discussions](https://github.com/tiangolo/sqlmodel/discussions/categories/questions) instead.
|
||||
- type: checkboxes
|
||||
id: privileged
|
||||
attributes:
|
||||
label: Privileged issue
|
||||
description: Confirm that you are allowed to create an issue here.
|
||||
options:
|
||||
- label: I'm @tiangolo or he asked me directly to create an issue here.
|
||||
required: true
|
||||
- type: textarea
|
||||
id: content
|
||||
attributes:
|
||||
label: Issue Content
|
||||
description: Add the content of the issue here.
|
||||
@@ -48,9 +48,7 @@ if __name__ == "__main__":
|
||||
use_pr = pr
|
||||
break
|
||||
if not use_pr:
|
||||
logging.error(
|
||||
f"No PR found for hash: {event.workflow_run.head_commit.id}"
|
||||
)
|
||||
logging.error(f"No PR found for hash: {event.workflow_run.head_commit.id}")
|
||||
sys.exit(0)
|
||||
github_headers = {
|
||||
"Authorization": f"token {settings.input_token.get_secret_value()}"
|
||||
|
||||
7
.github/actions/watch-previews/Dockerfile
vendored
@@ -1,7 +0,0 @@
|
||||
FROM python:3.7
|
||||
|
||||
RUN pip install httpx PyGithub "pydantic==1.5.1"
|
||||
|
||||
COPY ./app /app
|
||||
|
||||
CMD ["python", "/app/main.py"]
|
||||
10
.github/actions/watch-previews/action.yml
vendored
@@ -1,10 +0,0 @@
|
||||
name: Watch docs previews in PRs
|
||||
description: Check PRs and trigger new docs deploys
|
||||
author: "Sebastián Ramírez <tiangolo@gmail.com>"
|
||||
inputs:
|
||||
token:
|
||||
description: 'Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}'
|
||||
required: true
|
||||
runs:
|
||||
using: docker
|
||||
image: Dockerfile
|
||||
102
.github/actions/watch-previews/app/main.py
vendored
@@ -1,102 +0,0 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
import httpx
|
||||
from github import Github
|
||||
from github.NamedUser import NamedUser
|
||||
from pydantic import BaseModel, BaseSettings, SecretStr
|
||||
|
||||
github_api = "https://api.github.com"
|
||||
netlify_api = "https://api.netlify.com"
|
||||
main_branch = "main"
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
input_token: SecretStr
|
||||
github_repository: str
|
||||
github_event_path: Path
|
||||
github_event_name: Optional[str] = None
|
||||
|
||||
|
||||
class Artifact(BaseModel):
|
||||
id: int
|
||||
node_id: str
|
||||
name: str
|
||||
size_in_bytes: int
|
||||
url: str
|
||||
archive_download_url: str
|
||||
expired: bool
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class ArtifactResponse(BaseModel):
|
||||
total_count: int
|
||||
artifacts: List[Artifact]
|
||||
|
||||
|
||||
def get_message(commit: str) -> str:
|
||||
return f"Docs preview for commit {commit} at"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
settings = Settings()
|
||||
logging.info(f"Using config: {settings.json()}")
|
||||
g = Github(settings.input_token.get_secret_value())
|
||||
repo = g.get_repo(settings.github_repository)
|
||||
owner: NamedUser = repo.owner
|
||||
headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"}
|
||||
prs = list(repo.get_pulls(state="open"))
|
||||
response = httpx.get(
|
||||
f"{github_api}/repos/{settings.github_repository}/actions/artifacts",
|
||||
headers=headers,
|
||||
)
|
||||
data = response.json()
|
||||
artifacts_response = ArtifactResponse.parse_obj(data)
|
||||
for pr in prs:
|
||||
logging.info("-----")
|
||||
logging.info(f"Processing PR #{pr.number}: {pr.title}")
|
||||
pr_comments = list(pr.get_issue_comments())
|
||||
pr_commits = list(pr.get_commits())
|
||||
last_commit = pr_commits[0]
|
||||
for pr_commit in pr_commits:
|
||||
if pr_commit.commit.author.date > last_commit.commit.author.date:
|
||||
last_commit = pr_commit
|
||||
commit = last_commit.commit.sha
|
||||
logging.info(f"Last commit: {commit}")
|
||||
message = get_message(commit)
|
||||
notified = False
|
||||
for pr_comment in pr_comments:
|
||||
if message in pr_comment.body:
|
||||
notified = True
|
||||
logging.info(f"Docs preview was notified: {notified}")
|
||||
if not notified:
|
||||
artifact_name = f"docs-zip-{commit}"
|
||||
use_artifact: Optional[Artifact] = None
|
||||
for artifact in artifacts_response.artifacts:
|
||||
if artifact.name == artifact_name:
|
||||
use_artifact = artifact
|
||||
break
|
||||
if not use_artifact:
|
||||
logging.info("Artifact not available")
|
||||
else:
|
||||
logging.info(f"Existing artifact: {use_artifact.name}")
|
||||
response = httpx.post(
|
||||
f"{github_api}/repos/{settings.github_repository}/actions/workflows/preview-docs.yml/dispatches",
|
||||
headers=headers,
|
||||
json={
|
||||
"ref": main_branch,
|
||||
"inputs": {
|
||||
"pr": f"{pr.number}",
|
||||
"name": artifact_name,
|
||||
"commit": commit,
|
||||
},
|
||||
},
|
||||
)
|
||||
logging.info(
|
||||
f"Trigger sent, response status: {response.status_code} - content: {response.content}"
|
||||
)
|
||||
logging.info("Finished")
|
||||
4
.github/dependabot.yml
vendored
@@ -5,8 +5,12 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
commit-message:
|
||||
prefix: ⬆
|
||||
# Python
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
commit-message:
|
||||
prefix: ⬆
|
||||
|
||||
119
.github/workflows/build-docs.yml
vendored
@@ -4,78 +4,87 @@ on:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
debug_enabled:
|
||||
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
||||
required: false
|
||||
default: false
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
# Required permissions
|
||||
permissions:
|
||||
pull-requests: read
|
||||
# Set job outputs to values from filter step
|
||||
outputs:
|
||||
docs: ${{ steps.filter.outputs.docs }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# For pull requests it's not necessary to checkout the code but for the main branch it is
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
docs:
|
||||
- README.md
|
||||
- docs/**
|
||||
- docs_src/**
|
||||
- requirements-docs.txt
|
||||
- pyproject.toml
|
||||
- mkdocs.yml
|
||||
- mkdocs.insiders.yml
|
||||
- .github/workflows/build-docs.yml
|
||||
- .github/workflows/deploy-docs.yml
|
||||
|
||||
build-docs:
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- changes
|
||||
if: ${{ needs.changes.outputs.docs == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Dump GitHub context
|
||||
env:
|
||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
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 }}
|
||||
with:
|
||||
limit-access-to-actor: true
|
||||
- uses: actions/cache@v2
|
||||
python-version: "3.11"
|
||||
- uses: actions/cache@v3
|
||||
id: cache
|
||||
with:
|
||||
path: ${{ env.pythonLocation }}
|
||||
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root-docs
|
||||
- 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'
|
||||
# TODO: remove python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
|
||||
# once there's a release of Poetry 1.2.x including poetry-core > 1.1.0a6
|
||||
# Ref: https://github.com/python-poetry/poetry-core/pull/188
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
|
||||
python -m pip install "poetry==1.2.0a2"
|
||||
python -m poetry plugin 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.pull_request.head.repo.fork == false && steps.cache.outputs.cache-hit != 'true'
|
||||
run: python -m poetry run pip install git+https://${{ secrets.ACTIONS_TOKEN }}@github.com/squidfunk/mkdocs-material-insiders.git
|
||||
- uses: actions/cache@v2
|
||||
if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' ) && steps.cache.outputs.cache-hit != 'true'
|
||||
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 }}
|
||||
path: .cache
|
||||
- name: Verify README
|
||||
run: python ./scripts/docs.py verify-readme
|
||||
- name: Build Docs
|
||||
if: github.event.pull_request.head.repo.fork == true
|
||||
run: python -m poetry run mkdocs build
|
||||
- name: Build Docs with Insiders
|
||||
if: github.event.pull_request.head.repo.fork == false
|
||||
run: python -m poetry run mkdocs build --config-file mkdocs.insiders.yml
|
||||
- name: Zip docs
|
||||
run: python -m poetry run bash ./scripts/zip-docs.sh
|
||||
- uses: actions/upload-artifact@v2
|
||||
run: python ./scripts/docs.py build
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docs-zip
|
||||
path: ./docs.zip
|
||||
- name: Deploy to Netlify
|
||||
uses: nwtgck/actions-netlify@v1.1.5
|
||||
name: docs-site
|
||||
path: ./site/**
|
||||
|
||||
# https://github.com/marketplace/actions/alls-green#why
|
||||
docs-all-green: # This job does nothing and is only used for the branch protection
|
||||
if: always()
|
||||
needs:
|
||||
- build-docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Decide whether the needed jobs succeeded or failed
|
||||
uses: re-actors/alls-green@release/v1
|
||||
with:
|
||||
publish-dir: './site'
|
||||
production-branch: main
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
enable-commit-comment: false
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
allowed-skips: build-docs
|
||||
|
||||
46
.github/workflows/deploy-docs.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Deploy Docs
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- Build Docs
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
deploy-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Dump GitHub context
|
||||
env:
|
||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
- uses: actions/checkout@v4
|
||||
- name: Clean site
|
||||
run: |
|
||||
rm -rf ./site
|
||||
mkdir ./site
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
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
|
||||
# hashFiles returns an empty string if there are no files
|
||||
if: hashFiles('./site/*')
|
||||
id: deploy
|
||||
uses: cloudflare/pages-action@v1
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
projectName: sqlmodel
|
||||
directory: './site'
|
||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: ${{ ( github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'main' && 'main' ) || ( github.event.workflow_run.head_sha ) }}
|
||||
- name: Comment Deploy
|
||||
if: steps.deploy.outputs.url != ''
|
||||
uses: ./.github/actions/comment-docs-preview-in-pr
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
deploy_url: "${{ steps.deploy.outputs.url }}"
|
||||
2
.github/workflows/issue-manager.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
issue-manager:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: tiangolo/issue-manager@0.4.0
|
||||
- uses: tiangolo/issue-manager@0.5.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
config: >
|
||||
|
||||
15
.github/workflows/latest-changes.yml
vendored
@@ -14,25 +14,28 @@ on:
|
||||
debug_enabled:
|
||||
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
||||
required: false
|
||||
default: false
|
||||
default: 'false'
|
||||
|
||||
jobs:
|
||||
latest-changes:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# To allow latest-changes to commit to the main branch
|
||||
token: ${{ secrets.ACTIONS_TOKEN }}
|
||||
token: ${{ secrets.SQLMODEL_LATEST_CHANGES }}
|
||||
# Allow debugging with tmate
|
||||
- name: Setup tmate session
|
||||
uses: mxschmitt/action-tmate@v3
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
|
||||
with:
|
||||
limit-access-to-actor: true
|
||||
- uses: docker://tiangolo/latest-changes:0.0.3
|
||||
- uses: docker://tiangolo/latest-changes:0.2.0
|
||||
# - uses: tiangolo/latest-changes@main
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
latest_changes_file: docs/release-notes.md
|
||||
latest_changes_header: '## Latest Changes\n\n'
|
||||
latest_changes_header: '## Latest Changes'
|
||||
end_regex: '^## '
|
||||
debug_logs: true
|
||||
label_header_prefix: '### '
|
||||
|
||||
41
.github/workflows/preview-docs.yml
vendored
@@ -1,41 +0,0 @@
|
||||
name: Preview Docs
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- Build Docs
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
preview-docs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Download Artifact Docs
|
||||
uses: dawidd6/action-download-artifact@v2.9.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
workflow: build-docs.yml
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
name: docs-zip
|
||||
- name: Unzip docs
|
||||
run: |
|
||||
rm -rf ./site
|
||||
unzip docs.zip
|
||||
rm -f docs.zip
|
||||
- name: Deploy to Netlify
|
||||
id: netlify
|
||||
uses: nwtgck/actions-netlify@v1.1.5
|
||||
with:
|
||||
publish-dir: './site'
|
||||
production-deploy: false
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
enable-commit-comment: false
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
||||
- name: Comment Deploy
|
||||
uses: ./.github/actions/comment-docs-preview-in-pr
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
deploy_url: "${{ steps.netlify.outputs.deploy-url }}"
|
||||
55
.github/workflows/publish.yml
vendored
@@ -9,46 +9,29 @@ on:
|
||||
debug_enabled:
|
||||
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
||||
required: false
|
||||
default: false
|
||||
default: 'false'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
package:
|
||||
- sqlmodel
|
||||
- sqlmodel-slim
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
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 }}
|
||||
with:
|
||||
limit-access-to-actor: true
|
||||
- uses: actions/cache@v2
|
||||
id: cache
|
||||
with:
|
||||
path: ${{ env.pythonLocation }}
|
||||
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root
|
||||
- name: Install poetry
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
# TODO: remove python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
|
||||
# once there's a release of Poetry 1.2.x including poetry-core > 1.1.0a6
|
||||
# Ref: https://github.com/python-poetry/poetry-core/pull/188
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
|
||||
python -m pip install "poetry==1.2.0a2"
|
||||
python -m poetry plugin 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
|
||||
|
||||
37
.github/workflows/smokeshow.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: Smokeshow
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [Test]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
smokeshow:
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
- run: pip install smokeshow
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: coverage-html
|
||||
path: htmlcov
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
- run: smokeshow upload htmlcov
|
||||
env:
|
||||
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage}
|
||||
SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 95
|
||||
SMOKESHOW_GITHUB_CONTEXT: coverage
|
||||
SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }}
|
||||
53
.github/workflows/test-redistribute.yml
vendored
Normal file
@@ -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
|
||||
113
.github/workflows/test.yml
vendored
@@ -5,58 +5,117 @@ on:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
debug_enabled:
|
||||
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
||||
required: false
|
||||
default: false
|
||||
default: 'false'
|
||||
schedule:
|
||||
# cron every week on monday
|
||||
- cron: "0 0 * * 1"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.6.15", "3.7", "3.8", "3.9", "3.10"]
|
||||
python-version:
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
pydantic-version:
|
||||
- pydantic-v1
|
||||
- pydantic-v2
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
# Allow debugging with tmate
|
||||
- name: Setup tmate session
|
||||
uses: mxschmitt/action-tmate@v3
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
|
||||
with:
|
||||
limit-access-to-actor: true
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/cache@v3
|
||||
id: cache
|
||||
with:
|
||||
path: ${{ env.pythonLocation }}
|
||||
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root
|
||||
- name: Install poetry
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
# TODO: remove python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
|
||||
# once there's a release of Poetry 1.2.x including poetry-core > 1.1.0a6
|
||||
# Ref: https://github.com/python-poetry/poetry-core/pull/188
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
|
||||
python -m pip install "poetry==1.2.0a2"
|
||||
python -m poetry plugin 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') }}-v01
|
||||
- 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 --upgrade "pydantic>=1.10.0,<2.0.0"
|
||||
- name: Install Pydantic v2
|
||||
if: matrix.pydantic-version == 'pydantic-v2'
|
||||
run: pip install --upgrade "pydantic>=2.0.2,<3.0.0" "typing-extensions==4.6.1"
|
||||
- name: Lint
|
||||
if: ${{ matrix.python-version != '3.6.15' }}
|
||||
run: python -m poetry run bash scripts/lint.sh
|
||||
# Do not run on Python 3.7 as mypy behaves differently
|
||||
if: matrix.python-version != '3.7' && matrix.pydantic-version == 'pydantic-v2'
|
||||
run: bash scripts/lint.sh
|
||||
- run: mkdir coverage
|
||||
- name: Test
|
||||
run: python -m poetry run bash scripts/test.sh
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v2
|
||||
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@v4
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-${{ matrix.pydantic-version }}
|
||||
path: coverage
|
||||
coverage-combine:
|
||||
needs:
|
||||
- test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.8'
|
||||
|
||||
- name: Get coverage files
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: coverage-*
|
||||
path: coverage
|
||||
merge-multiple: true
|
||||
|
||||
- run: pip install coverage[toml]
|
||||
|
||||
- run: ls -la coverage
|
||||
- run: coverage combine coverage
|
||||
- run: coverage report
|
||||
- run: coverage html --show-contexts --title "Coverage for ${{ github.sha }}"
|
||||
|
||||
- name: Store coverage HTML
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-html
|
||||
path: htmlcov
|
||||
|
||||
# https://github.com/marketplace/actions/alls-green#why
|
||||
alls-green: # This job does nothing and is only used for the branch protection
|
||||
if: always()
|
||||
needs:
|
||||
- coverage-combine
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Decide whether the needed jobs succeeded or failed
|
||||
uses: re-actors/alls-green@release/v1
|
||||
with:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
|
||||
2
.gitignore
vendored
@@ -7,7 +7,7 @@ poetry.lock
|
||||
dist
|
||||
htmlcov
|
||||
*.egg-info
|
||||
.coverage
|
||||
.coverage*
|
||||
coverage.xml
|
||||
site
|
||||
*.db
|
||||
|
||||
25
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
default_language_version:
|
||||
python: python3.10
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-toml
|
||||
- id: check-yaml
|
||||
args:
|
||||
- --unsafe
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.4.7
|
||||
hooks:
|
||||
- id: ruff
|
||||
args:
|
||||
- --fix
|
||||
- id: ruff-format
|
||||
ci:
|
||||
autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
|
||||
autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate
|
||||
24
CITATION.cff
Normal file
@@ -0,0 +1,24 @@
|
||||
# This CITATION.cff file was generated with cffinit.
|
||||
# Visit https://bit.ly/cffinit to generate yours today!
|
||||
|
||||
cff-version: 1.2.0
|
||||
title: SQLModel
|
||||
message: >-
|
||||
If you use this software, please cite it using the
|
||||
metadata from this file.
|
||||
type: software
|
||||
authors:
|
||||
- given-names: Sebastián
|
||||
family-names: Ramírez
|
||||
email: tiangolo@gmail.com
|
||||
identifiers:
|
||||
repository-code: 'https://github.com/tiangolo/sqlmodel'
|
||||
url: 'https://sqlmodel.tiangolo.com'
|
||||
abstract: >-
|
||||
SQLModel, SQL databases in Python, designed for
|
||||
simplicity, compatibility, and robustness.
|
||||
keywords:
|
||||
- fastapi
|
||||
- pydantic
|
||||
- sqlalchemy
|
||||
license: MIT
|
||||
17
README.md
@@ -11,9 +11,8 @@
|
||||
<a href="https://github.com/tiangolo/sqlmodel/actions?query=workflow%3APublish" target="_blank">
|
||||
<img src="https://github.com/tiangolo/sqlmodel/workflows/Publish/badge.svg" alt="Publish">
|
||||
</a>
|
||||
<a href="https://codecov.io/gh/tiangolo/sqlmodel" target="_blank">
|
||||
<img src="https://img.shields.io/codecov/c/github/tiangolo/sqlmodel?color=%2334D058" alt="Coverage">
|
||||
</a>
|
||||
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/tiangolo/sqlmodel" target="_blank">
|
||||
<img src="https://coverage-badge.samuelcolvin.workers.dev/tiangolo/sqlmodel.svg" alt="Coverage">
|
||||
<a href="https://pypi.org/project/sqlmodel" target="_blank">
|
||||
<img src="https://img.shields.io/pypi/v/sqlmodel?color=%2334D058&label=pypi%20package" alt="Package version">
|
||||
</a>
|
||||
@@ -39,6 +38,14 @@ The key features are:
|
||||
* **Extensible**: You have all the power of SQLAlchemy and Pydantic underneath.
|
||||
* **Short**: Minimize code duplication. A single type annotation does a lot of work. No need to duplicate models in SQLAlchemy and Pydantic.
|
||||
|
||||
## Sponsors
|
||||
|
||||
<!-- sponsors -->
|
||||
|
||||
<a href="https://www.govcert.lu" target="_blank" title="This project is being supported by GOVCERT.LU"><img src="https://sqlmodel.tiangolo.com/img/sponsors/govcert.png"></a>
|
||||
|
||||
<!-- /sponsors -->
|
||||
|
||||
## SQL Databases in FastAPI
|
||||
|
||||
<a href="https://fastapi.tiangolo.com" target="_blank"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" style="width: 20%;"></a>
|
||||
@@ -51,7 +58,7 @@ It combines SQLAlchemy and Pydantic and tries to simplify the code you write as
|
||||
|
||||
## Requirements
|
||||
|
||||
A recent and currently supported version of Python (right now, <a href="https://www.python.org/downloads/" class="external-link" target="_blank">Python supports versions 3.6 and above</a>).
|
||||
A recent and currently supported <a href="https://www.python.org/downloads/" class="external-link" target="_blank">version of Python</a>.
|
||||
|
||||
As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. They will be automatically installed when you install SQLModel.
|
||||
|
||||
@@ -69,7 +76,7 @@ Successfully installed sqlmodel
|
||||
|
||||
## Example
|
||||
|
||||
For an introduction to databases, SQL, and everything else, see the <a href="https://sqlmodel.tiangolo.com" target="_blank">SQLModel documentation</a>.
|
||||
For an introduction to databases, SQL, and everything else, see the <a href="https://sqlmodel.tiangolo.com/databases/" target="_blank">SQLModel documentation</a>.
|
||||
|
||||
Here's a quick example. ✨
|
||||
|
||||
|
||||
6
data/sponsors.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
gold: []
|
||||
silver:
|
||||
- url: https://www.govcert.lu
|
||||
title: This project is being supported by GOVCERT.LU
|
||||
img: https://sqlmodel.tiangolo.com/img/sponsors/govcert.png
|
||||
bronze: []
|
||||
@@ -19,36 +19,59 @@ In most cases this would probably not be a problem, for example measuring views
|
||||
|
||||
## Decimal Types
|
||||
|
||||
Pydantic has special support for `Decimal` types using the <a href="https://pydantic-docs.helpmanual.io/usage/types/#arguments-to-condecimal" class="external-link" target="_blank">`condecimal()` special function</a>.
|
||||
Pydantic has special support for <a href="https://docs.pydantic.dev/latest/api/standard_library_types/#decimaldecimal" class="external-link" target="_blank">`Decimal` types</a>.
|
||||
|
||||
!!! tip
|
||||
Pydantic 1.9, that will be released soon, has improved support for `Decimal` types, without needing to use the `condecimal()` function.
|
||||
When you use `Decimal` you can specify the number of digits and decimal places to support in the `Field()` function. They will be validated by Pydantic (for example when using FastAPI) and the same information will also be used for the database columns.
|
||||
|
||||
But meanwhile, you can already use this feature with `condecimal()` in **SQLModel** it as it's explained here.
|
||||
/// info
|
||||
|
||||
When you use `condecimal()` you can specify the number of digits and decimal places to support. They will be validated by Pydantic (for example when using FastAPI) and the same information will also be used for the database columns.
|
||||
For the database, **SQLModel** will use <a href="https://docs.sqlalchemy.org/en/20/core/type_basics.html#sqlalchemy.types.DECIMAL" class="external-link" target="_blank">SQLAlchemy's `DECIMAL` type</a>.
|
||||
|
||||
!!! info
|
||||
For the database, **SQLModel** will use <a href="https://docs.sqlalchemy.org/en/14/core/type_basics.html#sqlalchemy.types.DECIMAL" class="external-link" target="_blank">SQLAlchemy's `DECIMAL` type</a>.
|
||||
///
|
||||
|
||||
## Decimals in SQLModel
|
||||
|
||||
Let's say that each hero in the database will have an amount of money. We could make that field a `Decimal` type using the `condecimal()` function:
|
||||
|
||||
```{.python .annotate hl_lines="12" }
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```python hl_lines="11"
|
||||
{!./docs_src/advanced/decimal/tutorial001_py310.py[ln:1-11]!}
|
||||
|
||||
# More code here later 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```python hl_lines="12"
|
||||
{!./docs_src/advanced/decimal/tutorial001.py[ln:1-12]!}
|
||||
|
||||
# More code here later 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!./docs_src/advanced/decimal/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python
|
||||
{!./docs_src/advanced/decimal/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Here we are saying that `money` can have at most `5` digits with `max_digits`, **this includes the integers** (to the left of the decimal dot) **and the decimals** (to the right of the decimal dot).
|
||||
|
||||
@@ -72,13 +95,30 @@ We are also saying that the number of decimal places (to the right of the decima
|
||||
* `123`
|
||||
* Even though this number doesn't have any decimals, we still have 3 places saved for them, which means that we can **only use 2 places** for the **integer part**, and this number has 3 integer digits. So, the allowed number of integer digits is `max_digits` - `decimal_places` = 2.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Make sure you adjust the number of digits and decimal places for your own needs, in your own application. 🤓
|
||||
|
||||
///
|
||||
|
||||
## Create models with Decimals
|
||||
|
||||
When creating new models you can actually pass normal (`float`) numbers, Pydantic will automatically convert them to `Decimal` types, and **SQLModel** will store them as `Decimal` types in the database (using SQLAlchemy).
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="4-6"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/advanced/decimal/tutorial001_py310.py[ln:24-34]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="4-6"
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -87,19 +127,46 @@ When creating new models you can actually pass normal (`float`) numbers, Pydanti
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!./docs_src/advanced/decimal/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python
|
||||
{!./docs_src/advanced/decimal/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Select Decimal data
|
||||
|
||||
Then, when working with Decimal types, you can confirm that they indeed avoid those rounding errors from floats:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="15-16"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/advanced/decimal/tutorial001_py310.py[ln:37-50]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="15-16"
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -108,14 +175,27 @@ Then, when working with Decimal types, you can confirm that they indeed avoid th
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!./docs_src/advanced/decimal/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python
|
||||
{!./docs_src/advanced/decimal/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Review the results
|
||||
|
||||
@@ -142,7 +222,10 @@ Total money: 3.300
|
||||
|
||||
</div>
|
||||
|
||||
!!! warning
|
||||
/// warning
|
||||
|
||||
Although Decimal types are supported and used in the Python side, not all databases support it. In particular, SQLite doesn't support decimals, so it will convert them to the same floating `NUMERIC` type it supports.
|
||||
|
||||
But decimals are supported by most of the other SQL databases. 🎉
|
||||
|
||||
///
|
||||
|
||||
@@ -6,10 +6,6 @@ First, you might want to see the basic ways to [help SQLModel and get help](help
|
||||
|
||||
If you already cloned the repository and you know that you need to deep dive in the code, here are some guidelines to set up your environment.
|
||||
|
||||
### Python
|
||||
|
||||
SQLModel supports Python 3.6 and above, but for development you should have at least **Python 3.7**.
|
||||
|
||||
### Poetry
|
||||
|
||||
**SQLModel** uses <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a> to build, package, and publish the project.
|
||||
@@ -116,7 +112,7 @@ There is a script that you can run locally to test all the code and generate cov
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ bash scripts/test-cov-html.sh
|
||||
$ bash scripts/test.sh
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
# Intro to Databases
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
Are you a seasoned developer and already know everything about databases? 🤓
|
||||
|
||||
Then you can skip to the [Tutorial - User Guide: First Steps](tutorial/index.md){.internal-link target=_blank} right away.
|
||||
|
||||
///
|
||||
|
||||
If you don't know everything about databases, here's a quick overview.
|
||||
|
||||
You can always study much more on your own later.
|
||||
@@ -17,9 +20,12 @@ So, what is a database?
|
||||
|
||||
A **database** is a system to store and manage data in a structured and very efficient way.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
It's very common to abbreviate the word "database" as **"DB"**.
|
||||
|
||||
///
|
||||
|
||||
As there's a lot of information about databases, and it can get very technical and academic, I'll give you a quick overview about some of the main concepts here.
|
||||
|
||||
I'll even tell you a bit about different types of databases, including the ones not covered by SQLModel ("NoSQL" databases).
|
||||
@@ -28,9 +34,12 @@ I'll even tell you a bit about different types of databases, including the ones
|
||||
|
||||
When starting to program, it might **not be obvious** why having a database apart from the code for your program is a **good idea**. Let's start with that.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
If that's obvious to you, just continue in the next section below. 👇
|
||||
|
||||
///
|
||||
|
||||
In your code you already have **variables**, **dictionaries**, **lists**, etc. They all store **data** in some way already. Why would you need to have a separate database?
|
||||
|
||||
If you look closely, your code is **static**, it doesn't really change over time *once you run it*. Of course, you change the code frequently, adding features, etc, but once you start Python running your code, the program stays as it was when you started it. And if you change the code, the program will only change **once you run it again**.
|
||||
@@ -128,7 +137,7 @@ If we worked with a single table to store our heroes, it could be like this:
|
||||
<th>id</th><th>name</th><th>secret_name</th><th>age</th><th>team</th><th>headquarters</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td><td>Deadpond</td><td>Dive Wilson</td><td>null</td><td>Z-Factor</td><td>Sister Margaret’s Bar</td>
|
||||
<td>1</td><td>Deadpond</td><td>Dive Wilson</td><td>null</td><td>Z-Factor</td><td>Sister Margaret's Bar</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td><td>Preventers</td><td>Sharp Tower</td>
|
||||
@@ -157,7 +166,7 @@ We could end up with inconsistent information, having one place saying "Prevente
|
||||
<th>id</th><th>name</th><th>secret_name</th><th>age</th><th>team</th><th>headquarters</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td><td>Deadpond</td><td>Dive Wilson</td><td>null</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
||||
<td>1</td><td>Deadpond</td><td>Dive Wilson</td><td>null</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td><td>Preventers</td><td>Preventers Tower ✅</td>
|
||||
@@ -176,7 +185,7 @@ We could forget the name of the team and end up adding "Mahjong" with an invalid
|
||||
<th>id</th><th>name</th><th>secret_name</th><th>age</th><th>team</th><th>headquarters</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td><td>Deadpond</td><td>Dive Wilson</td><td>null</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
||||
<td>1</td><td>Deadpond</td><td>Dive Wilson</td><td>null</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td><td>Preventers</td><td>Preventers Tower</td>
|
||||
@@ -185,7 +194,7 @@ We could forget the name of the team and end up adding "Mahjong" with an invalid
|
||||
<td>3</td><td>Rusty-Man</td><td>Tommy Sharp</td><td>48</td><td>Preventers</td><td>Sharp Tower</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td><td>Mahjong</td><td>Neena Thurgirl</td><td>31</td><td>Y-Force 🚨</td><td>Sister Margaret’s Bar</td>
|
||||
<td>4</td><td>Mahjong</td><td>Neena Thurgirl</td><td>31</td><td>Y-Force 🚨</td><td>Sister Margaret's Bar</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -207,7 +216,7 @@ The table for the teams could look like this:
|
||||
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -308,9 +317,12 @@ Next, it receives the data and puts it in Python objects that you can continue t
|
||||
|
||||
I'll tell you more about SQL, SQLModel, how to use them, and how they are related in the next sections.
|
||||
|
||||
!!! info "Technical Details"
|
||||
/// info | Technical Details
|
||||
|
||||
SQLModel is built on top of SQLAlchemy. It is, in fact, just <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> and <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a> mixed together with some sugar on top.
|
||||
|
||||
///
|
||||
|
||||
## NoSQL Databases
|
||||
|
||||
Although SQL Databases are the oldest and most commonly used type of database, there's another (very interesting) category, the one of **NoSQL Databases**.
|
||||
|
||||
@@ -62,7 +62,7 @@ The user is probably, in some way, telling your application:
|
||||
2
|
||||
```
|
||||
|
||||
And the would be this table (with a single row):
|
||||
And the result would be this table (with a single row):
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
@@ -111,7 +111,7 @@ DROP TABLE hero;
|
||||
|
||||
That is how you tell the database in SQL to delete the entire table `hero`.
|
||||
|
||||
<a href="http://www.nooooooooooooooo.com/" class="external-link" target="_blank">Nooooo!</a> We lost all the data in the `hero` table! 💥😱
|
||||
<a href="https://theuselessweb.site/nooooooooooooooo/" class="external-link" target="_blank">Nooooo!</a> We lost all the data in the `hero` table! 💥😱
|
||||
|
||||
### SQL Sanitization
|
||||
|
||||
@@ -172,9 +172,12 @@ The difference in the final SQL statement is subtle, but it changes the meaning
|
||||
SELECT * FROM hero WHERE id = "2; DROP TABLE hero;";
|
||||
```
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Notice the double quotes (`"`) making it a string instead of more raw SQL.
|
||||
|
||||
///
|
||||
|
||||
The database will not find any record with that ID:
|
||||
|
||||
```SQL
|
||||
@@ -187,9 +190,12 @@ Then your code will continue to execute and calmly tell the user that it couldn'
|
||||
|
||||
But we never deleted the `hero` table. 🎉
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
Of course, there are also other ways to do SQL data sanitization without using a tool like **SQLModel**, but it's still a nice feature you get by default.
|
||||
|
||||
///
|
||||
|
||||
### Editor Support
|
||||
|
||||
Check that Python snippet above again.
|
||||
@@ -291,9 +297,12 @@ There are many ORMs available apart from **SQLModel**, you can read more about s
|
||||
|
||||
## SQL Table Names
|
||||
|
||||
!!! info "Technical Background"
|
||||
/// info | Technical Background
|
||||
|
||||
This is a bit of boring background for SQL purists. Feel free to skip this section. 😉
|
||||
|
||||
///
|
||||
|
||||
When working with pure SQL, it's common to name the tables in plural. So, the table would be named `heroes` instead of `hero`, because it could contain multiple rows, each with one hero.
|
||||
|
||||
Nevertheless, **SQLModel** and many other similar tools can generate a table name automatically from your code, as you will see later in the tutorial.
|
||||
@@ -304,5 +313,8 @@ You will see **your own code** a lot more than the internal table names, so it's
|
||||
|
||||
So, to keep things consistent, I'll keep using the same table names that **SQLModel** would have generated.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
You can also override the table name. You can read about it in the Advanced User Guide.
|
||||
|
||||
///
|
||||
|
||||
@@ -12,7 +12,7 @@ Nevertheless, SQLModel is completely **independent** of FastAPI and can be used
|
||||
|
||||
## Just Modern Python
|
||||
|
||||
It's all based on standard <abbr title="Python currently supported versions, 3.6 and above.">modern **Python**</abbr> type annotations. No new syntax to learn. Just standard modern Python.
|
||||
It's all based on standard <abbr title="Currently supported versions of Python">modern **Python**</abbr> type annotations. No new syntax to learn. Just standard modern Python.
|
||||
|
||||
If you need a 2 minute refresher of how to use Python types (even if you don't use SQLModel or FastAPI), check the FastAPI tutorial section: <a href="https://fastapi.tiangolo.com/python-types/" class="external-link" target="_blank">Python types intro</a>.
|
||||
|
||||
@@ -36,17 +36,10 @@ You will get completion for everything while writing the **minimum** amount of c
|
||||
|
||||
You won't need to keep guessing the types of different attributes in your models, if they could be `None`, etc. Your editor will be able to help you with everything because **SQLModel** is based on **standard Python type annotations**.
|
||||
|
||||
**SQLModel** even adopts currently <a href="https://github.com/microsoft/pyright/blob/main/specs/dataclass_transforms.md" class="external-link" target="_blank">in development standards</a> for Python type annotations to ensure the **best developer experience**, so you will get inline errors and autocompletion even while creating new model instances.
|
||||
**SQLModel** adopts <a href="https://peps.python.org/pep-0681/" class="external-link" target="_blank">PEP 681</a> for Python type annotations to ensure the **best developer experience**, so you will get inline errors and autocompletion even while creating new model instances.
|
||||
|
||||
<img class="shadow" src="/img/index/autocompletion01.png">
|
||||
|
||||
!!! info
|
||||
Don't worry, adopting this in-development standard only affects/improves editor support.
|
||||
|
||||
It doesn't affect performance or correctness. And if the in-progress standard was deprecated your code won't be affected.
|
||||
|
||||
Meanwhile, you will get inline errors (like type checks) and autocompletion on places you wouldn't get with any other library. 🎉
|
||||
|
||||
## Short
|
||||
|
||||
**SQLModel** has **sensible defaults** for everything, with **optional configurations** everywhere.
|
||||
|
||||
156
docs/help.md
@@ -58,26 +58,128 @@ You can:
|
||||
|
||||
I love to hear about how **SQLModel** is being used, what you have liked in it, in which project/company are you using it, etc.
|
||||
|
||||
## Help others with issues in GitHub
|
||||
## Help others with questions in GitHub
|
||||
|
||||
You can see <a href="https://github.com/tiangolo/sqlmodel/issues" class="external-link" target="_blank">existing issues</a> and try and help others, most of the times they are questions that you might already know the answer for. 🤓
|
||||
You can try and help others with their questions in:
|
||||
|
||||
* <a href="https://github.com/tiangolo/sqlmodel/discussions/categories/questions?discussions_q=category%3AQuestions+is%3Aunanswered" class="external-link" target="_blank">GitHub Discussions</a>
|
||||
* <a href="https://github.com/tiangolo/sqlmodel/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aquestion+-label%3Aanswered+" class="external-link" target="_blank">GitHub Issues</a>
|
||||
|
||||
In many cases you might already know the answer for those questions. 🤓
|
||||
|
||||
Just remember, the most important point is: try to be kind. People come with their frustrations and in many cases don't ask in the best way, but try as best as you can to be kind. 🤗
|
||||
|
||||
The idea is for the **SQLModel** community to be kind and welcoming. At the same time, don't accept bullying or disrespectful behavior towards others. We have to take care of each other.
|
||||
|
||||
---
|
||||
|
||||
Here's how to help others with questions (in discussions or issues):
|
||||
|
||||
### Understand the question
|
||||
|
||||
* Check if you can understand what is the **purpose** and use case of the person asking.
|
||||
|
||||
* Then check if the question (the vast majority are questions) is **clear**.
|
||||
|
||||
* In many cases the question asked is about an imaginary solution from the user, but there might be a **better** one. If you can understand the problem and use case better, you might be able to suggest a better **alternative solution**.
|
||||
|
||||
* If you can't understand the question, ask for more **details**.
|
||||
|
||||
### Reproduce the problem
|
||||
|
||||
For most of the cases and most of the questions there's something related to the person's **original code**.
|
||||
|
||||
In many cases they will only copy a fragment of the code, but that's not enough to **reproduce the problem**.
|
||||
|
||||
* You can ask them to provide a <a href="https://stackoverflow.com/help/minimal-reproducible-example" class="external-link" target="_blank">minimal, reproducible, example</a>, that you can **copy-paste** and run locally to see the same error or behavior they are seeing, or to understand their use case better.
|
||||
|
||||
* If you are feeling too generous, you can try to **create an example** like that yourself, just based on the description of the problem. Just have in mind that this might take a lot of time and it might be better to ask them to clarify the problem first.
|
||||
|
||||
### Suggest solutions
|
||||
|
||||
* After being able to understand the question, you can give them a possible **answer**.
|
||||
|
||||
* In many cases, it's better to understand their **underlying problem or use case**, because there might be a better way to solve it than what they are trying to do.
|
||||
|
||||
### Ask to close
|
||||
|
||||
If they reply, there's a high chance you would have solved their problem, congrats, **you're a hero**! 🦸
|
||||
|
||||
* Now, if that solved their problem, you can ask them to:
|
||||
|
||||
* In GitHub Discussions: mark the comment as the **answer**.
|
||||
* In GitHub Issues: **close** the issue**.
|
||||
|
||||
## Watch the GitHub repository
|
||||
|
||||
You can "watch" SQLModel in GitHub (clicking the "watch" button at the top right): <a href="https://github.com/tiangolo/sqlmodel" class="external-link" target="_blank">https://github.com/tiangolo/sqlmodel</a>. 👀
|
||||
|
||||
If you select "Watching" instead of "Releases only" you will receive notifications when someone creates a new issue.
|
||||
If you select "Watching" instead of "Releases only" you will receive notifications when someone creates a new issue or question. You can also specify that you only want to be notified about new issues, or discussions, or PRs, etc.
|
||||
|
||||
Then you can try and help them solve those issues.
|
||||
Then you can try and help them solve those questions.
|
||||
|
||||
## Create issues
|
||||
## Ask Questions
|
||||
|
||||
You can <a href="https://github.com/tiangolo/sqlmodel/issues/new/choose" class="external-link" target="_blank">create a new issue</a> in the GitHub repository, for example to:
|
||||
You can <a href="https://github.com/tiangolo/sqlmodel/discussions/new?category=questions" class="external-link" target="_blank">create a new question</a> in the GitHub repository, for example to:
|
||||
|
||||
* Ask a **question** or ask about a **problem**.
|
||||
* Suggest a new **feature**.
|
||||
|
||||
**Note**: if you create an issue, then I'm going to ask you to also help others. 😉
|
||||
**Note**: if you do it, then I'm going to ask you to also help others. 😉
|
||||
|
||||
## Review Pull Requests
|
||||
|
||||
You can help me review pull requests from others.
|
||||
|
||||
Again, please try your best to be kind. 🤗
|
||||
|
||||
---
|
||||
|
||||
Here's what to have in mind and how to review a pull request:
|
||||
|
||||
### Understand the problem
|
||||
|
||||
* First, make sure you **understand the problem** that the pull request is trying to solve. It might have a longer discussion in a GitHub Discussion or issue.
|
||||
|
||||
* There's also a good chance that the pull request is not actually needed because the problem can be solved in a **different way**. Then you can suggest or ask about that.
|
||||
|
||||
### Don't worry about style
|
||||
|
||||
* Don't worry too much about things like commit message styles, I will squash and merge customizing the commit manually.
|
||||
|
||||
* Also don't worry about style rules, there are already automatized tools checking that.
|
||||
|
||||
And if there's any other style or consistency need, I'll ask directly for that, or I'll add commits on top with the needed changes.
|
||||
|
||||
### Check the code
|
||||
|
||||
* Check and read the code, see if it makes sense, **run it locally** and see if it actually solves the problem.
|
||||
|
||||
* Then **comment** saying that you did that, that's how I will know you really checked it.
|
||||
|
||||
/// info
|
||||
|
||||
Unfortunately, I can't simply trust PRs that just have several approvals.
|
||||
|
||||
Several times it has happened that there are PRs with 3, 5 or more approvals, probably because the description is appealing, but when I check the PRs, they are actually broken, have a bug, or don't solve the problem they claim to solve. 😅
|
||||
|
||||
So, it's really important that you actually read and run the code, and let me know in the comments that you did. 🤓
|
||||
|
||||
///
|
||||
|
||||
* If the PR can be simplified in a way, you can ask for that, but there's no need to be too picky, there might be a lot of subjective points of view (and I will have my own as well 🙈), so it's better if you can focus on the fundamental things.
|
||||
|
||||
### Tests
|
||||
|
||||
* Help me check that the PR has **tests**.
|
||||
|
||||
* Check that the tests **fail** before the PR. 🚨
|
||||
|
||||
* Then check that the tests **pass** after the PR. ✅
|
||||
|
||||
* Many PRs don't have tests, you can **remind** them to add tests, or you can even **suggest** some tests yourself. That's one of the things that consume most time and you can help a lot with that.
|
||||
|
||||
* Then also comment what you tried, that way I'll know that you checked it. 🤓
|
||||
|
||||
## Create a Pull Request
|
||||
|
||||
@@ -86,7 +188,47 @@ You can [contribute](contributing.md){.internal-link target=_blank} to the sourc
|
||||
* To fix a typo you found on the documentation.
|
||||
* To propose new documentation sections.
|
||||
* To fix an existing issue/bug.
|
||||
* Make sure to add tests.
|
||||
* To add a new feature.
|
||||
* Make sure to add tests.
|
||||
* Make sure to add documentation if it's relevant.
|
||||
|
||||
## Help Maintain SQLModel
|
||||
|
||||
Help me maintain **SQLModel**! 🤓
|
||||
|
||||
There's a lot of work to do, and for most of it, **YOU** can do it.
|
||||
|
||||
The main tasks that you can do right now are:
|
||||
|
||||
* [Help others with questions in GitHub](#help-others-with-questions-in-github){.internal-link target=_blank} (see the section above).
|
||||
* [Review Pull Requests](#review-pull-requests){.internal-link target=_blank} (see the section above).
|
||||
|
||||
Those two tasks are what **consume time the most**. That's the main work of maintaining SQLModel.
|
||||
|
||||
If you can help me with that, **you are helping me maintain SQLModel** and making sure it keeps **advancing faster and better**. 🚀
|
||||
|
||||
## Join the chat
|
||||
|
||||
Join the 👥 <a href="https://discord.gg/VQjSZaeJmf" class="external-link" target="_blank">FastAPI and Friends Discord chat server</a> 👥 and hang out with others in the community. There's a `#sqlmodel` channel.
|
||||
|
||||
/// tip
|
||||
|
||||
For questions, ask them in <a href="https://github.com/tiangolo/sqlmodel/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussions</a>, there's a much better chance you will receive help there.
|
||||
|
||||
Use the chat only for other general conversations.
|
||||
|
||||
///
|
||||
|
||||
### Don't use the chat for questions
|
||||
|
||||
Have in mind that as chats allow more "free conversation", it's easy to ask questions that are too general and more difficult to answer, so, you might not receive answers.
|
||||
|
||||
In GitHub, the template will guide you to write the right question so that you can more easily get a good answer, or even solve the problem yourself even before asking. And in GitHub I can make sure I always answer everything, even if it takes some time. I can't personally do that with the chat. 😅
|
||||
|
||||
Conversations in the chat are also not as easily searchable as in GitHub, so questions and answers might get lost in the conversation.
|
||||
|
||||
On the other side, there are thousands of users in the chat, so there's a high chance you'll find someone to talk to there, almost all the time. 😄
|
||||
|
||||
## Sponsor the author
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
<mxCell id="56" value="<span style="font-family: &#34;roboto&#34; ; font-size: 18px">Z-Force</span>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;strokeColor=none;fillColor=none;" parent="54" vertex="1">
|
||||
<mxGeometry x="50" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="57" value="<p style="background-color: rgb(255 , 255 , 255) ; line-height: 19px"><font face="Roboto" data-font-src="https://fonts.googleapis.com/css?family=Roboto" style="font-size: 18px">Sister Margaret’s Bar</font></p>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;strokeColor=none;fillColor=none;" parent="54" vertex="1">
|
||||
<mxCell id="57" value="<p style="background-color: rgb(255 , 255 , 255) ; line-height: 19px"><font face="Roboto" data-font-src="https://fonts.googleapis.com/css?family=Roboto" style="font-size: 18px">Sister Margaret's Bar</font></p>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;strokeColor=none;fillColor=none;" parent="54" vertex="1">
|
||||
<mxGeometry x="160" width="200" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="66" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;strokeWidth=2;" parent="1" source="18" target="54" edge="1">
|
||||
|
||||
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
BIN
docs/img/sponsors/govcert.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 87 KiB |
@@ -103,7 +103,7 @@
|
||||
<mxCell id="56" value="<span style="font-family: &#34;roboto&#34; ; font-size: 18px">Z-Force</span>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="54" vertex="1">
|
||||
<mxGeometry x="50" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="57" value="<p style="line-height: 19px"><font face="Roboto" data-font-src="https://fonts.googleapis.com/css?family=Roboto" style="font-size: 18px">Sister Margaret’s Bar</font></p>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="54" vertex="1">
|
||||
<mxCell id="57" value="<p style="line-height: 19px"><font face="Roboto" data-font-src="https://fonts.googleapis.com/css?family=Roboto" style="font-size: 18px">Sister Margaret's Bar</font></p>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="54" vertex="1">
|
||||
<mxGeometry x="160" width="200" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="69" value="<font face="Roboto" data-font-src="https://fonts.googleapis.com/css?family=Roboto" style="font-size: 18px">heroteamlink</font>" style="shape=table;html=1;whiteSpace=wrap;startSize=30;container=1;collapsible=0;childLayout=tableLayout;fontStyle=1;align=center;fillColor=#FFFFFF;swimlaneFillColor=#ffffff;" vertex="1" parent="1">
|
||||
|
||||
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
@@ -115,7 +115,7 @@
|
||||
<mxCell id="56" value="<span style="font-family: &#34;roboto&#34; ; font-size: 18px">Z-Force</span>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;" parent="54" vertex="1">
|
||||
<mxGeometry x="50" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="57" value="<p style="background-color: rgb(255 , 255 , 255) ; line-height: 19px"><font face="Roboto" data-font-src="https://fonts.googleapis.com/css?family=Roboto" style="font-size: 18px">Sister Margaret’s Bar</font></p>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;" parent="54" vertex="1">
|
||||
<mxCell id="57" value="<p style="background-color: rgb(255 , 255 , 255) ; line-height: 19px"><font face="Roboto" data-font-src="https://fonts.googleapis.com/css?family=Roboto" style="font-size: 18px">Sister Margaret's Bar</font></p>" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;top=0;left=0;bottom=0;right=0;overflow=hidden;" parent="54" vertex="1">
|
||||
<mxGeometry x="160" width="200" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="66" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;strokeWidth=2;" parent="1" source="18" target="54" edge="1">
|
||||
|
||||
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
@@ -1,3 +1,7 @@
|
||||
<style>
|
||||
.md-content .md-typeset h1 { display: none; }
|
||||
</style>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://sqlmodel.tiangolo.com"><img src="https://sqlmodel.tiangolo.com/img/logo-margin/logo-margin-vector.svg" alt="SQLModel"></a>
|
||||
</p>
|
||||
@@ -11,9 +15,8 @@
|
||||
<a href="https://github.com/tiangolo/sqlmodel/actions?query=workflow%3APublish" target="_blank">
|
||||
<img src="https://github.com/tiangolo/sqlmodel/workflows/Publish/badge.svg" alt="Publish">
|
||||
</a>
|
||||
<a href="https://codecov.io/gh/tiangolo/sqlmodel" target="_blank">
|
||||
<img src="https://img.shields.io/codecov/c/github/tiangolo/sqlmodel?color=%2334D058" alt="Coverage">
|
||||
</a>
|
||||
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/tiangolo/sqlmodel" target="_blank">
|
||||
<img src="https://coverage-badge.samuelcolvin.workers.dev/tiangolo/sqlmodel.svg" alt="Coverage">
|
||||
<a href="https://pypi.org/project/sqlmodel" target="_blank">
|
||||
<img src="https://img.shields.io/pypi/v/sqlmodel?color=%2334D058&label=pypi%20package" alt="Package version">
|
||||
</a>
|
||||
@@ -39,6 +42,21 @@ The key features are:
|
||||
* **Extensible**: You have all the power of SQLAlchemy and Pydantic underneath.
|
||||
* **Short**: Minimize code duplication. A single type annotation does a lot of work. No need to duplicate models in SQLAlchemy and Pydantic.
|
||||
|
||||
## Sponsors
|
||||
|
||||
<!-- sponsors -->
|
||||
|
||||
{% if sponsors %}
|
||||
{% for sponsor in sponsors.gold -%}
|
||||
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||
{% endfor -%}
|
||||
{%- for sponsor in sponsors.silver -%}
|
||||
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<!-- /sponsors -->
|
||||
|
||||
## SQL Databases in FastAPI
|
||||
|
||||
<a href="https://fastapi.tiangolo.com" target="_blank"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" style="width: 20%;"></a>
|
||||
@@ -51,7 +69,7 @@ It combines SQLAlchemy and Pydantic and tries to simplify the code you write as
|
||||
|
||||
## Requirements
|
||||
|
||||
A recent and currently supported version of Python (right now, <a href="https://www.python.org/downloads/" class="external-link" target="_blank">Python supports versions 3.6 and above</a>).
|
||||
A recent and currently supported <a href="https://www.python.org/downloads/" class="external-link" target="_blank">version of Python</a>.
|
||||
|
||||
As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. They will be automatically installed when you install SQLModel.
|
||||
|
||||
@@ -69,7 +87,7 @@ Successfully installed sqlmodel
|
||||
|
||||
## Example
|
||||
|
||||
For an introduction to databases, SQL, and everything else, see the <a href="https://sqlmodel.tiangolo.com" target="_blank">SQLModel documentation</a>.
|
||||
For an introduction to databases, SQL, and everything else, see the <a href="https://sqlmodel.tiangolo.com/databases/" target="_blank">SQLModel documentation</a>.
|
||||
|
||||
Here's a quick example. ✨
|
||||
|
||||
|
||||
@@ -1,31 +1 @@
|
||||
{% extends "base.html" %}
|
||||
{%- block scripts %}
|
||||
{{ super() }}
|
||||
<script src="https://cdn.jsdelivr.net/npm/qabot@0.4"></script>
|
||||
<script>
|
||||
// This prevents the global search from interfering with qa-bot's internal text input.
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('qa-bot').forEach((x) => {
|
||||
x.addEventListener('keydown', (event) => {
|
||||
event.stopPropagation();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<qa-bot
|
||||
server="https://tiangolo-sqlmodel.docsqa.jina.ai"
|
||||
theme="infer"
|
||||
title="SQLModel Bot"
|
||||
description="SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness."
|
||||
style="font-size: 0.8rem"
|
||||
>
|
||||
<template>
|
||||
<dl>
|
||||
<dt>You can ask questions about SQLModel. Try:</dt>
|
||||
<dd>Which Python version is supported?</dd>
|
||||
<dd>How SQLModel interacts with the database?</dd>
|
||||
<dd>How can I link tables?</dd>
|
||||
</dl>
|
||||
</template>
|
||||
</qa-bot>
|
||||
{%- endblock %}
|
||||
@@ -2,6 +2,249 @@
|
||||
|
||||
## Latest Changes
|
||||
|
||||
## 0.0.19
|
||||
|
||||
### Fixes
|
||||
|
||||
* 🐛 Fix pydantic `EmailStr` support and `max_length` in several String subclasses. PR [#966](https://github.com/tiangolo/sqlmodel/pull/966) by [@estebanx64](https://github.com/estebanx64).
|
||||
* 🐛 Fix set varchar limit when `max_length` is set on Pydantic models using Pydantic v2. PR [#963](https://github.com/tiangolo/sqlmodel/pull/963) by [@estebanx64](https://github.com/estebanx64).
|
||||
|
||||
### Refactors
|
||||
|
||||
* ♻️ Refactor generate select template to isolate templated code to the minimum. PR [#967](https://github.com/tiangolo/sqlmodel/pull/967) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Upgrades
|
||||
|
||||
* ⬆️ Update minimum SQLAlchemy version to 2.0.14 as that one includes `TryCast` used internally. PR [#964](https://github.com/tiangolo/sqlmodel/pull/964) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Docs
|
||||
|
||||
* ✏️ Fix broken link to `@dataclass_transform` (now PEP 681) in `docs/features.md`. PR [#753](https://github.com/tiangolo/sqlmodel/pull/753) by [@soof-golan](https://github.com/soof-golan).
|
||||
|
||||
### Internal
|
||||
|
||||
* ⬆️ Upgrade Ruff and Black. PR [#968](https://github.com/tiangolo/sqlmodel/pull/968) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ⬆ Bump tiangolo/issue-manager from 0.4.1 to 0.5.0. PR [#922](https://github.com/tiangolo/sqlmodel/pull/922) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* 📌 Pin typing-extensions in tests for compatiblity with Python 3.8, dirty-equals, Pydantic. PR [#965](https://github.com/tiangolo/sqlmodel/pull/965) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 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
|
||||
|
||||
### Features
|
||||
|
||||
* ✨ Add support for Pydantic v2 (while keeping support for v1 if v2 is not available). PR [#722](https://github.com/tiangolo/sqlmodel/pull/722) by [@tiangolo](https://github.com/tiangolo) including initial work in PR [#699](https://github.com/tiangolo/sqlmodel/pull/699) by [@AntonDeMeester](https://github.com/AntonDeMeester).
|
||||
|
||||
## 0.0.13
|
||||
|
||||
### Fixes
|
||||
|
||||
* ♻️ Refactor type generation of selects re-order to prioritize models to optimize editor support. PR [#718](https://github.com/tiangolo/sqlmodel/pull/718) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Refactors
|
||||
|
||||
* 🔇 Do not raise deprecation warnings for execute as it's automatically used internally. PR [#716](https://github.com/tiangolo/sqlmodel/pull/716) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ✅ Move OpenAPI tests inline to simplify updating them with Pydantic v2. PR [#709](https://github.com/tiangolo/sqlmodel/pull/709) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Upgrades
|
||||
|
||||
* ⬆️ Add support for Python 3.11 and Python 3.12. PR [#710](https://github.com/tiangolo/sqlmodel/pull/710) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Docs
|
||||
|
||||
* ✏️ Fix typo, simplify single quote/apostrophe character in "Sister Margaret's" everywhere in the docs. PR [#721](https://github.com/tiangolo/sqlmodel/pull/721) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 📝 Update docs for Decimal, use proper types. PR [#719](https://github.com/tiangolo/sqlmodel/pull/719) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 📝 Add source examples for Python 3.9 and 3.10. PR [#715](https://github.com/tiangolo/sqlmodel/pull/715) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Internal
|
||||
|
||||
* 🙈 Update gitignore, include all coverage files. PR [#711](https://github.com/tiangolo/sqlmodel/pull/711) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 🔧 Update config with new pymdown extensions. PR [#712](https://github.com/tiangolo/sqlmodel/pull/712) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 🔧 Update docs build setup, add support for sponsors, add sponsor GOVCERT.LU. PR [#720](https://github.com/tiangolo/sqlmodel/pull/720) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#697](https://github.com/tiangolo/sqlmodel/pull/697) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
|
||||
* 🔧 Show line numbers in docs during local development. PR [#714](https://github.com/tiangolo/sqlmodel/pull/714) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 📝 Update details syntax with new pymdown extensions format. PR [#713](https://github.com/tiangolo/sqlmodel/pull/713) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.0.12
|
||||
|
||||
### Features
|
||||
|
||||
* ✨ Upgrade SQLAlchemy to 2.0. PR [#700](https://github.com/tiangolo/sqlmodel/pull/700) by [@tiangolo](https://github.com/tiangolo) including initial work in PR [#563](https://github.com/tiangolo/sqlmodel/pull/563) by [@farahats9](https://github.com/farahats9).
|
||||
|
||||
### Internal
|
||||
|
||||
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#686](https://github.com/tiangolo/sqlmodel/pull/686) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
|
||||
* 👷 Upgrade latest-changes GitHub Action. PR [#693](https://github.com/tiangolo/sqlmodel/pull/693) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.0.11
|
||||
|
||||
### Features
|
||||
|
||||
* ✨ Add support for passing a custom SQLAlchemy type to `Field()` with `sa_type`. PR [#505](https://github.com/tiangolo/sqlmodel/pull/505) by [@maru0123-2004](https://github.com/maru0123-2004).
|
||||
* You might consider this a breaking change if you were using an incompatible combination of arguments, those arguments were not taking effect and now you will have a type error and runtime error telling you that.
|
||||
* ✨ Do not allow invalid combinations of field parameters for columns and relationships, `sa_column` excludes `sa_column_args`, `primary_key`, `nullable`, etc. PR [#681](https://github.com/tiangolo/sqlmodel/pull/681) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Docs
|
||||
|
||||
* 🎨 Update inline source examples, hide `#` in annotations (from MkDocs Material). PR [#677](https://github.com/tiangolo/sqlmodel/pull/677) by [@Matthieu-LAURENT39](https://github.com/Matthieu-LAURENT39).
|
||||
|
||||
### Internal
|
||||
|
||||
* ⬆ Update coverage requirement from ^6.2 to >=6.2,<8.0. PR [#663](https://github.com/tiangolo/sqlmodel/pull/663) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Update mkdocs-material requirement from 9.1.21 to 9.2.7. PR [#675](https://github.com/tiangolo/sqlmodel/pull/675) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆️ Upgrade mypy manually. PR [#684](https://github.com/tiangolo/sqlmodel/pull/684) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ⬆ Update black requirement from ^22.10.0 to >=22.10,<24.0. PR [#664](https://github.com/tiangolo/sqlmodel/pull/664) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* 👷 Update CI to build MkDocs Insiders only when the secrets are available, for Dependabot. PR [#683](https://github.com/tiangolo/sqlmodel/pull/683) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.0.10
|
||||
|
||||
### Features
|
||||
|
||||
* ✨ Add support for all `Field` parameters from Pydantic `1.9.0` and above, make Pydantic `1.9.0` the minimum required version. PR [#440](https://github.com/tiangolo/sqlmodel/pull/440) by [@daniil-berg](https://github.com/daniil-berg).
|
||||
|
||||
### Internal
|
||||
|
||||
* 🔧 Adopt Ruff for formatting. PR [#679](https://github.com/tiangolo/sqlmodel/pull/679) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.0.9
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* 🗑️ Deprecate Python 3.6 and upgrade Poetry and Poetry Version Plugin. PR [#627](https://github.com/tiangolo/sqlmodel/pull/627) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Features
|
||||
|
||||
* ✨ Raise a more clear error when a type is not valid. PR [#425](https://github.com/tiangolo/sqlmodel/pull/425) by [@ddanier](https://github.com/ddanier).
|
||||
|
||||
### Fixes
|
||||
|
||||
* 🐛 Fix `AsyncSession` type annotations for `exec()`. PR [#58](https://github.com/tiangolo/sqlmodel/pull/58) by [@Bobronium](https://github.com/Bobronium).
|
||||
* 🐛 Fix allowing using a `ForeignKey` directly, remove repeated column construction from `SQLModelMetaclass.__init__` and upgrade minimum SQLAlchemy to `>=1.4.36`. PR [#443](https://github.com/tiangolo/sqlmodel/pull/443) by [@daniil-berg](https://github.com/daniil-berg).
|
||||
* 🐛 Fix enum type checks ordering in `get_sqlalchemy_type`. PR [#669](https://github.com/tiangolo/sqlmodel/pull/669) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 🐛 Fix SQLAlchemy version 1.4.36 breaks SQLModel relationships (#315). PR [#461](https://github.com/tiangolo/sqlmodel/pull/461) by [@byrman](https://github.com/byrman).
|
||||
|
||||
### Upgrades
|
||||
|
||||
* ⬆️ Upgrade support for SQLAlchemy 1.4.49, update tests. PR [#519](https://github.com/tiangolo/sqlmodel/pull/519) by [@sandrotosi](https://github.com/sandrotosi).
|
||||
* ⬆ Raise SQLAlchemy version requirement to at least `1.4.29` (related to #434). PR [#439](https://github.com/tiangolo/sqlmodel/pull/439) by [@daniil-berg](https://github.com/daniil-berg).
|
||||
|
||||
### Docs
|
||||
|
||||
* 📝 Clarify description of in-memory SQLite database in `docs/tutorial/create-db-and-table.md`. PR [#601](https://github.com/tiangolo/sqlmodel/pull/601) by [@SimonCW](https://github.com/SimonCW).
|
||||
* 📝 Tweak wording in `docs/tutorial/fastapi/multiple-models.md`. PR [#674](https://github.com/tiangolo/sqlmodel/pull/674) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ✏️ Fix contributing instructions to run tests, update script name. PR [#634](https://github.com/tiangolo/sqlmodel/pull/634) by [@PookieBuns](https://github.com/PookieBuns).
|
||||
* 📝 Update link to docs for intro to databases. PR [#593](https://github.com/tiangolo/sqlmodel/pull/593) by [@abenezerBelachew](https://github.com/abenezerBelachew).
|
||||
* 📝 Update docs, use `offset` in example with `limit` and `where`. PR [#273](https://github.com/tiangolo/sqlmodel/pull/273) by [@jbmchuck](https://github.com/jbmchuck).
|
||||
* 📝 Fix docs for Pydantic's fields using `le` (`lte` is invalid, use `le` ). PR [#207](https://github.com/tiangolo/sqlmodel/pull/207) by [@jrycw](https://github.com/jrycw).
|
||||
* 📝 Update outdated link in `docs/db-to-code.md`. PR [#649](https://github.com/tiangolo/sqlmodel/pull/649) by [@MatveyF](https://github.com/MatveyF).
|
||||
* ✏️ Fix typos found with codespell. PR [#520](https://github.com/tiangolo/sqlmodel/pull/520) by [@kianmeng](https://github.com/kianmeng).
|
||||
* 📝 Fix typos (duplication) in main page. PR [#631](https://github.com/tiangolo/sqlmodel/pull/631) by [@Mr-DRP](https://github.com/Mr-DRP).
|
||||
* 📝 Update release notes, add second author to PR. PR [#429](https://github.com/tiangolo/sqlmodel/pull/429) by [@br-follow](https://github.com/br-follow).
|
||||
* 📝 Update instructions about how to make a foreign key required in `docs/tutorial/relationship-attributes/define-relationships-attributes.md`. PR [#474](https://github.com/tiangolo/sqlmodel/pull/474) by [@jalvaradosegura](https://github.com/jalvaradosegura).
|
||||
* 📝 Update help SQLModel docs. PR [#548](https://github.com/tiangolo/sqlmodel/pull/548) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ✏️ Fix typo in internal function name `get_sqlachemy_type()`. PR [#496](https://github.com/tiangolo/sqlmodel/pull/496) by [@cmarqu](https://github.com/cmarqu).
|
||||
* ✏️ Fix typo in docs. PR [#446](https://github.com/tiangolo/sqlmodel/pull/446) by [@davidbrochart](https://github.com/davidbrochart).
|
||||
* ✏️ Fix typo in `docs/tutorial/create-db-and-table.md`. PR [#477](https://github.com/tiangolo/sqlmodel/pull/477) by [@FluffyDietEngine](https://github.com/FluffyDietEngine).
|
||||
* ✏️ Fix small typos in docs. PR [#481](https://github.com/tiangolo/sqlmodel/pull/481) by [@micuffaro](https://github.com/micuffaro).
|
||||
|
||||
### Internal
|
||||
|
||||
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#672](https://github.com/tiangolo/sqlmodel/pull/672) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
|
||||
* ⬆ Bump dawidd6/action-download-artifact from 2.24.2 to 2.28.0. PR [#660](https://github.com/tiangolo/sqlmodel/pull/660) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ✅ Refactor OpenAPI FastAPI tests to simplify updating them later, this moves things around without changes. PR [#671](https://github.com/tiangolo/sqlmodel/pull/671) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ⬆ Bump actions/checkout from 3 to 4. PR [#670](https://github.com/tiangolo/sqlmodel/pull/670) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* 🔧 Update mypy config, use `strict = true` instead of manual configs. PR [#428](https://github.com/tiangolo/sqlmodel/pull/428) by [@michaeloliverx](https://github.com/michaeloliverx).
|
||||
* ⬆️ Upgrade MkDocs Material. PR [#668](https://github.com/tiangolo/sqlmodel/pull/668) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 🎨 Update docs format and references with pre-commit and Ruff. PR [#667](https://github.com/tiangolo/sqlmodel/pull/667) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 🎨 Run pre-commit on all files and autoformat. PR [#666](https://github.com/tiangolo/sqlmodel/pull/666) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 Move to Ruff and add pre-commit. PR [#661](https://github.com/tiangolo/sqlmodel/pull/661) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 🛠️ Add `CITATION.cff` file for academic citations. PR [#13](https://github.com/tiangolo/sqlmodel/pull/13) by [@sugatoray](https://github.com/sugatoray).
|
||||
* 👷 Update docs deployments to Cloudflare. PR [#630](https://github.com/tiangolo/sqlmodel/pull/630) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷♂️ Upgrade CI for docs. PR [#628](https://github.com/tiangolo/sqlmodel/pull/628) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 Update CI debug mode with Tmate. PR [#629](https://github.com/tiangolo/sqlmodel/pull/629) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 Update latest changes token. PR [#616](https://github.com/tiangolo/sqlmodel/pull/616) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ⬆️ Upgrade analytics. PR [#558](https://github.com/tiangolo/sqlmodel/pull/558) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 🔧 Update new issue chooser to point to GitHub Discussions. PR [#546](https://github.com/tiangolo/sqlmodel/pull/546) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 🔧 Add template for GitHub Discussion questions and update issues template. PR [#544](https://github.com/tiangolo/sqlmodel/pull/544) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 Refactor CI artifact upload/download for docs previews. PR [#514](https://github.com/tiangolo/sqlmodel/pull/514) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ⬆ Bump actions/cache from 2 to 3. PR [#497](https://github.com/tiangolo/sqlmodel/pull/497) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump dawidd6/action-download-artifact from 2.24.0 to 2.24.2. PR [#493](https://github.com/tiangolo/sqlmodel/pull/493) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* 🔧 Update Smokeshow coverage threshold. PR [#487](https://github.com/tiangolo/sqlmodel/pull/487) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 Move from Codecov to Smokeshow. PR [#486](https://github.com/tiangolo/sqlmodel/pull/486) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ⬆ Bump actions/setup-python from 2 to 4. PR [#411](https://github.com/tiangolo/sqlmodel/pull/411) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Update black requirement from ^21.5-beta.1 to ^22.10.0. PR [#460](https://github.com/tiangolo/sqlmodel/pull/460) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ➕ Add extra dev dependencies for MkDocs Material. PR [#485](https://github.com/tiangolo/sqlmodel/pull/485) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ⬆ Update mypy requirement from 0.930 to 0.971. PR [#380](https://github.com/tiangolo/sqlmodel/pull/380) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Update coverage requirement from ^5.5 to ^6.2. PR [#171](https://github.com/tiangolo/sqlmodel/pull/171) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump codecov/codecov-action from 2 to 3. PR [#415](https://github.com/tiangolo/sqlmodel/pull/415) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump actions/upload-artifact from 2 to 3. PR [#412](https://github.com/tiangolo/sqlmodel/pull/412) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Update flake8 requirement from ^3.9.2 to ^5.0.4. PR [#396](https://github.com/tiangolo/sqlmodel/pull/396) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Update pytest requirement from ^6.2.4 to ^7.0.1. PR [#242](https://github.com/tiangolo/sqlmodel/pull/242) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump actions/checkout from 2 to 3.1.0. PR [#458](https://github.com/tiangolo/sqlmodel/pull/458) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump dawidd6/action-download-artifact from 2.9.0 to 2.24.0. PR [#470](https://github.com/tiangolo/sqlmodel/pull/470) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* 👷 Update Dependabot config. PR [#484](https://github.com/tiangolo/sqlmodel/pull/484) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.0.8
|
||||
|
||||
### Fixes
|
||||
|
||||
* 🐛 Fix auto detecting and setting `nullable`, allowing overrides in field. PR [#423](https://github.com/tiangolo/sqlmodel/pull/423) by [@JonasKs](https://github.com/JonasKs) and [@br-follow](https://github.com/br-follow).
|
||||
* ♻️ Update `expresion.py`, sync from Jinja2 template, implement `inherit_cache` to solve errors like: `SAWarning: Class SelectOfScalar will not make use of SQL compilation caching`. PR [#422](https://github.com/tiangolo/sqlmodel/pull/422) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Docs
|
||||
|
||||
* 📝 Adjust and clarify docs for `docs/tutorial/create-db-and-table.md`. PR [#426](https://github.com/tiangolo/sqlmodel/pull/426) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ✏ Fix typo in `docs/tutorial/connect/remove-data-connections.md`. PR [#421](https://github.com/tiangolo/sqlmodel/pull/421) by [@VerdantFox](https://github.com/VerdantFox).
|
||||
|
||||
## 0.0.7
|
||||
|
||||
|
||||
@@ -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,14 +28,27 @@ But the same `id` field actually **can be `None`** in the Python code, so we dec
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Next, I'll show you a bit more about the synchronization of data between the database and the Python code.
|
||||
|
||||
@@ -31,22 +58,49 @@ 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 👆
|
||||
|
||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:23-26]!}
|
||||
|
||||
# Code below ommitted 👇
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
### How `Optional` Helps
|
||||
|
||||
@@ -74,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 👆
|
||||
|
||||
@@ -82,14 +150,27 @@ We can confirm that by printing our heroes before adding them to the database:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
That will output:
|
||||
|
||||
@@ -120,22 +201,49 @@ 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 👆
|
||||
|
||||
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:23-41]!}
|
||||
|
||||
# Code below ommitted 👇
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This will, again, output the `id`s of the objects as `None`:
|
||||
|
||||
@@ -160,6 +268,20 @@ 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:
|
||||
|
||||
//// 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 👆
|
||||
|
||||
@@ -168,14 +290,27 @@ Then we can `commit` the changes in the session, and print again:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
And now, something unexpected happens, look at the output, it seems as if the `Hero` instance objects had no data at all:
|
||||
|
||||
@@ -233,22 +368,49 @@ 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):
|
||||
|
||||
//// 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]!}
|
||||
|
||||
# Code below ommitted 👇
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Now we are actually accessing the attributes, because instead of printing the whole object `hero_1`:
|
||||
|
||||
@@ -271,7 +433,7 @@ Let's see how it works:
|
||||
```console
|
||||
$ python app.py
|
||||
|
||||
// Output above ommitted 👆
|
||||
// Output above omitted 👆
|
||||
|
||||
// After committing, the objects are expired and have no values
|
||||
After committing the session
|
||||
@@ -317,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 🤓
|
||||
|
||||
```
|
||||
|
||||
</div>
|
||||
@@ -330,22 +491,49 @@ But what if you want to **explicitly refresh** the data?
|
||||
|
||||
You can do that too with `session.refresh(object)`:
|
||||
|
||||
//// 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]!}
|
||||
|
||||
# Code below ommitted 👇
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
When Python executes this code:
|
||||
|
||||
@@ -362,7 +550,7 @@ Here's how the output would look like:
|
||||
```console
|
||||
$ python app.py
|
||||
|
||||
// Output above ommitted 👆
|
||||
// Output above omitted 👆
|
||||
|
||||
// The first refresh
|
||||
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
|
||||
@@ -403,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 👆
|
||||
|
||||
@@ -411,14 +613,27 @@ There are no surprises here, it still works:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
And the output shows again the same data:
|
||||
|
||||
@@ -427,7 +642,7 @@ And the output shows again the same data:
|
||||
```console
|
||||
$ python app.py
|
||||
|
||||
// Output above ommitted 👆
|
||||
// Output above omitted 👆
|
||||
|
||||
// By finishing the with block, the Session is closed, including a rollback of any pending transaction that could have been there and was not committed
|
||||
INFO Engine ROLLBACK
|
||||
@@ -445,17 +660,34 @@ Hero 3: age=48 id=3 name='Rusty-Man' secret_name='Tommy Sharp'
|
||||
|
||||
Now let's review all this code once again.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Each one of the numbered bubbles shows what each line will print in the output.
|
||||
|
||||
And as we created the **engine** with `echo=True`, we can see the SQL statements being executed at each step.
|
||||
|
||||
```{ .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:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -138,7 +138,7 @@ So, the output would be:
|
||||
$ python -m project.app
|
||||
|
||||
Created hero: id=1 secret_name='Dive Wilson' team_id=1 name='Deadpond' age=None
|
||||
Hero's team: name='Z-Force' headquarters='Sister Margaret’s Bar' id=1
|
||||
Hero's team: name='Z-Force' headquarters='Sister Margaret's Bar' id=1
|
||||
```
|
||||
|
||||
</div>
|
||||
@@ -149,11 +149,14 @@ Let's say that for some reason you hate the idea of having all the database mode
|
||||
|
||||
You can also do it. 😎 There's a couple of things to keep in mind. 🤓
|
||||
|
||||
!!! warning
|
||||
/// warning
|
||||
|
||||
This is a bit more advanced.
|
||||
|
||||
If the solution above already worked for you, that might be enough for you, and you can continue in the next chapter. 🤓
|
||||
|
||||
///
|
||||
|
||||
Let's assume that now the file structure is:
|
||||
|
||||
```
|
||||
@@ -168,7 +171,7 @@ Let's assume that now the file structure is:
|
||||
|
||||
### Circular Imports and Type Annotations
|
||||
|
||||
The problem with circular imports is that Python can't resolve them at <abbr title="While it is executing the program, as oposed to the code as just text in a file stored on disk.">*runtime*</abbr>.
|
||||
The problem with circular imports is that Python can't resolve them at <abbr title="While it is executing the program, as opposed to the code as just text in a file stored on disk.">*runtime*</abbr>.
|
||||
|
||||
But when using Python **type annotations** it's very common to need to declare the type of some variables with classes imported from other files.
|
||||
|
||||
@@ -240,7 +243,7 @@ And running that achieves the same result as before:
|
||||
$ python -m project.app
|
||||
|
||||
Created hero: id=1 age=None name='Deadpond' secret_name='Dive Wilson' team_id=1
|
||||
Hero's team: id=1 name='Z-Force' headquarters='Sister Margaret’s Bar'
|
||||
Hero's team: id=1 name='Z-Force' headquarters='Sister Margaret's Bar'
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ The `team` table will look like this:
|
||||
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -37,19 +37,33 @@ Each row in the table `hero` will point to a row in the table `team`:
|
||||
|
||||
<img alt="table relationships" src="/img/tutorial/relationships/select/relationships2.svg">
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
We will later update **Spider-Boy** to add him to the **Preventers** team too, but not yet.
|
||||
|
||||
///
|
||||
|
||||
We will continue with the code in the previous example and we will add more things to it.
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Make sure you remove the `database.db` file before running the examples to get the same results.
|
||||
|
||||
@@ -61,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 👆
|
||||
|
||||
@@ -69,14 +97,27 @@ Let's start by creating two teams:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This would hopefully look already familiar.
|
||||
|
||||
@@ -92,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 👆
|
||||
|
||||
@@ -100,14 +155,27 @@ Let's not forget to add this function `create_heroes()` to the `main()` function
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Run it
|
||||
|
||||
@@ -126,7 +194,7 @@ INFO Engine BEGIN (implicit)
|
||||
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
|
||||
INFO Engine [generated in 0.00050s] ('Preventers', 'Sharp Tower')
|
||||
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
|
||||
INFO Engine [cached since 0.002324s ago] ('Z-Force', 'Sister Margaret’s Bar')
|
||||
INFO Engine [cached since 0.002324s ago] ('Z-Force', 'Sister Margaret's Bar')
|
||||
INFO Engine COMMIT
|
||||
```
|
||||
|
||||
@@ -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,14 +230,27 @@ As the `Hero` class model now has a field (column, attribute) `team_id`, we can
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
We haven't committed this hero to the database yet, but there are already a couple of things to pay **attention** to.
|
||||
|
||||
@@ -179,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 👆
|
||||
|
||||
@@ -187,14 +296,27 @@ Let's now create two more heroes:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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:
|
||||
|
||||
@@ -225,6 +347,20 @@ INFO Engine COMMIT
|
||||
|
||||
Now let's refresh and print those new heroes to see their new ID pointing to their teams:
|
||||
|
||||
//// 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 👆
|
||||
|
||||
@@ -233,15 +369,27 @@ Now let's refresh and print those new heroes to see their new ID pointing to the
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
If we execute that in the command line, it will output:
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ The team table will look like this:
|
||||
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -57,20 +57,45 @@ 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This is very similar to what we have been doing with the `Hero` model.
|
||||
|
||||
@@ -89,24 +114,49 @@ 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Most of that should look familiar:
|
||||
|
||||
The column will be named `team_id`. It will be an integer, and it could be `NULL` in the database (or `None` in Python), becase there could be some heroes that don't belong to any team.
|
||||
The column will be named `team_id`. It will be an integer, and it could be `NULL` in the database (or `None` in Python), because there could be some heroes that don't belong to any team.
|
||||
|
||||
We add a default of `None` to the `Field()` so we don't have to explicitly pass `team_id=None` when creating a hero.
|
||||
|
||||
@@ -126,50 +176,106 @@ This is the name of the **table** in the database, so it is `"team"`, not the na
|
||||
|
||||
If you had a custom table name, you would use that custom table name.
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
You can learn about setting a custom table name for a model in the Advanced User Guide.
|
||||
|
||||
///
|
||||
|
||||
### Create the Tables
|
||||
|
||||
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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Run the Code
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Before running the code, make sure you delete the file `database.db` to make sure you start from scratch.
|
||||
|
||||
///
|
||||
|
||||
If we run the code we have up to now, it will go and create the database file `database.db` and the tables in it we just defined, `team` and `hero`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -6,7 +6,10 @@ But the main advantage and feature of SQL databases is being able to handle rela
|
||||
|
||||
Let's see how to use **SQLModel** to manage connected data in the next chapters. 🤝
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
We will extend this further in the next group of chapters making it even more convenient to work with in Python code, using **relationship attributes**.
|
||||
|
||||
But you should start in this group of chapters first. 🤓
|
||||
|
||||
///
|
||||
|
||||
@@ -12,7 +12,7 @@ The `team` table has this data:
|
||||
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -35,14 +35,25 @@ And the `hero` table has this data:
|
||||
|
||||
We will continue with the code in the previous example and we will add more things to it.
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## `SELECT` Connected Data with SQL
|
||||
|
||||
@@ -62,9 +73,12 @@ FROM hero, team
|
||||
WHERE hero.team_id = team.id
|
||||
```
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
Because we have two columns called `name`, one for `hero` and one for `team`, we can specify them with the prefix of the table name and the dot to make it explicit what we refer to.
|
||||
|
||||
///
|
||||
|
||||
Notice that now in the `WHERE` part we are not comparing one column with a literal value (like `hero.name = "Deadpond"`), but we are comparing two columns.
|
||||
|
||||
It means, more or less:
|
||||
@@ -99,7 +113,8 @@ You can go ahead and try it in **DB Browser for SQLite**:
|
||||
|
||||
<img class="shadow" src="/img/tutorial/relationships/select/image01.png">
|
||||
|
||||
!!! note
|
||||
/// note
|
||||
|
||||
Wait, what about Spider-Boy? 😱
|
||||
|
||||
He doesn't have a team, so his `team_id` is `NULL` in the database. And this SQL is comparing that `NULL` from the `team_id` with all the `id` fields in the rows in the `team` table.
|
||||
@@ -108,6 +123,8 @@ You can go ahead and try it in **DB Browser for SQLite**:
|
||||
|
||||
But we'll see how to fix that later with a `LEFT JOIN`.
|
||||
|
||||
///
|
||||
|
||||
## Select Related Data with **SQLModel**
|
||||
|
||||
Now let's use SQLModel to do the same select.
|
||||
@@ -118,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 👆
|
||||
|
||||
@@ -126,14 +157,27 @@ So, we can pass the `Hero` and `Team` model classes. And we can also use both th
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Notice that in the comparison with `==` we are using the class attributes for both `Hero.team_id` and `Team.id`.
|
||||
|
||||
@@ -143,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 👆
|
||||
|
||||
@@ -151,28 +209,58 @@ And as we used `select` with two models, we will receive tuples of instances of
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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`.
|
||||
|
||||
And in this `for` loop we assign them to the variable `hero` and the variable `team`.
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
There was a lot of research, design, and work behind **SQLModel** to make this provide the best possible developer experience.
|
||||
|
||||
And you should get autocompletion and inline errors in your editor for both `hero` and `team`. 🎉
|
||||
|
||||
///
|
||||
|
||||
## Add It to Main
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -181,14 +269,27 @@ As always, we must remember to add this new `select_heroes()` function to the `m
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
|
||||
## Run the Program
|
||||
@@ -209,7 +310,7 @@ WHERE hero.team_id = team.id
|
||||
2021-08-09 08:55:50,682 INFO sqlalchemy.engine.Engine [no key 0.00015s] ()
|
||||
|
||||
// Print the first hero and team
|
||||
Hero: id=1 secret_name='Dive Wilson' team_id=2 name='Deadpond' age=None Team: headquarters='Sister Margaret’s Bar' id=2 name='Z-Force'
|
||||
Hero: id=1 secret_name='Dive Wilson' team_id=2 name='Deadpond' age=None Team: headquarters='Sister Margaret's Bar' id=2 name='Z-Force'
|
||||
|
||||
// Print the second hero and team
|
||||
Hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48 Team: headquarters='Sharp Tower' id=1 name='Preventers'
|
||||
@@ -281,17 +382,34 @@ Also in **DB Browser for SQLite**:
|
||||
|
||||
<img class="shadow" src="/img/tutorial/relationships/select/image02.png">
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Why bother with all this if the result is the same?
|
||||
|
||||
This `JOIN` will be useful in a bit to be able to also get Spider-Boy, even if he doesn't have a team.
|
||||
|
||||
///
|
||||
|
||||
## Join Tables in **SQLModel**
|
||||
|
||||
The same way there's a `.where()` available when using `select()`, there's also a `.join()`.
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -300,14 +418,27 @@ And in SQLModel (actually SQLAlchemy), when using the `.join()`, because we alre
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Also notice that we are still including `Team` in the `select(Hero, Team)`, because we still want to access that data.
|
||||
|
||||
@@ -328,7 +459,7 @@ FROM hero JOIN team ON team.id = hero.team_id
|
||||
INFO Engine [no key 0.00032s] ()
|
||||
|
||||
// Print the first hero and team
|
||||
Hero: id=1 secret_name='Dive Wilson' team_id=2 name='Deadpond' age=None Team: headquarters='Sister Margaret’s Bar' id=2 name='Z-Force'
|
||||
Hero: id=1 secret_name='Dive Wilson' team_id=2 name='Deadpond' age=None Team: headquarters='Sister Margaret's Bar' id=2 name='Z-Force'
|
||||
|
||||
// Print the second hero and team
|
||||
Hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48 Team: headquarters='Sharp Tower' id=1 name='Preventers'
|
||||
@@ -420,9 +551,12 @@ And that would return the following result, including **Spider-Boy** 🎉:
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
The only difference between this query and the previous is that extra `LEFT OUTER`.
|
||||
|
||||
///
|
||||
|
||||
And here's another of the SQL variations, you could write `LEFT OUTER JOIN` or just `LEFT JOIN`, it means the same.
|
||||
|
||||
## Join Tables in **SQLModel** with `LEFT OUTER`
|
||||
@@ -431,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 👆
|
||||
|
||||
@@ -439,14 +587,27 @@ Now let's replicate the same query in **SQLModel**.
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
And if we run it, it will output:
|
||||
|
||||
@@ -464,7 +625,7 @@ FROM hero LEFT OUTER JOIN team ON team.id = hero.team_id
|
||||
INFO Engine [no key 0.00051s] ()
|
||||
|
||||
// Print the first hero and team
|
||||
Hero: id=1 secret_name='Dive Wilson' team_id=2 name='Deadpond' age=None Team: headquarters='Sister Margaret’s Bar' id=2 name='Z-Force'
|
||||
Hero: id=1 secret_name='Dive Wilson' team_id=2 name='Deadpond' age=None Team: headquarters='Sister Margaret's Bar' id=2 name='Z-Force'
|
||||
// Print the second hero and team
|
||||
Hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48 Team: headquarters='Sharp Tower' id=1 name='Preventers'
|
||||
// Print the third hero and team, we included Spider-Boy 🎉
|
||||
@@ -493,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 👆
|
||||
|
||||
@@ -501,14 +676,27 @@ We could even add some additional `.where()` after `.join()` to filter the data
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Here we are **filtering** with `.where()` to get only the heroes that belong to the **Preventers** team.
|
||||
|
||||
@@ -539,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 👆
|
||||
|
||||
@@ -547,14 +749,27 @@ By putting the `Team` in `select()` we tell **SQLModel** and the database that w
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
And if we run that, it will output:
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ We currently have a `team` table:
|
||||
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -35,23 +35,52 @@ Let's see how to **remove** connections between rows in tables.
|
||||
|
||||
We will continue with the code from the previous chapter.
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Break a Connection
|
||||
|
||||
We don't really have to delete anyting to break a connection. We can just assign `None` to the foreign key, in this case, to the `team_id`.
|
||||
We don't really have to delete anything to break a connection. We can just assign `None` to the foreign key, in this case, to the `team_id`.
|
||||
|
||||
Let's say **Spider-Boy** is tired of the lack of friendly neighbors and wants to get out of the **Preventers**.
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -64,14 +93,27 @@ We can simply set the `team_id` to `None`, and now it doesn't have a connection
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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()`.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ At this point we have a `team` table:
|
||||
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -37,14 +37,25 @@ Now we'll see how to **update** those connections between rows tables.
|
||||
|
||||
We will continue with the code we used to create some heroes, and we'll update them.
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Assign a Team to a Hero
|
||||
|
||||
@@ -52,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 👆
|
||||
|
||||
@@ -64,14 +93,27 @@ Doing it is just like updating any other field:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
We can simply **assign** a value to that field attribute `team_id`, then `add()` the hero to the session, and then `commit()`.
|
||||
|
||||
|
||||
@@ -42,9 +42,12 @@ Click the button <kbd>New Database</kbd>.
|
||||
|
||||
A dialog should show up. Go to the [project directory you created](./index.md#create-a-project){.internal-link target=_blank} and save the file with a name of `database.db`.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
It's common to save SQLite database files with an extension of `.db`. Sometimes also `.sqlite`.
|
||||
|
||||
///
|
||||
|
||||
## Create a Table
|
||||
|
||||
After doing that, it might prompt you to create a new table right away.
|
||||
@@ -164,6 +167,6 @@ Of course, you can also go and take a full SQL course or read a book about SQL,
|
||||
|
||||
We saw how to interact with SQLite databases in files using **DB Browser for SQLite** in a visual user interface.
|
||||
|
||||
We also saw how to use it to write some SQL directly to the SQLite database. This will be useful to verify the data in the database is looking correclty, to debug, etc.
|
||||
We also saw how to use it to write some SQL directly to the SQLite database. This will be useful to verify the data in the database is looking correctly, to debug, etc.
|
||||
|
||||
In the next chapters we will start using **SQLModel** to interact with the database, and we will continue to use **DB Browser for SQLite** at the same time to look at the database underneath. 🔍
|
||||
|
||||
@@ -33,35 +33,66 @@ The first thing we need to do is create a class to represent the data in the tab
|
||||
|
||||
A class like this that represents some data is commonly called a **model**.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
That's why this package is called `SQLModel`. Because it's mainly used to create **SQL Models**.
|
||||
|
||||
///
|
||||
|
||||
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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This class `Hero` **represents the table** for our heroes. And each instance we create later will **represent a row** in the table.
|
||||
|
||||
We use the config `table=True` to tell **SQLModel** that this is a **table model**, it represents a table.
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
It's also possible to have models without `table=True`, those would be only **data models**, without a table in the database, they would not be **table models**.
|
||||
|
||||
Those **data models** will be **very useful later**, but for now, we'll just keep adding the `table=True` configuration.
|
||||
|
||||
///
|
||||
|
||||
## Define the Fields, Columns
|
||||
|
||||
The next step is to define the fields or columns of the class by using standard Python type annotations.
|
||||
@@ -70,20 +101,45 @@ 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Let's now see with more detail these field/column declarations.
|
||||
|
||||
@@ -97,35 +153,66 @@ 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
/// tip
|
||||
|
||||
!!! tip
|
||||
We also define `id` with `Optional`. But we will talk about `id` below.
|
||||
|
||||
///
|
||||
|
||||
This way, we tell **SQLModel** that `age` is not required when validating data and that it has a default value of `None`.
|
||||
|
||||
And we also tell it that, in the SQL database, the default value of `age` is `NULL` (the SQL equivalent to Python's `None`).
|
||||
|
||||
So, this column is "nullable" (can be set to `NULL`).
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
In terms of **Pydantic**, `age` is an **optional field**.
|
||||
|
||||
In terms of **SQLAlchemy**, `age` is a **nullable column**.
|
||||
|
||||
///
|
||||
|
||||
### Primary Key `id`
|
||||
|
||||
Now let's review the `id` field. This is the <abbr title="That unique identifier of each row in a specific table.">**primary key**</abbr> of the table.
|
||||
@@ -134,20 +221,45 @@ 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
That way, we tell **SQLModel** that this `id` field/column is the primary key of the table.
|
||||
|
||||
@@ -190,28 +302,56 @@ 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:
|
||||
|
||||
```Python hl_lines="3 16"
|
||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-16]!}
|
||||
//// 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 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="3 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
You should normally have a single **engine** object for your whole application and re-use it everywhere.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
There's another related thing called a **Session** that normally should *not* be a single object per application.
|
||||
|
||||
But we will talk about it later.
|
||||
|
||||
///
|
||||
|
||||
### Engine Database URL
|
||||
|
||||
Each supported database has it's own URL type. For example, for **SQLite** it is `sqlite:///` followed by the file path. For example:
|
||||
@@ -220,24 +360,49 @@ Each supported database has it's own URL type. For example, for **SQLite** it is
|
||||
* `sqlite:///databases/local/application.db`
|
||||
* `sqlite:///db.sqlite`
|
||||
|
||||
For SQLAlchemy, there's also a special one, which is a database all *in memory*, this means that it is deleted after the program terminates, and it's also very fast:
|
||||
SQLite supports a special database that lives all *in memory*. Hence, it's very fast, but be careful, the database gets deleted after the program terminates. You can specify this in-memory database by using just two slash characters (`//`) and no file name:
|
||||
|
||||
* `sqlite://`
|
||||
|
||||
```Python hl_lines="13-14 16"
|
||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-19]!}
|
||||
//// 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 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="13-14 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
You can read a lot more about all the databases supported by **SQLAlchemy** (and that way supported by **SQLModel**) in the <a href="https://docs.sqlalchemy.org/en/14/core/engines.html" class="external-link" target="_blank">SQLAlchemy documentation</a>.
|
||||
|
||||
@@ -249,20 +414,45 @@ It will make the engine print all the SQL statements it executes, which can help
|
||||
|
||||
It is particularly useful for **learning** and **debugging**:
|
||||
|
||||
```Python hl_lines="16"
|
||||
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-16]!}
|
||||
//// 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 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
But in production, you would probably want to remove `echo=True`:
|
||||
|
||||
@@ -272,9 +462,12 @@ engine = create_engine(sqlite_url)
|
||||
|
||||
### Engine Technical Details
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
If you didn't know about SQLAlchemy before and are just learning **SQLModel**, you can probably skip this section, scroll below.
|
||||
|
||||
///
|
||||
|
||||
You can read a lot more about the engine in the <a href="https://docs.sqlalchemy.org/en/14/tutorial/engine.html" class="external-link" target="_blank">SQLAlchemy documentation</a>.
|
||||
|
||||
**SQLModel** defines it's own `create_engine()` function. It is the same as SQLAlchemy's `create_engine()`, but with the difference that it defaults to use `future=True` (which means that it uses the style of the latest SQLAlchemy, 1.4, and the future 2.0).
|
||||
@@ -285,17 +478,32 @@ 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
|
||||
////
|
||||
|
||||
/// tip
|
||||
|
||||
Creating the engine doesn't create the `database.db` file.
|
||||
|
||||
But once we run `SQLModel.metadata.create_all(engine)`, it creates the `database.db` file **and** creates the `hero` table in that database.
|
||||
|
||||
Both things are done in this single step.
|
||||
|
||||
///
|
||||
|
||||
Let's unwrap that:
|
||||
|
||||
```Python
|
||||
@@ -395,18 +603,32 @@ Let's run the program to see it all working.
|
||||
|
||||
Put the code it in a file `app.py` if you haven't already.
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
/// tip
|
||||
|
||||
!!! tip
|
||||
Remember to [activate the virtual environment](./index.md#create-a-python-virtual-environment){.internal-link target=_blank} before running it.
|
||||
|
||||
///
|
||||
|
||||
Now run the program with Python:
|
||||
|
||||
<div class="termy">
|
||||
@@ -415,7 +637,7 @@ Now run the program with Python:
|
||||
// We set echo=True, so this will show the SQL code
|
||||
$ python app.py
|
||||
|
||||
// First, some boilerplate SQL that we are not that intereted in
|
||||
// First, some boilerplate SQL that we are not that interested in
|
||||
|
||||
INFO Engine BEGIN (implicit)
|
||||
INFO Engine PRAGMA main.table_info("hero")
|
||||
@@ -442,7 +664,8 @@ INFO Engine COMMIT
|
||||
|
||||
</div>
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
I simplified the output above a bit to make it easier to read.
|
||||
|
||||
But in reality, instead of showing:
|
||||
@@ -457,6 +680,8 @@ INFO Engine COMMIT
|
||||
2021-07-25 21:37:39,175 INFO sqlalchemy.engine.Engine BEGIN (implicit)
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
### `TEXT` or `VARCHAR`
|
||||
|
||||
In the example in the previous chapter we created the table using `TEXT` for some columns.
|
||||
@@ -479,9 +704,12 @@ Additional to the difference between those two data types, some databases like M
|
||||
|
||||
To make it easier to start using **SQLModel** right away independent of the database you use (even with MySQL), and without any extra configurations, by default, `str` fields are interpreted as `VARCHAR` in most databases and `VARCHAR(255)` in MySQL, this way you know the same class will be compatible with the most popular databases without extra effort.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
You will learn how to change the maximum length of string columns later in the Advanced Tutorial - User Guide.
|
||||
|
||||
///
|
||||
|
||||
### Verify the Database
|
||||
|
||||
Now, open the database with **DB Browser for SQLite**, you will see that the program created the table `hero` just as before. 🎉
|
||||
@@ -498,47 +726,90 @@ In this example it's just the `SQLModel.metadata.create_all(engine)`.
|
||||
|
||||
Let's put it in a function `create_db_and_tables()`:
|
||||
|
||||
```Python hl_lines="22-23"
|
||||
//// 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
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 don't want that to happen like that, only when we **intend** it to happen, that's why we put it in a function.
|
||||
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.
|
||||
|
||||
We don't want that to happen like that, only when we **intend** it to happen, that's why we put it in a function, because we can make sure that the tables are created only when we call that function, and not when this module is imported somewhere else.
|
||||
|
||||
Now we would be able to, for example, import the `Hero` class in some other file without having those **side effects**.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
😅 **Spoiler alert**: The function is called `create_db_and_tables()` because we will have more **tables** in the future with other classes apart from `Hero`. 🚀
|
||||
|
||||
///
|
||||
|
||||
### Create Data as a Script
|
||||
|
||||
We prevented the side effects when importing something from your `app.py` file.
|
||||
|
||||
But we still want it to **create the database and table** when we call it with Python directly as an independent script from the terminal, just as as above.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Think of the word **script** and **program** as interchangeable.
|
||||
|
||||
The word **script** often implies that the code could be run independently and easily. Or in some cases it refers to a relatively simple program.
|
||||
|
||||
///
|
||||
|
||||
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:
|
||||
@@ -559,11 +830,14 @@ $ python app.py
|
||||
from app import Hero
|
||||
```
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
That `if` block using `if __name__ == "__main__":` is sometimes called the "**main block**".
|
||||
|
||||
The official name (in the <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">Python docs</a>) is "**Top-level script environment**".
|
||||
|
||||
///
|
||||
|
||||
#### More details
|
||||
|
||||
Let's say your file is named `myapp.py`.
|
||||
@@ -614,9 +888,12 @@ if __name__ == "__main__":
|
||||
|
||||
...will **not** be executed.
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
For more information, check <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">the official Python docs</a>.
|
||||
|
||||
///
|
||||
|
||||
## Last Review
|
||||
|
||||
After those changes, you could run it again, and it would generate the same output as before.
|
||||
@@ -625,15 +902,32 @@ 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
|
||||
////
|
||||
|
||||
/// tip
|
||||
|
||||
Review what each line does by clicking each number bubble in the code. 👆
|
||||
|
||||
///
|
||||
|
||||
## Recap
|
||||
|
||||
We learnt how to use **SQLModel** to define how a table in the database should look like, and we created a database and a table using **SQLModel**.
|
||||
|
||||
@@ -6,14 +6,25 @@ Now let's delete some data using **SQLModel**.
|
||||
|
||||
As before, we'll continue from where we left off with the previous code.
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Remember to remove the `database.db` file before running the examples to get the same results.
|
||||
|
||||
@@ -63,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 👆
|
||||
|
||||
@@ -71,31 +96,69 @@ We'll start by selecting the hero `"Spider-Youngster"` that we updated in the pr
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
That will print the same existing hero **Spider-Youngster**:
|
||||
|
||||
@@ -123,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 👆
|
||||
|
||||
@@ -131,14 +208,27 @@ Now, very similar to how we used `session.add()` to add or update new heroes, we
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Commit the Session
|
||||
|
||||
@@ -146,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 👆
|
||||
|
||||
@@ -154,14 +258,27 @@ This will save all the changes stored in the **session**, like the deleted hero:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
The same as we have seen before, `.commit()` will also save anything else that was added to the session. Including updates, or created heroes.
|
||||
|
||||
@@ -196,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 👆
|
||||
|
||||
@@ -204,14 +335,27 @@ Because of that, the object still contains its attributes with the data in it, s
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This will output:
|
||||
|
||||
@@ -234,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 👆
|
||||
|
||||
@@ -242,14 +400,27 @@ To confirm if it was deleted, now let's query the database again, with the same
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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.
|
||||
|
||||
@@ -286,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 👆
|
||||
|
||||
@@ -294,14 +479,27 @@ We'll do it by checking that the "first" item in the `results` is `None`:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This will output:
|
||||
|
||||
@@ -327,15 +525,32 @@ 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
|
||||
////
|
||||
|
||||
/// tip
|
||||
|
||||
Check out the number bubbles to see what is done by each line of code.
|
||||
|
||||
///
|
||||
|
||||
## Recap
|
||||
|
||||
To delete rows with **SQLModel** you just have to `.delete()` them with the **session**, and then, as always, `.commit()` the session to save the changes to the database. 🔥
|
||||
|
||||
@@ -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,14 +46,35 @@ And if we actually find a hero, we just delete it with the **session**.
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
After deleting it successfully, we just return a response of:
|
||||
|
||||
|
||||
@@ -8,9 +8,12 @@ So, we probably want to limit it.
|
||||
|
||||
Let's use the same **offset** and **limit** we learned about in the previous tutorial chapters for the API.
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
In many cases, this is also called **pagination**.
|
||||
|
||||
///
|
||||
|
||||
## Add a Limit and Offset to the Query Parameters
|
||||
|
||||
Let's add `limit` and `offset` to the query parameters.
|
||||
@@ -19,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]!}
|
||||
|
||||
@@ -29,30 +62,54 @@ And by default, we will return a maximum of `100` heroes, so `limit` will have a
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
We want to allow clients to set different `offset` and `limit` values.
|
||||
|
||||
But we don't want them to be able to set a `limit` of something like `9999`, that's over `9000`! 😱
|
||||
|
||||
So, to prevent it, we add additional validation to the `limit` query parameter, declaring that it has to be **l**ess **t**han or **e**qual to `100` with `lte=100`.
|
||||
So, to prevent it, we add additional validation to the `limit` query parameter, declaring that it has to be **l**ess than or **e**qual to `100` with `le=100`.
|
||||
|
||||
This way, a client can decide to take fewer heroes if they want, but not more.
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
If you need to refresh how query parameters and their validation work, check out the docs in FastAPI:
|
||||
|
||||
* <a href="https://fastapi.tiangolo.com/tutorial/query-params/" class="external-link" target="_blank">Query Parameters</a>
|
||||
* <a href="https://fastapi.tiangolo.com/tutorial/query-params-str-validations/" class="external-link" target="_blank">Query Parameters and String Validations</a>
|
||||
* <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/" class="external-link" target="_blank">Path Parameters and Numeric Validations</a>
|
||||
|
||||
///
|
||||
|
||||
## Check the Docs UI
|
||||
|
||||
Now we can see that the docs UI shows the new parameters to control **limit** and **offset** of our data.
|
||||
|
||||
@@ -53,11 +53,11 @@ Here's the weird thing, the `id` currently seems also "optional". 🤔
|
||||
|
||||
This is because in our **SQLModel** class we declare the `id` with `Optional[int]`, because it could be `None` in memory until we save it in the database and we finally get the actual ID.
|
||||
|
||||
But in the responses, we would always send a model from the database, and it would **always have an ID**. So the `id` in the responses could be declared as required too.
|
||||
But in the responses, we always send a model from the database, so it **always has an ID**. So the `id` in the responses can be declared as required.
|
||||
|
||||
This would mean that our application is making the compromise with the clients that if it sends a hero, it would for sure have an `id` with a value, it would not be `None`.
|
||||
This means that our application is making the promise to the clients that if it sends a hero, it will for sure have an `id` with a value, it will not be `None`.
|
||||
|
||||
### Why Is it Important to Compromise with the Responses
|
||||
### Why Is it Important to Have a Contract for Responses
|
||||
|
||||
The ultimate goal of an API is for some **clients to use it**.
|
||||
|
||||
@@ -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,32 +149,82 @@ The simplest way to solve it could be to create **multiple models**, each one wi
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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
|
||||
|
||||
!!! tip
|
||||
We will improve this code to avoid duplicating the fields, but for now we can continue learning with these models.
|
||||
|
||||
///
|
||||
|
||||
## Use Multiple Models to Create a Hero
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -153,19 +233,66 @@ Let's first check how is the process to create a hero now:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -174,15 +301,45 @@ 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.from_orm()`.
|
||||
////
|
||||
|
||||
The method `.from_orm()` reads data from another object with attributes and creates a new instance of this class, in this case `Hero`.
|
||||
Then we create a new `Hero` (this is the actual **table** model that saves things to the database) using `Hero.model_validate()`.
|
||||
|
||||
The alternative is `Hero.parse_obj()` that reads data from a dictionary.
|
||||
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`.
|
||||
|
||||
But as in this case, we have a `HeroCreate` instance in the `hero` variable. This is an object with attributes, so we use `.from_orm()` to read those attributes.
|
||||
In this case, we have a `HeroCreate` instance in the `hero` variable. This is an object with attributes, so we use `.model_validate()` to read those attributes.
|
||||
|
||||
With this, we 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.
|
||||
/// tip
|
||||
In versions of **SQLModel** before `0.0.14` you would use the method `.from_orm()`, but it is now deprecated and you should use `.model_validate()` instead.
|
||||
///
|
||||
|
||||
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 👆
|
||||
@@ -192,11 +349,39 @@ With this, we create a new `Hero` instance (the one for the database) and put it
|
||||
# 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 👆
|
||||
@@ -206,13 +391,18 @@ 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
|
||||
/// tip
|
||||
|
||||
This filtering could be very important and could be a very good security feature, for example, to make sure you filter private data, hashed passwords, etc.
|
||||
|
||||
You can read more about it in the <a href="https://fastapi.tiangolo.com/tutorial/response-model/" class="external-link" target="_blank">FastAPI docs about Response Model</a>.
|
||||
|
||||
///
|
||||
|
||||
In particular, it will make sure that the `id` is there and that it is indeed an integer (and not `None`).
|
||||
|
||||
## Shared Fields
|
||||
@@ -253,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 👆
|
||||
|
||||
@@ -261,14 +477,35 @@ So let's create a **base** model `HeroBase` that the others can inherit from:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
As you can see, this is *not* a **table model**, it doesn't have the `table = True` config.
|
||||
|
||||
@@ -278,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 👆
|
||||
|
||||
@@ -286,14 +549,35 @@ Let's start with the only **table model**, the `Hero`:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Notice that `Hero` now doesn't inherit from `SQLModel`, but from `HeroBase`.
|
||||
|
||||
@@ -309,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 👆
|
||||
|
||||
@@ -317,14 +627,35 @@ Notice that the parent model `HeroBase` is not a **table model**, but still, we
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This won't affect this parent **data model** `HeroBase`.
|
||||
|
||||
@@ -336,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 👆
|
||||
|
||||
@@ -344,14 +701,35 @@ This is a fun one:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
What's happening here?
|
||||
|
||||
@@ -365,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 👆
|
||||
|
||||
@@ -379,18 +783,39 @@ This one just declares that the `id` field is required when reading a hero from
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
@@ -8,9 +8,38 @@ Let's add a new *path operation* to read one single hero.
|
||||
|
||||
We want to get the hero based on the `id`, so we will use a **path parameter** `hero_id`.
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
If you need to refresh how *path parameters* work, including their data validation, check the <a href="https://fastapi.tiangolo.com/tutorial/path-params/" class="external-link" target="_blank">FastAPI docs about Path Parameters</a>.
|
||||
|
||||
///
|
||||
|
||||
//// 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]!}
|
||||
|
||||
@@ -19,14 +48,35 @@ We want to get the hero based on the `id`, so we will use a **path parameter** `
|
||||
{!./docs_src/tutorial/fastapi/read_one/tutorial001.py[ln:61-67]!}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
For example, to get the hero with ID `2` we would send a `GET` request to:
|
||||
|
||||
@@ -46,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]!}
|
||||
|
||||
@@ -54,20 +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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## 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]!}
|
||||
@@ -77,14 +200,35 @@ 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Check the Docs UI
|
||||
|
||||
|
||||
@@ -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,18 +114,73 @@ And the same way, we declared the `TeamRead` with only the same base fields of t
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Now, remember that <a href="https://fastapi.tiangolo.com/tutorial/response-model/" class="external-link" target="_blank">FastAPI uses the `response_model` to validate and **filter** the response data</a>?
|
||||
|
||||
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 👆
|
||||
@@ -84,19 +189,40 @@ In this case, we used `response_model=TeamRead` and `response_model=HeroRead`, s
|
||||
|
||||
# Code here omitted 👈
|
||||
|
||||
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:159-164]!}
|
||||
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:158-163]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Don't Include All the Data
|
||||
|
||||
@@ -174,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 👆
|
||||
|
||||
@@ -186,24 +338,45 @@ We'll add them **after** the other models so that we can easily reference the pr
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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
|
||||
|
||||
@@ -213,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
|
||||
|
||||
@@ -227,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 👆
|
||||
|
||||
@@ -234,19 +441,40 @@ In the case of the hero, this tells FastAPI to extract the `team` too. And in th
|
||||
|
||||
# Code here omitted 👈
|
||||
|
||||
{!./docs_src/tutorial/fastapi/relationships/tutorial001.py[ln:168-173]!}
|
||||
{!./docs_src/tutorial/fastapi/relationships/tutorial001.py[ln:167-172]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Check It Out in the Docs UI
|
||||
|
||||
@@ -267,7 +495,7 @@ Now we get the **team** data included:
|
||||
"id": 1,
|
||||
"team": {
|
||||
"name": "Z-Force",
|
||||
"headquarters": "Sister Margaret’s Bar",
|
||||
"headquarters": "Sister Margaret's Bar",
|
||||
"id": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,14 +66,35 @@ For example, we can pass the same `Hero` **SQLModel** class (because it is also
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## List of Heroes in `response_model`
|
||||
|
||||
@@ -55,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]!}
|
||||
|
||||
@@ -65,14 +140,35 @@ First, we import `List` from `typing` and then we declare the `response_model` w
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## FastAPI and Response Model
|
||||
|
||||
@@ -100,11 +196,14 @@ Additionally, because the schemas are defined in using a standard, there are man
|
||||
|
||||
For example, client generators, that can automatically create the code necessary to talk to your API in many languages.
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
If you are curious about the standards, FastAPI generates OpenAPI, that internally uses JSON Schema.
|
||||
|
||||
You can read about all that in the <a href="https://fastapi.tiangolo.com/tutorial/first-steps/#openapi" class="external-link" target="_blank">FastAPI docs - First Steps</a>.
|
||||
|
||||
///
|
||||
|
||||
## Recap
|
||||
|
||||
Use the `response_model` to tell FastAPI the schema of the data you want to send back and have awesome data APIs. 😎
|
||||
|
||||
@@ -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,14 +40,35 @@ Up to now, we have been creating a session in each *path operation*, in a `with`
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
That's perfectly fine, but in many use cases we would want to use <a href="https://fastapi.tiangolo.com/tutorial/dependencies/" class="external-link" target="_blank">FastAPI Dependencies</a>, for example to **verify** that the client is **logged in** and get the **current user** before executing any other code in the *path operation*.
|
||||
|
||||
@@ -35,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 👆
|
||||
|
||||
@@ -43,14 +116,35 @@ It could use `yield` instead of `return`, and in that case **FastAPI** will make
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Use the Dependency
|
||||
|
||||
@@ -58,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]!}
|
||||
|
||||
@@ -72,16 +204,38 @@ We import `Depends()` from `fastapi`. Then we use it in the *path operation func
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
/// tip
|
||||
|
||||
!!! tip
|
||||
Here's a tip about that `*,` thing in the parameters.
|
||||
|
||||
Here we are passing the parameter `session` that has a "default value" of `Depends(get_session)` before the parameter `hero`, that doesn't have any default value.
|
||||
@@ -90,6 +244,8 @@ We import `Depends()` from `fastapi`. Then we use it in the *path operation func
|
||||
|
||||
You can read more about it in the FastAPI documentation <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#order-the-parameters-as-you-need-tricks" class="external-link" target="_blank">Path Parameters and Numeric Validations - Order the parameters as you need, tricks</a>
|
||||
|
||||
///
|
||||
|
||||
The value of a dependency will **only be used for one request**, FastAPI will call it right before calling your code and will give you the value from that dependency.
|
||||
|
||||
If it had `yield`, then it will continue the rest of the execution once you are done sending the response. In the case of the **session**, it will finish the cleanup code from the `with` block, closing the session, etc.
|
||||
@@ -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,19 +312,78 @@ This means that in the main code of the *path operation function*, it will work
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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]!}
|
||||
|
||||
@@ -145,14 +398,35 @@ But now, the `with` block is not explicitly in the function, but in the dependen
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
We will see how this is very useful when testing the code later. ✅
|
||||
|
||||
@@ -168,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]!}
|
||||
|
||||
@@ -177,17 +485,38 @@ And then we remove the previous `with` block with the old **session**.
|
||||
|
||||
# Code here omitted 👈
|
||||
|
||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:55-107]!}
|
||||
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:55-106]!}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Recap
|
||||
|
||||
|
||||
@@ -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,14 +59,27 @@ This is almost the same code we have seen up to now in previous examples:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
There's only one change here from the code we have used before, the `check_same_thread` in the `connect_args`.
|
||||
|
||||
@@ -62,11 +91,14 @@ But here we will make sure we don't share the same **session** in more than one
|
||||
|
||||
And we also need to disable it because in **FastAPI** each request could be handled by multiple interacting threads.
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
That's enough information for now, you can read more about it in the <a href="https://fastapi.tiangolo.com/async/" class="external-link" target="_blank">FastAPI docs for `async` and `await`</a>.
|
||||
|
||||
The main point is, by ensuring you **don't share** the same **session** with more than one request, the code is already safe.
|
||||
|
||||
///
|
||||
|
||||
## **FastAPI** App
|
||||
|
||||
The next step is to create the **FastAPI** app.
|
||||
@@ -75,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]!}
|
||||
|
||||
@@ -85,14 +133,27 @@ And then create an `app` object that is an instance of that `FastAPI` class:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Create Database and Tables on `startup`
|
||||
|
||||
@@ -100,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 👆
|
||||
|
||||
@@ -108,24 +183,54 @@ This should be called only once at startup, not before every request, so we put
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Create Heroes *Path Operation*
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
If you need a refresher on what a **Path Operation** is (an endpoint with a specific HTTP Operation) and how to work with it in FastAPI, check out the <a href="https://fastapi.tiangolo.com/tutorial/first-steps/" class="external-link" target="_blank">FastAPI First Steps docs</a>.
|
||||
|
||||
///
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -134,22 +239,38 @@ It will be called when a user sends a request with a `POST` **operation** to the
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
/// info
|
||||
|
||||
!!! info
|
||||
If you need a refresher on some of those concepts, checkout the FastAPI documentation:
|
||||
|
||||
* <a href="https://fastapi.tiangolo.com/tutorial/first-steps/" class="external-link" target="_blank">First Steps</a>
|
||||
* <a href="https://fastapi.tiangolo.com/tutorial/path-params/" class="external-link" target="_blank">Path Parameters - Data Validation and Data Conversion</a>
|
||||
* <a href="https://fastapi.tiangolo.com/tutorial/body/" class="external-link" target="_blank">Request Body</a>
|
||||
|
||||
///
|
||||
|
||||
## The **SQLModel** Advantage
|
||||
|
||||
Here's where having our **SQLModel** class models be both **SQLAlchemy** models and **Pydantic** models at the same time shine. ✨
|
||||
@@ -162,27 +283,55 @@ And then, because this same **SQLModel** object is not only a **Pydantic** model
|
||||
|
||||
So we can use intuitive standard Python **type annotations**, and we don't have to duplicate a lot of the code for the database models and the API data models. 🎉
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
We will improve this further later, but for now, it already shows the power of having **SQLModel** classes be both **SQLAlchemy** models and **Pydantic** models at the same time.
|
||||
|
||||
///
|
||||
|
||||
## Read Heroes *Path Operation*
|
||||
|
||||
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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This is pretty straightforward.
|
||||
|
||||
@@ -226,12 +375,15 @@ $ uvicorn main:app
|
||||
|
||||
</div>
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
The command `uvicorn main:app` refers to:
|
||||
|
||||
* `main`: the file `main.py` (the Python "module").
|
||||
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
|
||||
|
||||
///
|
||||
|
||||
### Uvicorn `--reload`
|
||||
|
||||
During development (and only during development), you can also add the option `--reload` to Uvicorn.
|
||||
|
||||
@@ -14,24 +14,67 @@ 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
We now also have **relationship attributes**. 🎉
|
||||
|
||||
@@ -39,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]!}
|
||||
@@ -47,14 +116,35 @@ Let's now update the `Hero` models too.
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
We now have a `team_id` in the hero models.
|
||||
|
||||
@@ -66,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 👆
|
||||
|
||||
@@ -74,14 +190,35 @@ Notice that the **relationship attributes**, the ones with `Relationship()`, are
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Path Operations for Teams
|
||||
|
||||
@@ -89,22 +226,69 @@ 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.py[ln:139-193]!}
|
||||
{!./docs_src/tutorial/fastapi/teams/tutorial001_py310.py[ln:136-190]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
//// 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 👆
|
||||
|
||||
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:138-192]!}
|
||||
|
||||
# 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Using Relationships Attributes
|
||||
|
||||
|
||||
@@ -14,14 +14,13 @@ We will use the application with the hero models, but without team models, and w
|
||||
|
||||
Now we will see how useful it is to have this session dependency. ✨
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/fastapi/app_testing/tutorial001/main.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
///
|
||||
|
||||
## File Structure
|
||||
|
||||
@@ -71,9 +70,12 @@ Let's start with a simple test, with just the basic test code we need the check
|
||||
|
||||
{!./docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_001.md!}
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Check out the number bubbles to see what is done by each line of code.
|
||||
|
||||
///
|
||||
|
||||
That's the **core** of the code we need for all the tests later.
|
||||
|
||||
But now, we need to deal with a bit of logistics and details we are not paying attention to just yet. 🤓
|
||||
@@ -82,7 +84,7 @@ But now, we need to deal with a bit of logistics and details we are not paying a
|
||||
|
||||
This test looks fine, but there's a problem.
|
||||
|
||||
If we run it, it will use the same **production database** that we are using to store our very important **heroes**, and we will end up adding unnecesary data to it, or even worse, in future tests we could end up removing production data.
|
||||
If we run it, it will use the same **production database** that we are using to store our very important **heroes**, and we will end up adding unnecessary data to it, or even worse, in future tests we could end up removing production data.
|
||||
|
||||
So, we should use an independent **testing database**, just for the tests.
|
||||
|
||||
@@ -116,9 +118,12 @@ That way we protect the production database and we have better control of the da
|
||||
|
||||
{!./docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_002.md!}
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Check out the number bubbles to see what is done by each line of code.
|
||||
|
||||
///
|
||||
|
||||
## Create the Engine and Session for Testing
|
||||
|
||||
Now let's create that **session** object that will be used during testing.
|
||||
@@ -165,10 +170,8 @@ But **it works great for testing**, because it can be quickly created before eac
|
||||
|
||||
And also, because it never has to write anything to a file and it's all just in memory, it will be even faster than normally. 🏎
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Other alternatives and ideas 👀
|
||||
</summary>
|
||||
/// details | Other alternatives and ideas 👀
|
||||
|
||||
Before arriving at the idea of using an **in-memory database** we could have explored other alternatives and ideas.
|
||||
|
||||
The first is that we are not deleting the file after we finish the test, so the next test could have **leftover data**. So, the right thing would be to delete the file right after finishing the test. 🔥
|
||||
@@ -181,7 +184,7 @@ So, if we tried to run the tests at the same time **in parallel** to try to spee
|
||||
|
||||
Of course, we could also fix that, using some **random name** for each testing database file... but in the case of SQLite, we have an even better alternative by just using an **in-memory database**. ✨
|
||||
|
||||
</details>
|
||||
///
|
||||
|
||||
## Configure the In-Memory Database
|
||||
|
||||
@@ -197,9 +200,12 @@ We just have to change a couple of parameters in the **engine**.
|
||||
|
||||
{!./docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_004.md!}
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Check out the number bubbles to see what is done by each line of code.
|
||||
|
||||
///
|
||||
|
||||
That's it, now the test will run using the **in-memory database**, which will be faster and probably safer.
|
||||
|
||||
And all the other tests can do the same.
|
||||
@@ -214,9 +220,12 @@ Do we really have to duplicate all that for **each test**? No, we can do better!
|
||||
|
||||
We are using **pytest** to run the tests. And pytest also has a very similar concept to the **dependencies in FastAPI**.
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
In fact, pytest was one of the things that inspired the design of the dependencies in FastAPI.
|
||||
|
||||
///
|
||||
|
||||
It's a way for us to declare some **code that should be run before** each test and **provide a value** for the test function (that's pretty much the same as FastAPI dependencies).
|
||||
|
||||
In fact, it also has the same trick of allowing to use `yield` instead of `return` to provide the value, and then **pytest** makes sure that the code after `yield` is executed *after* the function with the test is done.
|
||||
@@ -237,9 +246,12 @@ Let's see the first code example with a fixture:
|
||||
|
||||
{!./docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_005.md!}
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Check out the number bubbles to see what is done by each line of code.
|
||||
|
||||
///
|
||||
|
||||
**pytest** fixtures work in a very similar way to FastAPI dependencies, but have some minor differences:
|
||||
|
||||
* In pytest fixtures, we need to add a decorator of `@pytest.fixture()` on top.
|
||||
@@ -274,9 +286,12 @@ So, we can create a **client fixture** that will be used in all the tests, and i
|
||||
|
||||
{!./docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_006.md!}
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Check out the number bubbles to see what is done by each line of code.
|
||||
|
||||
///
|
||||
|
||||
Now we have a **client fixture** that, in turn, uses the **session fixture**.
|
||||
|
||||
And in the actual test function, we just have to declare that we require this **client fixture**.
|
||||
@@ -297,20 +312,22 @@ Let's add some more tests:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/fastapi/app_testing/tutorial001/test_main.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
///
|
||||
|
||||
/// tip
|
||||
|
||||
!!! tip
|
||||
It's always **good idea** to not only test the normal case, but also that **invalid data**, **errors**, and **corner cases** are handled correctly.
|
||||
|
||||
That's why we add these two extra tests here.
|
||||
|
||||
///
|
||||
|
||||
Now, any additional test functions can be as **simple** as the first one, they just have to **declare the `client` parameter** to get the `TestClient` **fixture** with all the database stuff setup. Nice! 😎
|
||||
|
||||
## Why Two Fixtures
|
||||
@@ -331,14 +348,13 @@ But for the next test function, we will require **both fixtures**, the **client*
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/fastapi/app_testing/tutorial001/test_main.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
///
|
||||
|
||||
In this test function, we want to check that the *path operation* to **read a list of heroes** actually sends us heroes.
|
||||
|
||||
@@ -370,14 +386,13 @@ Using the same ideas, requiring the fixtures, creating data that we need for the
|
||||
{!./docs_src/tutorial/fastapi/app_testing/tutorial001/test_main.py[ln:84-125]!}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/fastapi/app_testing/tutorial001/test_main.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
///
|
||||
|
||||
## Run the Tests
|
||||
|
||||
|
||||
465
docs/tutorial/fastapi/update-extra-data.md
Normal file
@@ -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 <a href="https://passlib.readthedocs.io/en/stable/" class="external-link" target="_blank">passlib</a> 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. 🤓
|
||||
@@ -12,13 +12,42 @@ So, we need to have all those fields **marked as optional**.
|
||||
|
||||
And because the `HeroBase` has some of them as *required* and not optional, we will need to **create a new model**.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Here is one of those cases where it probably makes sense to use an **independent model** instead of trying to come up with a complex tree of models inheriting from each other.
|
||||
|
||||
Because each field is **actually different** (we just change it to `Optional`, but that's already making it different), it makes sense to have them in their own model.
|
||||
|
||||
///
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -27,14 +56,35 @@ So, let's create this new `HeroUpdate` model:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This is almost the same as `HeroBase`, but all the fields are optional, so we can't simply inherit from `HeroBase`.
|
||||
|
||||
@@ -44,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 👆
|
||||
|
||||
@@ -52,14 +128,35 @@ We will use a `PATCH` HTTP operation. This is used to **partially update data**,
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
We also read the `hero_id` from the *path parameter* and the request body, a `HeroUpdate`.
|
||||
|
||||
@@ -69,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 👆
|
||||
|
||||
@@ -77,20 +200,41 @@ So, we need to read the hero from the database, with the **same logic** we used
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
### Get the New Data
|
||||
|
||||
The `HeroUpdate` model has all the fields with **default values**, because they all have defaults, they are all optional, which is what we want.
|
||||
|
||||
But that also means that if we just call `hero.dict()` we will get a dictionary that could potentially have several or all of those values with their defaults, for example:
|
||||
But that also means that if we just call `hero.model_dump()` we will get a dictionary that could potentially have several or all of those values with their defaults, for example:
|
||||
|
||||
```Python
|
||||
{
|
||||
@@ -102,7 +246,7 @@ But that also means that if we just call `hero.dict()` we will get a dictionary
|
||||
|
||||
And then, if we update the hero in the database with this data, we would be removing any existing values, and that's probably **not what the client intended**.
|
||||
|
||||
But fortunately Pydantic models (and so SQLModel models) have a parameter we can pass to the `.dict()` method for that: `exclude_unset=True`.
|
||||
But fortunately Pydantic models (and so SQLModel models) have a parameter we can pass to the `.model_dump()` method for that: `exclude_unset=True`.
|
||||
|
||||
This tells Pydantic to **not include** the values that were **not sent** by the client. Saying it another way, it would **only** include the values that were **sent by the client**.
|
||||
|
||||
@@ -112,7 +256,7 @@ So, if the client sent a JSON with no values:
|
||||
{}
|
||||
```
|
||||
|
||||
Then the dictionary we would get in Python using `hero.dict(exclude_unset=True)` would be:
|
||||
Then the dictionary we would get in Python using `hero.model_dump(exclude_unset=True)` would be:
|
||||
|
||||
```Python
|
||||
{}
|
||||
@@ -126,7 +270,7 @@ But if the client sent a JSON with:
|
||||
}
|
||||
```
|
||||
|
||||
Then the dictionary we would get in Python using `hero.dict(exclude_unset=True)` would be:
|
||||
Then the dictionary we would get in Python using `hero.model_dump(exclude_unset=True)` would be:
|
||||
|
||||
```Python
|
||||
{
|
||||
@@ -136,6 +280,32 @@ Then the dictionary we would get in Python using `hero.dict(exclude_unset=True)`
|
||||
|
||||
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,20 +314,71 @@ Then we use that to get the data that was actually sent by the client:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
/// 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]!}
|
||||
@@ -165,28 +386,47 @@ Now that we have a **dictionary with the data sent by the client**, we can itera
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
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:
|
||||
/// tip
|
||||
|
||||
```Python
|
||||
setattr(db_hero, key, value)
|
||||
```
|
||||
The method `db_hero.sqlmodel_update()` was added in SQLModel 0.0.16. 🤓
|
||||
|
||||
...would be more or less equivalent to:
|
||||
Before that, you would need to manually get the values and set them using `setattr()`.
|
||||
|
||||
```Python
|
||||
db_hero.name = "Deadpuddle"
|
||||
```
|
||||
///
|
||||
|
||||
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
|
||||
|
||||
@@ -210,7 +450,7 @@ So, if the client wanted to intentionally remove the `age` of a hero, they could
|
||||
}
|
||||
```
|
||||
|
||||
And when getting the data with `hero.dict(exclude_unset=True)`, we would get:
|
||||
And when getting the data with `hero.model_dump(exclude_unset=True)`, we would get:
|
||||
|
||||
```Python
|
||||
{
|
||||
@@ -228,4 +468,4 @@ These are some of the advantages of Pydantic, that we can use with SQLModel.
|
||||
|
||||
## Recap
|
||||
|
||||
Using `.dict(exclude_unset=True)` in SQLModel models (and Pydantic models) we can easily update data **correctly**, even in the **edge cases**. 😎
|
||||
Using `.model_dump(exclude_unset=True)` in SQLModel models (and Pydantic models) we can easily update data **correctly**, even in the **edge cases**. 😎
|
||||
|
||||
@@ -57,22 +57,23 @@ $ cd sqlmodel-tutorial
|
||||
|
||||
</div>
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Make sure you don't name it also `sqlmodel`, so that you don't end up overriding the name of the package.
|
||||
|
||||
///
|
||||
|
||||
### Make sure you have Python
|
||||
|
||||
Make sure you have an officially supported version of Python.
|
||||
|
||||
Currently it is **Python 3.6** and above (Python 3.5 was already deprecated).
|
||||
|
||||
You can check which version you have with:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python3 --version
|
||||
Python 3.6.9
|
||||
Python 3.11
|
||||
```
|
||||
|
||||
</div>
|
||||
@@ -81,11 +82,10 @@ There's a chance that you have multiple Python versions installed.
|
||||
|
||||
You might want to try with the specific versions, for example with:
|
||||
|
||||
* `python3.12`
|
||||
* `python3.11`
|
||||
* `python3.10`
|
||||
* `python3.9`
|
||||
* `python3.8`
|
||||
* `python3.7`
|
||||
* `python3.6`
|
||||
|
||||
The code would look like this:
|
||||
|
||||
@@ -122,16 +122,19 @@ In very short, a virtual environment is a small directory that contains a copy o
|
||||
|
||||
And when you "activate" it, any package that you install, for example with `pip`, will be installed in that virtual environment.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
There are other tools to manage virtual environments, like <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a>.
|
||||
|
||||
And there are alternatives that are particularly useful for deployment like <a href="https://docs.docker.com/get-started/" class="external-link" target="_blank">Docker</a> and other types of containers. In this case, the "virtual environment" is not just the Python standard files and the installed packages, but the whole system.
|
||||
|
||||
///
|
||||
|
||||
Go ahead and create a Python virtual environment for this project. And make sure to also upgrade `pip`.
|
||||
|
||||
Here are the commands you could use:
|
||||
|
||||
=== "Linux, macOS, Linux in Windows"
|
||||
/// tab | Linux, macOS, Linux in Windows
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -139,7 +142,7 @@ Here are the commands you could use:
|
||||
// Remember that you might need to use python3.9 or similar 💡
|
||||
// Create the virtual environment using the module "venv"
|
||||
$ python3 -m venv env
|
||||
// ...here it creates the virtual enviroment in the directory "env"
|
||||
// ...here it creates the virtual environment in the directory "env"
|
||||
// Activate the virtual environment
|
||||
$ source ./env/bin/activate
|
||||
// Verify that the virtual environment is active
|
||||
@@ -154,14 +157,16 @@ Here are the commands you could use:
|
||||
|
||||
</div>
|
||||
|
||||
=== "Windows PowerShell"
|
||||
///
|
||||
|
||||
/// tab | Windows PowerShell
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Create the virtual environment using the module "venv"
|
||||
# >$ python3 -m venv env
|
||||
// ...here it creates the virtual enviroment in the directory "env"
|
||||
// ...here it creates the virtual environment in the directory "env"
|
||||
// Activate the virtual environment
|
||||
# >$ .\env\Scripts\Activate.ps1
|
||||
// Verify that the virtual environment is active
|
||||
@@ -178,6 +183,8 @@ Here are the commands you could use:
|
||||
|
||||
</div>
|
||||
|
||||
///
|
||||
|
||||
## Install **SQLModel**
|
||||
|
||||
Now, after making sure we are inside of a virtual environment in some way, we can install **SQLModel**:
|
||||
|
||||
@@ -20,14 +20,25 @@ Are you already a **SQL expert** and don't have time for all my explanations?
|
||||
|
||||
Fine, in that case, you can **sneak peek** the final code to create indexes here.
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
..but if you are not an expert, **continue reading**, this will probably be useful. 🤓
|
||||
|
||||
@@ -73,13 +84,16 @@ You repeat this process **a few more times**, and you finally arrive at the lett
|
||||
|
||||
You had to open the dictionary a few times, maybe **5 or 10**. That's actually **very little work** compared to what it could have been.
|
||||
|
||||
!!! note "Technical Details"
|
||||
/// note | Technical Details
|
||||
|
||||
Do you like **fancy words**? Cool! Programmers tend to like fancy words. 😅
|
||||
|
||||
That <abbr title="a recipe, a sequence of predefined steps that achieve a result">algorithm</abbr> I showed you above is called **Binary Search**.
|
||||
|
||||
It's called like that because you **search** something by splitting the dictionary (or any ordered list of things) in **two** ("binary" means "two") parts. And you do that process multiple times until you find what you want.
|
||||
|
||||
///
|
||||
|
||||
### An Index and a Novel
|
||||
|
||||
Let's now imagine you are reading a **novel book**. And someone told you that at some point, they mention a **database**, and you want to find that chapter.
|
||||
@@ -261,47 +275,100 @@ 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
We use the same `Field()` again as we did before, and set `index=True`. That's it! 🚀
|
||||
|
||||
Notice that we didn't set an argument of `default=None` or anything similar. This means that **SQLModel** (thanks to Pydantic) will keep it as a **required** field.
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
SQLModel (actually SQLAlchemy) will **automatically generate the index name** for you.
|
||||
|
||||
In this case the generated name would be `ix_hero_name`.
|
||||
|
||||
///
|
||||
|
||||
## Query Data
|
||||
|
||||
Now, to query the data using the field `name` and the new index we don't have to do anything special or different in the code, it's just **the same code**.
|
||||
@@ -310,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 👆
|
||||
|
||||
@@ -318,14 +399,27 @@ This is great because it means that indexes are very **simple to use**. But it m
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This is exactly the same code as we had before, but now the database will **use the index** underneath.
|
||||
|
||||
@@ -368,20 +462,45 @@ 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
In this case, we want the default value of `age` to continue being `None`, so we set `default=None` when using `Field()`.
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -70,9 +88,12 @@ You can try that SQL statement in **DB Explorer for SQLite**.
|
||||
|
||||
Make sure to open the same database we already created by clicking <kbd>Open Database</kbd> and selecting the same `database.db` file.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
If you don't have that `database.db` file with the table `hero`, you can re-create it by running the Python program at the top. 👆
|
||||
|
||||
///
|
||||
|
||||
Then go to the <kbd>Execute SQL</kbd> tab and copy the SQL from above.
|
||||
|
||||
It would look like this:
|
||||
@@ -124,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 👆
|
||||
|
||||
@@ -132,20 +167,36 @@ We'll create 3 right away, for the 3 heroes:
|
||||
# More code here later 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
/// tip
|
||||
|
||||
!!! tip
|
||||
The code above in this file (the omitted code) is just the same code that you see at the top of this chapter.
|
||||
|
||||
The same code we used before to create the `Hero` model.
|
||||
|
||||
///
|
||||
|
||||
We are putting that in a function `create_heroes()`, to call it later once we finish it.
|
||||
|
||||
If you are trying the code interactively, you could also write that directly.
|
||||
@@ -168,23 +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 ommitted 👇
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -193,24 +283,53 @@ Then we can create a new session:
|
||||
# More code here later 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
The new `Session` takes an `engine` as a parameter. And it will use the **engine** underneath.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
We will see a better way to create a **session** using a `with` block later.
|
||||
|
||||
///
|
||||
|
||||
## Add Model Instances to the Session
|
||||
|
||||
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]!}
|
||||
@@ -218,14 +337,27 @@ Now that we have some hero model instances (some objects in memory) and a **sess
|
||||
# More code here later 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
By this point, our heroes are *not* stored in the database yet.
|
||||
|
||||
@@ -237,15 +369,31 @@ And once we are ready, we can **commit** those changes, and then the **session**
|
||||
|
||||
This makes the interactions with the database more efficient (plus some extra benefits).
|
||||
|
||||
!!! info "Technical Details"
|
||||
/// info | Technical Details
|
||||
|
||||
The session will create a new transaction and execute all the SQL code in that transaction.
|
||||
|
||||
This ensures that the data is saved in a single batch, and that it will all succeed or all fail, but it won't leave the database in a broken state.
|
||||
|
||||
///
|
||||
|
||||
## Commit the Session Changes
|
||||
|
||||
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]!}
|
||||
@@ -253,14 +401,27 @@ Now that we have the heroes in the **session** and that we are ready to save all
|
||||
# More code here later 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Once this line is executed, the **session** will use the **engine** to save all the data in the database by sending the corresponding SQL.
|
||||
|
||||
@@ -287,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]!}
|
||||
@@ -294,30 +468,67 @@ But to keep things a bit more organized, let's instead create a new function `ma
|
||||
# More code here later 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
By having everything that should happen when called as a script in a single function, we can easily add more code later on.
|
||||
|
||||
@@ -372,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 👆
|
||||
|
||||
@@ -380,14 +605,27 @@ So once we are done with the session, we should **close** it to make it release
|
||||
# More code here later 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
But what happens if we forget to close the session?
|
||||
|
||||
@@ -401,19 +639,43 @@ 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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**.
|
||||
|
||||
@@ -427,15 +689,32 @@ 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
|
||||
////
|
||||
|
||||
/// tip
|
||||
|
||||
Review what each line does by clicking each number bubble in the code. 👆
|
||||
|
||||
///
|
||||
|
||||
You can now put it in a `app.py` file and run it with Python. And you will see an output like the one shown above.
|
||||
|
||||
After that, if you open the database with **DB Browser for SQLite**, you will see the data you just created in the <kbd>Browse Data</kbd> tab:
|
||||
|
||||
@@ -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,19 +36,46 @@ Again, we will create several heroes to have some data to select from:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## 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 👆
|
||||
|
||||
@@ -43,14 +84,27 @@ This is the code we had to select all the heroes in the `select()` examples:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
But this would get us **all** the heroes at the same time, in a database that could have thousands, that could be problematic.
|
||||
|
||||
@@ -58,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 👆
|
||||
|
||||
@@ -66,14 +134,27 @@ We currently have 7 heroes in the database. But we could as well have thousands,
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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.
|
||||
|
||||
@@ -110,26 +191,46 @@ INFO Engine [no key 0.00014s] (3, 0)
|
||||
|
||||
Great! We got only 3 heroes as we wanted.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
We will check out that SQL code more in a bit.
|
||||
|
||||
///
|
||||
|
||||
## Select with Offset and Limit
|
||||
|
||||
Now we can limit the results to get only the first 3.
|
||||
|
||||
But imagine we are in a user interface showing the results in batches of 3 heroes at a time.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
This is commonly called "pagination". Because the user interface would normally show a "page" of a predefined number of heroes at a time.
|
||||
|
||||
And then you can interact with the user interface to get the next page, and so on.
|
||||
|
||||
///
|
||||
|
||||
How do we get the next 3?
|
||||
|
||||
<img class="shadow" alt="table with next rows selected, from 4 to 6" src="/img/tutorial/offset-and-limit/limit2.svg">
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -138,14 +239,27 @@ We can use `.offset()`:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
The way this works is that the special **select** object we get from `select()` has methods like `.where()`, `.offset()` and `.limit()`.
|
||||
|
||||
@@ -184,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 👆
|
||||
|
||||
@@ -192,14 +320,27 @@ Then to get the next batch of 3 rows we would offset all the ones we already saw
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
The database right now has **only 7 rows**, so this query can only get 1 row.
|
||||
|
||||
@@ -254,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 👆
|
||||
|
||||
@@ -262,20 +417,33 @@ Of course, you can also combine `.limit()` and `.offset()` with `.where()` and o
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
## Run the Program with Limit and Where on the Command Line
|
||||
///
|
||||
|
||||
## Run the Program with Limit, Offset, and Where on the Command Line
|
||||
|
||||
If we run it on the command line, it will find all the heroes in the database with an age above 32. That would normally be 4 heroes.
|
||||
|
||||
But we are limiting the results to only get the first 3:
|
||||
But we are starting to include after an offset of 1 (so we don't count the first one), and we are limiting the results to only get the first 2 after that:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -284,18 +452,17 @@ $ python app.py
|
||||
|
||||
// Previous output omitted 🙈
|
||||
|
||||
// Select with WHERE and LIMIT
|
||||
// Select with WHERE and LIMIT and OFFSET
|
||||
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
|
||||
FROM hero
|
||||
WHERE hero.age > ?
|
||||
LIMIT ? OFFSET ?
|
||||
INFO Engine [no key 0.00022s] (32, 3, 0)
|
||||
INFO Engine [no key 0.00022s] (32, 2, 1)
|
||||
|
||||
// Print the heroes received, only 3
|
||||
// Print the heroes received, only 2
|
||||
[
|
||||
Hero(age=35, secret_name='Trevor Challa', id=5, name='Black Lion'),
|
||||
Hero(age=36, secret_name='Steve Weird', id=6, name='Dr. Weird'),
|
||||
Hero(age=48, secret_name='Tommy Sharp', id=3, name='Rusty-Man')
|
||||
Hero(age=36, id=6, name='Dr. Weird', secret_name='Steve Weird'),
|
||||
Hero(age=48, id=3, name='Rusty-Man', secret_name='Tommy Sharp')
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
@@ -8,19 +8,64 @@ We'll create data for this same **many-to-many** relationship with a link table:
|
||||
|
||||
We'll continue from where we left off with the previous code.
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## 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 👆
|
||||
|
||||
@@ -29,14 +74,35 @@ As we have done before, we'll create a function `create_heroes()` and we'll crea
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This is very similar to what we have done before.
|
||||
|
||||
@@ -50,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 👆
|
||||
|
||||
@@ -58,20 +150,67 @@ Now let's do as we have done before, `commit` the **session**, `refresh` the dat
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## 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]!}
|
||||
@@ -79,14 +218,35 @@ As before, add the `create_heroes()` function to the `main()` function to make s
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Run the Program
|
||||
|
||||
@@ -110,7 +270,7 @@ INFO Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?)
|
||||
INFO Engine [cached since 0.002541s ago] ('Spider-Boy', 'Pedro Parqueador', None)
|
||||
// Insert the team data second
|
||||
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
|
||||
INFO Engine [generated in 0.00037s] ('Z-Force', 'Sister Margaret’s Bar')
|
||||
INFO Engine [generated in 0.00037s] ('Z-Force', 'Sister Margaret's Bar')
|
||||
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
|
||||
INFO Engine [cached since 0.001239s ago] ('Preventers', 'Sharp Tower')
|
||||
// Insert the link data last, to be able to re-use the created IDs
|
||||
@@ -145,7 +305,7 @@ WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
|
||||
INFO Engine [generated in 0.00025s] (1,)
|
||||
|
||||
// Print Deadpond's teams, 2 teams! 🎉
|
||||
Deadpond teams: [Team(id=1, name='Z-Force', headquarters='Sister Margaret’s Bar'), Team(id=2, name='Preventers', headquarters='Sharp Tower')]
|
||||
Deadpond teams: [Team(id=1, name='Z-Force', headquarters='Sister Margaret's Bar'), Team(id=2, name='Preventers', headquarters='Sharp Tower')]
|
||||
|
||||
// Print Rusty-Man
|
||||
Rusty-Man: name='Rusty-Man' age=48 id=2 secret_name='Tommy Sharp'
|
||||
@@ -179,4 +339,4 @@ INFO Engine ROLLBACK
|
||||
|
||||
## Recap
|
||||
|
||||
After setting up the model link, using it with **relationship attributes** is fairly straighforward, just Python objects. ✨
|
||||
After setting up the model link, using it with **relationship attributes** is fairly straightforward, just Python objects. ✨
|
||||
|
||||
@@ -12,20 +12,63 @@ 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This is a **SQLModel** class model table like any other.
|
||||
|
||||
@@ -39,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 👆
|
||||
|
||||
@@ -47,14 +116,35 @@ Let's see the `Team` model, it's almost identical as before, but with a little c
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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).
|
||||
|
||||
@@ -68,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 👆
|
||||
|
||||
@@ -76,14 +192,35 @@ Let's see the other side, here's the `Hero` model:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
We **removed** the previous `team_id` field (column) because now the relationship is done via the link table. 🔥
|
||||
|
||||
@@ -101,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 👆
|
||||
|
||||
@@ -109,18 +272,67 @@ The same as before, we will have the rest of the code to create the **engine**,
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -130,14 +342,35 @@ 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
|
||||
## Run the Code
|
||||
|
||||
@@ -26,13 +26,16 @@ The `team` table looks like this:
|
||||
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Notice that it doesn't have any foreign key to other tables.
|
||||
|
||||
///
|
||||
|
||||
And the `hero` table looks like this:
|
||||
|
||||
<table>
|
||||
@@ -106,7 +109,8 @@ Specifically, the new link table `heroteamlink` would be:
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
Other names used for this **link table** are:
|
||||
|
||||
* association table
|
||||
@@ -120,6 +124,8 @@ Specifically, the new link table `heroteamlink` would be:
|
||||
|
||||
I'm using the term "link table" because it's short, doesn't collide with other terms already used (e.g. "relationship"), it's easy to remember how to write it, etc.
|
||||
|
||||
///
|
||||
|
||||
## Link Primary Key
|
||||
|
||||
Cool, we have a link table with **just two columns**. But remember that SQL databases [require each row to have a **primary key**](../../databases.md#identifications-primary-key){.internal-link target=_blank} that **uniquely identifies** the row in that table?
|
||||
|
||||
@@ -18,9 +18,12 @@ A row in the table `heroteamlink` points to **one** particular hero, but a singl
|
||||
|
||||
And also, the same row in the table `heroteamlink` points to **one** team, but a single team can be connected to **many** hero-team links, so it's also **one-to-many**.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
The previous many-to-many relationship was also just two one-to-many relationships combined, but now it's going to be much more explicit.
|
||||
|
||||
///
|
||||
|
||||
## Update Link Model
|
||||
|
||||
Let's update the `HeroTeamLink` model.
|
||||
@@ -29,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 👆
|
||||
|
||||
@@ -37,31 +66,81 @@ And we will also add two **relationship attributes**, for the linked `team` and
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
The new **relationship attributes** have their own `back_populates` pointing to new relationship attributes we will create in the `Hero` and `Team` models:
|
||||
|
||||
* `team`: has `back_populates="hero_links"`, because in the `Team` model, the attribute will contain the links to the **team's heroes**.
|
||||
* `hero`: has `back_populates="team_links"`, because in the `Hero` model, the attribute will contain the links to the **hero's teams**.
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
In SQLAlchemy this is called an Association Object or Association Model.
|
||||
|
||||
I'm calling it **Link Model** just because that's easier to write avoiding typos. But you are also free to call it however you want. 😉
|
||||
|
||||
///
|
||||
|
||||
## Update Team Model
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -70,14 +149,35 @@ We no longer have the `heroes` relationship attribute, and instead we have the n
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Update Hero Model
|
||||
|
||||
@@ -85,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 👆
|
||||
|
||||
@@ -93,14 +219,35 @@ We change the `teams` relationship attribute for `team_links`:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Create Relationships
|
||||
|
||||
@@ -108,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 👆
|
||||
|
||||
@@ -116,14 +289,35 @@ But now we create the **explicit link models** manually, pointing to their hero
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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.
|
||||
|
||||
@@ -151,7 +345,7 @@ INFO Engine [cached since 0.001858s ago] ('Rusty-Man', 'Tommy Sharp', 48)
|
||||
|
||||
// Insert the teams
|
||||
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
|
||||
INFO Engine [generated in 0.00019s] ('Z-Force', 'Sister Margaret’s Bar')
|
||||
INFO Engine [generated in 0.00019s] ('Z-Force', 'Sister Margaret's Bar')
|
||||
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
|
||||
INFO Engine [cached since 0.0007985s ago] ('Preventers', 'Sharp Tower')
|
||||
|
||||
@@ -221,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 👆
|
||||
|
||||
@@ -229,14 +449,35 @@ Here we do that in the `update_heroes()` function:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Run the Program with the New Relationship
|
||||
|
||||
@@ -317,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 👆
|
||||
|
||||
@@ -329,14 +604,35 @@ We can do that by iterating on the links:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Run the Program with the Updated Relationships
|
||||
|
||||
@@ -389,7 +685,7 @@ WHERE team.id = ?
|
||||
INFO Engine [cached since 0.2097s ago] (1,)
|
||||
|
||||
// Print Spider-Boy team, including link data, if is training
|
||||
Spider-Boy team: headquarters='Sister Margaret’s Bar' id=1 name='Z-Force' is training: True
|
||||
Spider-Boy team: headquarters='Sister Margaret's Bar' id=1 name='Z-Force' is training: True
|
||||
INFO Engine ROLLBACK
|
||||
```
|
||||
|
||||
|
||||
@@ -4,14 +4,33 @@ Now we'll see how to update and remove these **many-to-many** relationships.
|
||||
|
||||
We'll continue from where we left off with the previous code.
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Get Data to Update
|
||||
|
||||
@@ -23,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]!}
|
||||
|
||||
@@ -33,31 +82,95 @@ And because we are now using `select()`, we also have to import it.
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Add Many-to-Many Relationships
|
||||
|
||||
@@ -65,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 👆
|
||||
|
||||
@@ -73,20 +212,44 @@ We can use the same **relationship attributes** to include `hero_spider_boy` in
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
/// tip
|
||||
|
||||
!!! tip
|
||||
Because we are accessing an attribute in the models right after we commit, with `hero_spider_boy.teams` and `team_z_force.heroes`, the data is refreshed automatically.
|
||||
|
||||
So we don't have to call `session.refresh()`.
|
||||
|
||||
///
|
||||
|
||||
We then commit the change, refresh, and print the updated **Spider-Boy**'s heroes to confirm.
|
||||
|
||||
Notice that we only `add` **Z-Force** to the session, then we commit.
|
||||
@@ -127,7 +290,7 @@ INFO Engine [cached since 0.1648s ago] (3,)
|
||||
// Print Spider-Boy teams, including Z-Force 🎉
|
||||
Updated Spider-Boy's Teams: [
|
||||
Team(id=2, name='Preventers', headquarters='Sharp Tower'),
|
||||
Team(id=1, name='Z-Force', headquarters='Sister Margaret’s Bar')
|
||||
Team(id=1, name='Z-Force', headquarters='Sister Margaret's Bar')
|
||||
]
|
||||
|
||||
// Automatically refresh the data while accessing the attribute .heores
|
||||
@@ -141,7 +304,7 @@ Z-Force heroes: [
|
||||
Hero(name='Deadpond', age=None, id=1, secret_name='Dive Wilson'),
|
||||
Hero(name='Spider-Boy', age=None, id=3, secret_name='Pedro Parqueador', teams=[
|
||||
Team(id=2, name='Preventers', headquarters='Sharp Tower'),
|
||||
Team(id=1, name='Z-Force', headquarters='Sister Margaret’s Bar', heroes=[...])
|
||||
Team(id=1, name='Z-Force', headquarters='Sister Margaret's Bar', heroes=[...])
|
||||
])
|
||||
]
|
||||
```
|
||||
@@ -162,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 👆
|
||||
|
||||
@@ -170,14 +359,35 @@ In this case, we use the method `.remove()`, that takes an item and removes it f
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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`.
|
||||
|
||||
|
||||
@@ -12,16 +12,27 @@ Let's see the utilities to read a single row.
|
||||
|
||||
## Continue From Previous Code
|
||||
|
||||
We'll continue with the same examples we have been using in the previous chapters to create and select data and we'll keep udpating them.
|
||||
We'll continue with the same examples we have been using in the previous chapters to create and select data and we'll keep updating them.
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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.
|
||||
|
||||
@@ -29,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 👆
|
||||
|
||||
@@ -37,19 +62,46 @@ We have been iterating over the rows in a `result` object like:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -58,22 +110,38 @@ We can call the `.first()` method on the `results` object to get the first row:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This will return the first object in the `results` (if there was any).
|
||||
|
||||
That way, we don't have to deal with an iterable or a list.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Notice that `.first()` is a method of the `results` object, not of the `select()` statement.
|
||||
|
||||
///
|
||||
|
||||
Although this query would find two rows, by using `.first()` we get only the first row.
|
||||
|
||||
If we run it in the command line it would output:
|
||||
@@ -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,14 +193,27 @@ In that case, `.first()` will return `None`:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
In this case, as there's no hero with an age less than 25, `.first()` will return `None`.
|
||||
|
||||
@@ -151,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 👆
|
||||
|
||||
@@ -159,14 +268,27 @@ In that case, instead of `.first()` we can use `.one()`:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Here we know that there's only one `"Deadpond"`, and there shouldn't be any more than one.
|
||||
|
||||
@@ -222,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 👆
|
||||
|
||||
@@ -230,14 +366,27 @@ Of course, even if we don't duplicate the data, we could get the same error if w
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
That would find 2 rows, and would end up with the same error.
|
||||
|
||||
@@ -245,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 👆
|
||||
|
||||
@@ -253,14 +416,27 @@ And also, if we get no rows at all with `.one()`, it will also raise an error:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
In this case, as there are no heroes with an age less than 25, `.one()` will raise an error.
|
||||
|
||||
@@ -293,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 👆
|
||||
|
||||
@@ -301,14 +491,27 @@ Of course, with `.first()` and `.one()` you would also probably write all that i
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
That would result in the same as some examples above.
|
||||
|
||||
@@ -318,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 👆
|
||||
|
||||
@@ -326,14 +543,27 @@ You could do it the same way we have been doing with a `.where()` and then getti
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
That would work correctly, as expected. But there's a shorter version. 👇
|
||||
|
||||
@@ -341,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 👆
|
||||
|
||||
@@ -349,14 +593,27 @@ As selecting a single row by its Id column with the **primary key** is a common
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
`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()`.
|
||||
|
||||
@@ -385,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 👆
|
||||
|
||||
@@ -393,14 +664,27 @@ Hero: secret_name='Dive Wilson' age=None id=1 name='Deadpond'
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Running that will output:
|
||||
|
||||
|
||||
@@ -20,20 +20,63 @@ 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Read Data Objects
|
||||
|
||||
@@ -41,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 👆
|
||||
|
||||
@@ -49,22 +118,72 @@ As you already know how this works, I won't separate that in a select `statement
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
/// tip
|
||||
|
||||
!!! tip
|
||||
When writing your own code, this is probably the style you will use most often, as it's shorter, more convenient, and you still get all the power of autocompletion and inline errors.
|
||||
|
||||
///
|
||||
|
||||
## Print the Data
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -73,14 +192,35 @@ Now, let's print the current **Spider-Boy**, the current **Preventers** team, an
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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,22 +288,46 @@ Now let's update **Spider-Boy**, removing him from the team by setting `hero_spi
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
The first important thing is, we *haven't commited* the hero yet, so accessing the list of heroes would not trigger an automatic refresh.
|
||||
///
|
||||
|
||||
The first important thing is, we *haven't committed* the hero yet, so accessing the list of heroes would not trigger an automatic refresh.
|
||||
|
||||
But in our code, in this exact point in time, we already said that **Spider-Boy** is no longer part of the **Preventers**. 🔥
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
We could revert that later by not committing the **session**, but that's not what we are interested in here.
|
||||
|
||||
///
|
||||
|
||||
Here, at this point in the code, in memory, the code expects **Preventers** to *not include* **Spider-Boy**.
|
||||
|
||||
The output of printing `hero_spider_boy` without team is:
|
||||
@@ -158,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 👆
|
||||
|
||||
@@ -170,14 +402,35 @@ Now, if we commit it and print again:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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:
|
||||
|
||||
@@ -209,23 +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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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,20 +568,44 @@ And we can keep the rest of the code the same:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
/// tip
|
||||
|
||||
!!! tip
|
||||
This is the same section where we updated `hero_spider_boy.team` to `None` but we *haven't committed* that change yet.
|
||||
|
||||
The same section that caused a problem before.
|
||||
|
||||
///
|
||||
|
||||
## Review the Result
|
||||
|
||||
This time, **SQLModel** (actually SQLAlchemy) will be able to notice the change, and **automatically update the list of heroes** in the team, even before we commit.
|
||||
@@ -275,20 +629,63 @@ 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
The string in `back_populates` is the name of the attribute *in the other* model, that will reference *the current* model.
|
||||
|
||||
@@ -296,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 👆
|
||||
|
||||
@@ -304,14 +727,35 @@ So, in the class `Team`, we have an attribute `heroes` and we declare it with `R
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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,18 +797,42 @@ So, the string `"heroes"` refers to the attribute `heroes` in the class `Team`.
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
/// tip
|
||||
|
||||
!!! tip
|
||||
Each **relationship attribute** points to the other one, in the other model, using `back_populates`.
|
||||
|
||||
///
|
||||
|
||||
Although it's simple code, it can be confusing to think about 😵, because the same line has concepts related to both models in multiple places:
|
||||
|
||||
* Just by being in the **current** model, the line has something to do with the current model.
|
||||
@@ -356,6 +850,32 @@ So, `back_populates` would most probably be something like `"hero"` or `"heroes"
|
||||
|
||||
<img src="/img/tutorial/relationships/attributes/back-populates2.svg">
|
||||
|
||||
//// 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 👆
|
||||
|
||||
@@ -364,11 +884,32 @@ So, `back_populates` would most probably be something like `"hero"` or `"heroes"
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
@@ -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,14 +28,27 @@ Let's check the old code we used to create some heroes and teams:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
There are several things to **notice** here.
|
||||
|
||||
@@ -41,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 👆
|
||||
|
||||
@@ -49,14 +102,35 @@ Now let's do all that, but this time using the new, shiny `Relationship` attribu
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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`.
|
||||
|
||||
@@ -72,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 👆
|
||||
|
||||
@@ -84,14 +192,35 @@ The same way we could assign an integer with a `team.id` to a `hero.team_id`, we
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Create a Team with Heroes
|
||||
|
||||
@@ -99,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 👆
|
||||
|
||||
@@ -111,14 +274,35 @@ We could also create the `Hero` instances first, and then pass them in the `hero
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Here we create two heroes first, **Black Lion** and **Princess Sure-E**, and then we pass them in the `heroes` argument.
|
||||
|
||||
@@ -134,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 👆
|
||||
|
||||
@@ -146,14 +364,35 @@ Let's create some more heroes and add them to the `team_preventers.heroes` list
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ We currently have a `team` table:
|
||||
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret’s Bar</td>
|
||||
<td>2</td><td>Z-Force</td><td>Sister Margaret's Bar</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -41,20 +41,45 @@ 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This is a **plain field** like all the others, all representing a **column in the table**.
|
||||
|
||||
@@ -62,37 +87,123 @@ 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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## What Are These Relationship Attributes
|
||||
|
||||
@@ -115,9 +226,7 @@ This means that this attribute could be `None`, or it could be a full `Team` obj
|
||||
|
||||
This is because the related **`team_id` could also be `None`** (or `NULL` in the database).
|
||||
|
||||
If it was required for a `Hero` instance to belong to a `Team`, then the `team_id` would be `int` instead of `Optional[int]`.
|
||||
|
||||
And the `team` attribute would be a `Team` instead of `Optional[Team]`.
|
||||
If it was required for a `Hero` instance to belong to a `Team`, then the `team_id` would be `int` instead of `Optional[int]`, its `Field` would be `Field(foreign_key="team.id")` instead of `Field(default=None, foreign_key="team.id")` and the `team` attribute would be a `Team` instead of `Optional[Team]`.
|
||||
|
||||
## Relationship Attributes With Lists
|
||||
|
||||
@@ -125,11 +234,14 @@ And in the `Team` class, the `heroes` attribute is annotated as a list of `Hero`
|
||||
|
||||
**SQLModel** (actually SQLAlchemy) is smart enough to know that the relationship is established by the `team_id`, as that's the foreign key that points from the `hero` table to the `team` table, so we don't have to specify that explicitly here.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
There's a couple of things we'll check again in some of the next chapters, about the `List["Hero"]` and the `back_populates`.
|
||||
|
||||
But for now, let's first see how to use these relationship attributes.
|
||||
|
||||
///
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now let's see some real examples of how to use these new **relationship attributes** in the next chapters. ✨
|
||||
|
||||
@@ -6,9 +6,12 @@ And then we read the data together with `select()` and using `.where()` or `.joi
|
||||
|
||||
Now we will see how to use **Relationship Attributes**, an extra feature of **SQLModel** (and SQLAlchemy) to work with the data in the database in way much more familiar way, and closer to normal Python code.
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
When I say "**relationship**" I mean the standard dictionary term, of data related to other data.
|
||||
|
||||
I'm not using the term "**relation**" that is the technical, academical, SQL term for a single table.
|
||||
|
||||
///
|
||||
|
||||
And using those **relationship attributes** is where a tool like **SQLModel** really shines. ✨
|
||||
|
||||
@@ -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,14 +52,35 @@ First, add a function `select_heroes()` where we get a hero to start working wit
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Select the Related Team - Old Way
|
||||
|
||||
@@ -33,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 👆
|
||||
|
||||
@@ -41,21 +122,76 @@ With what we have learned **up to now**, we could use a `select()` statement, th
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Get Relationship Team - New Way
|
||||
|
||||
But now that we have the **relationship attributes**, we can just access them, and **SQLModel** (actually SQLAlchemy) will go and fetch the correspoinding data from the database, and make it available in the attribute. ✨
|
||||
But now that we have the **relationship attributes**, we can just access them, and **SQLModel** (actually SQLAlchemy) will go and fetch the corresponding data from the database, and make it available in the attribute. ✨
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -68,24 +204,74 @@ So, the highlighted block above, has the same results as the block below:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
/// tip
|
||||
|
||||
!!! tip
|
||||
The automatic data fetching will work as long as the starting object (in this case the `Hero`) is associated with an **open** session.
|
||||
|
||||
For example, here, **inside** a `with` block with a `Session` object.
|
||||
|
||||
///
|
||||
|
||||
## Get a List of Relationship Objects
|
||||
|
||||
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,14 +280,35 @@ And the same way, when we are working on the **many** side of the **one-to-many*
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
That would print a list with all the heroes in the Preventers team:
|
||||
|
||||
|
||||
@@ -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,17 +42,64 @@ We can remove the relationship by setting it to `None`, the same as with the `te
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -35,14 +108,35 @@ And of course, we should remember to add this `update_heroes()` function to `mai
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Recap
|
||||
|
||||
|
||||
@@ -2,20 +2,63 @@
|
||||
|
||||
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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
What's that about? Can't we just write it normally as `List[Hero]`?
|
||||
|
||||
@@ -29,5 +72,8 @@ And of course, **SQLModel** can also understand it in the string correctly. ✨
|
||||
|
||||
That is actually part of Python, it's the current official solution to handle it.
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
There's a lot of work going on in Python itself to make that simpler and more intuitive, and find ways to make it possible to not wrap the class in a string.
|
||||
|
||||
///
|
||||
|
||||
@@ -23,14 +23,25 @@ Things are getting more exciting! Let's now see how to read data from the databa
|
||||
|
||||
Let's continue from the last code we used to create some data.
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
We are creating a **SQLModel** `Hero` class model and creating some records.
|
||||
|
||||
@@ -79,13 +90,16 @@ You can try that out in **DB Browser for SQLite**:
|
||||
|
||||
<img class="shadow" src="/img/tutorial/select/image01.png">
|
||||
|
||||
!!! warning
|
||||
/// warning
|
||||
|
||||
Here we are getting all the rows.
|
||||
|
||||
If you have thousands of rows, that could be expensive to compute for the database.
|
||||
|
||||
You would normally want to filter the rows to receive only the ones you want. But we'll learn about that later in the next chapter.
|
||||
|
||||
///
|
||||
|
||||
### A SQL Shortcut
|
||||
|
||||
If we want to get all the columns like in this case above, in SQL there's a shortcut, instead of specifying each of the column names we could write a `*`:
|
||||
@@ -164,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 👆
|
||||
|
||||
@@ -172,14 +200,27 @@ We will start with that in a new function `select_heroes()`:
|
||||
# More code here later 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Create a `select` Statement
|
||||
|
||||
@@ -187,23 +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 ommitted 👇
|
||||
# More code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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,14 +296,27 @@ And then we will use it to create a `SELECT` statement in Python code:
|
||||
# More code here later 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
It's a very simple line of code that conveys a lot of information:
|
||||
|
||||
@@ -240,15 +335,32 @@ We pass the class model `Hero` to the `select()` function. And that tells it tha
|
||||
|
||||
And notice that in the `select()` function we don't explicitly specify the `FROM` part. It is already obvious to **SQLModel** (actually to SQLAlchemy) that we want to select `FROM` the table `hero`, because that's the one associated with the `Hero` class model.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
The value of the `statement` returned by `select()` is a special object that allows us to do other things.
|
||||
|
||||
I'll tell you about that in the next chapters.
|
||||
|
||||
///
|
||||
|
||||
## Execute the Statement
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -257,14 +369,27 @@ Now that we have the `select` statement, we can execute it with the **session**:
|
||||
# More code here later 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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.
|
||||
|
||||
@@ -302,6 +427,20 @@ The `results` object is an <abbr title="Something that can be used in a for loop
|
||||
|
||||
Now we can put it in a `for` loop and print each one of the heroes:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="7-8"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/select/tutorial001_py310.py[ln:34-39]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="7-8"
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -310,14 +449,27 @@ Now we can put it in a `for` loop and print each one of the heroes:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This will print the output:
|
||||
|
||||
@@ -331,6 +483,20 @@ id=3 name='Rusty-Man' age=48 secret_name='Tommy Sharp'
|
||||
|
||||
Now include a call to `select_heroes()` in the `main()` function so that it is executed when we run the program from the command line:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="14"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/select/tutorial001_py310.py[ln:34-45]!}
|
||||
|
||||
# More code here later 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="14"
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -339,14 +505,27 @@ Now include a call to `select_heroes()` in the `main()` function so that it is e
|
||||
# More code here later 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Review The Code
|
||||
|
||||
@@ -354,15 +533,32 @@ Great, you're now being able to read the data from the database! 🎉
|
||||
|
||||
Let's review the code up to this point:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```{ .python .annotate }
|
||||
{!./docs_src/tutorial/select/tutorial002_py310.py!}
|
||||
```
|
||||
|
||||
{!./docs_src/tutorial/select/annotations/en/tutorial002.md!}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```{ .python .annotate }
|
||||
{!./docs_src/tutorial/select/tutorial002.py!}
|
||||
```
|
||||
|
||||
{!./docs_src/tutorial/select/annotations/en/tutorial002.md!}
|
||||
|
||||
!!! tip
|
||||
////
|
||||
|
||||
/// tip
|
||||
|
||||
Check out the number bubbles to see what is done by each line of code.
|
||||
|
||||
///
|
||||
|
||||
Here it starts to become more evident why we should have a single **engine** for the whole application, but different **sessions** for each group of operations.
|
||||
|
||||
This new session we created uses the *same* **engine**, but it's a new and independent **session**.
|
||||
@@ -373,11 +569,14 @@ And the second section reading data from the database could be in another functi
|
||||
|
||||
So, both sections could be in **different places** and would need their own sessions.
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
To be fair, in this example all that code could actually share the same **session**, there's actually no need to have two here.
|
||||
|
||||
But it allows me to show you how they could be separated and to reinforce the idea that you should have **one engine** per application, and **multiple sessions**, one per each group of operations.
|
||||
|
||||
///
|
||||
|
||||
## Get a List of `Hero` Objects
|
||||
|
||||
Up to now we are using the `results` to iterate over them.
|
||||
@@ -386,6 +585,20 @@ But for different reasons you might want to have the full **list of `Hero`** obj
|
||||
|
||||
The special `results` object also has a method `results.all()` that returns a list with all the objects:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="7"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/select/tutorial003_py310.py[ln:34-39]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="7"
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -394,14 +607,27 @@ The special `results` object also has a method `results.all()` that returns a li
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
With this now we have all the heroes in a list in the `heroes` variable.
|
||||
|
||||
@@ -415,15 +641,32 @@ After printing it, we would see something like:
|
||||
]
|
||||
```
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
It would actually look more compact, I'm formatting it a bit for you to see that it is actually a list with all the data.
|
||||
|
||||
///
|
||||
|
||||
## Compact Version
|
||||
|
||||
I have been creating several variables to be able to explain to you what each thing is doing.
|
||||
|
||||
But knowing what is each object and what it is all doing, we can simplify it a bit and put it in a more compact form:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="5"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/select/tutorial004_py310.py[ln:34-37]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="5"
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -432,14 +675,27 @@ But knowing what is each object and what it is all doing, we can simplify it a b
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/select/tutorial004_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/select/tutorial004.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Here we are putting it all on a single line, you will probably put the select statements in a single line like this more often.
|
||||
|
||||
@@ -461,9 +717,12 @@ SQLAchemy also has it's own `select`, and SQLModel's `select` uses SQLAlchemy's
|
||||
|
||||
But SQLModel's version does a lot of **tricks** with type annotations to make sure you get the best **editor support** possible, no matter if you use **VS Code**, **PyCharm**, or something else. ✨
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
There was a lot of work and research, with different versions of the internal code, to improve this as much as possible. 🤓
|
||||
|
||||
///
|
||||
|
||||
### SQLModel's `session.exec`
|
||||
|
||||
📢 This is one to pay special attention to.
|
||||
@@ -472,7 +731,7 @@ SQLAlchemy's own `Session` has a method `session.execute()`. It doesn't have a `
|
||||
|
||||
If you see SQLAlchemy tutorials, they will always use `session.execute()`.
|
||||
|
||||
**SQLModel**'s own `Session` inherits directly from SQLAlchemy's `Session`, and adds this additonal method `session.exec()`. Underneath, it uses the same `session.execute()`.
|
||||
**SQLModel**'s own `Session` inherits directly from SQLAlchemy's `Session`, and adds this additional method `session.exec()`. Underneath, it uses the same `session.execute()`.
|
||||
|
||||
But `session.exec()` does several **tricks** combined with the tricks in `session()` to give you the **best editor support**, with **autocompletion** and **inline errors** everywhere, even after getting data from a select. ✨
|
||||
|
||||
@@ -492,11 +751,14 @@ On top of that, **SQLModel**'s `session.exec()` also does some tricks to reduce
|
||||
|
||||
But SQLModel's `Session` still has access to `session.execute()` too.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Your editor will give you autocompletion for both `session.exec()` and `session.execute()`.
|
||||
|
||||
📢 Remember to **always use `session.exec()`** to get the best editor support and developer experience.
|
||||
|
||||
///
|
||||
|
||||
### Caveats of **SQLModel** Flavor
|
||||
|
||||
SQLModel is designed to have the best **developer experience** in a narrow set of **very common use cases**. ✨
|
||||
|
||||
@@ -6,14 +6,25 @@ Now let's see how to update data using **SQLModel**.
|
||||
|
||||
As before, we'll continue from where we left off with the previous code.
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Remember to remove the `database.db` file before running the examples to get the same results.
|
||||
|
||||
@@ -41,13 +52,16 @@ And the second part, with the `WHERE`, defines to which rows it should apply tha
|
||||
|
||||
In this case, as we only have one hero with the name `"Spider-Boy"`, it will only apply the update in that row.
|
||||
|
||||
!!! info
|
||||
/// info
|
||||
|
||||
Notice that in the `UPDATE` the single equals sign (`=`) means **assignment**, setting a column to some value.
|
||||
|
||||
And in the `WHERE` the same single equals sign (`=`) is used for **comparison** between two values, to find rows that match.
|
||||
|
||||
This is in contrast to Python and most programming languages, where a single equals sign (`=`) is used for assignment, and two equal signs (`==`) are used for comparisons.
|
||||
|
||||
///
|
||||
|
||||
You can try that in **DB Browser for SQLite**:
|
||||
|
||||
<img class="shadow" src="/img/tutorial/update/image01.png">
|
||||
@@ -69,7 +83,8 @@ After that update, the data in the table will look like this, with the new age f
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
It will probably be more common to find the row to update by `id`, for example:
|
||||
|
||||
```SQL
|
||||
@@ -80,6 +95,8 @@ After that update, the data in the table will look like this, with the new age f
|
||||
|
||||
But in the example above I used `name` to make it more intuitive.
|
||||
|
||||
///
|
||||
|
||||
Now let's do the same update in code, with **SQLModel**.
|
||||
|
||||
To get the same results, delete the `database.db` file before running the examples.
|
||||
@@ -88,6 +105,20 @@ To get the same results, delete the `database.db` file before running the exampl
|
||||
|
||||
We'll start by selecting the hero `"Spider-Boy"`, this is the one we will update:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="5"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/update/tutorial001_py310.py[ln:42-47]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="5"
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -96,31 +127,69 @@ We'll start by selecting the hero `"Spider-Boy"`, this is the one we will update
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/update/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/update/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Let's not forget to add that `update_heroes()` function to the `main()` function so that we call it when executing the program from the command line:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="6"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/update/tutorial001_py310.py[ln:56-63]!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="6"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/update/tutorial001.py[ln:58-65]!}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/update/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/update/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Up to that point, running that in the command line will output:
|
||||
|
||||
@@ -143,15 +212,32 @@ Hero: name='Spider-Boy' secret_name='Pedro Parqueador' age=None id=2
|
||||
|
||||
</div>
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Notice that by this point, the hero still doesn't have an age.
|
||||
|
||||
///
|
||||
|
||||
## Set a Field Value
|
||||
|
||||
Now that you have a `hero` object, you can simply set the value of the field (the attribute representing a column) that you want.
|
||||
|
||||
In this case, we will set the `age` to `16`:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="10"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/update/tutorial001_py310.py[ln:42-49]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="10"
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -160,14 +246,27 @@ In this case, we will set the `age` to `16`:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/update/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/update/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Add the Hero to the Session
|
||||
|
||||
@@ -175,6 +274,20 @@ Now that the hero object in memory has a change, in this case a new value for th
|
||||
|
||||
This is the same we did when creating new hero instances:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="11"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/update/tutorial001_py310.py[ln:42-50]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="11"
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -183,14 +296,27 @@ This is the same we did when creating new hero instances:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/update/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/update/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Commit the Session
|
||||
|
||||
@@ -198,6 +324,20 @@ To save the current changes in the session, **commit** it.
|
||||
|
||||
This will save the updated hero in the database:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="12"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/update/tutorial001_py310.py[ln:42-51]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="12"
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -206,14 +346,27 @@ This will save the updated hero in the database:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/update/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/update/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
It will also save anything else that was added to the session.
|
||||
|
||||
@@ -246,6 +399,20 @@ The data in the object would be automatically refreshed if we accessed an attrib
|
||||
|
||||
But in this example we are not accessing any attribute, we will only print the object. And we also want to be explicit, so we will `.refresh()` the object directly:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="13"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/update/tutorial001_py310.py[ln:42-52]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="13"
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -254,14 +421,27 @@ But in this example we are not accessing any attribute, we will only print the o
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/update/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/update/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This refresh will trigger the same SQL query that would be automatically triggered by accessing an attribute. So it will generate this output:
|
||||
|
||||
@@ -287,6 +467,20 @@ INFO Engine [generated in 0.00018s] (2,)
|
||||
|
||||
Now we can just print the hero:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="14"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/update/tutorial001_py310.py[ln:42-53]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="14"
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -295,14 +489,27 @@ Now we can just print the hero:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/update/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/update/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Because we refreshed it right after updating it, it has fresh data, including the new `age` we just updated.
|
||||
|
||||
@@ -327,21 +534,54 @@ Updated hero: name='Spider-Boy' secret_name='Pedro Parqueador' age=16 id=2
|
||||
|
||||
Now let's review all that code:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```{ .python .annotate hl_lines="42-53" }
|
||||
{!./docs_src/tutorial/update/tutorial002_py310.py!}
|
||||
```
|
||||
|
||||
{!./docs_src/tutorial/update/annotations/en/tutorial002.md!}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```{ .python .annotate hl_lines="44-55" }
|
||||
{!./docs_src/tutorial/update/tutorial002.py!}
|
||||
```
|
||||
|
||||
{!./docs_src/tutorial/update/annotations/en/tutorial002.md!}
|
||||
|
||||
!!! tip
|
||||
////
|
||||
|
||||
/// tip
|
||||
|
||||
Check out the number bubbles to see what is done by each line of code.
|
||||
|
||||
///
|
||||
|
||||
## Multiple Updates
|
||||
|
||||
The update process with **SQLModel** is more or less the same as with creating new objects, you add them to the session, and then commit them.
|
||||
|
||||
This also means that you can update several fields (attributes, columns) at once, and you can also update several objects (heroes) at once:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```{ .python .annotate hl_lines="15-17 19-21 23" }
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/update/tutorial004_py310.py[ln:42-68]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
{!./docs_src/tutorial/update/annotations/en/tutorial004.md!}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```{ .python .annotate hl_lines="15-17 19-21 23" }
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -352,18 +592,34 @@ This also means that you can update several fields (attributes, columns) at once
|
||||
|
||||
{!./docs_src/tutorial/update/annotations/en/tutorial004.md!}
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/update/tutorial004_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/update/tutorial004.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
/// tip
|
||||
|
||||
!!! tip
|
||||
Review what each line does by clicking each number bubble in the code. 👆
|
||||
|
||||
///
|
||||
|
||||
## Recap
|
||||
|
||||
Update **SQLModel** objects just as you would with other Python objects. 🐍
|
||||
|
||||
@@ -31,14 +31,25 @@ We'll continue with the same examples we have been using in the previous chapter
|
||||
|
||||
And now we will update `select_heroes()` to filter the data.
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="36-41"
|
||||
{!./docs_src/tutorial/select/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="36-41"
|
||||
{!./docs_src/tutorial/select/tutorial001.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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.
|
||||
|
||||
@@ -81,11 +92,14 @@ Then the database will bring a table like this:
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Even if the result is only one row, the database always returns a **table**.
|
||||
|
||||
In this case, a table with only one row.
|
||||
|
||||
///
|
||||
|
||||
You can try that out in **DB Browser for SQLite**:
|
||||
|
||||
<img class="shadow" src="/img/tutorial/where/image01.png">
|
||||
@@ -187,6 +201,20 @@ Let's review some of the code we used to read data with **SQLModel**.
|
||||
|
||||
We care specially about the **select** statement:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="5"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/select/tutorial001_py310.py[ln:34-39]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="5"
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -195,18 +223,45 @@ We care specially about the **select** statement:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
## Filter Rows Using `WHERE` with **SQLModel**
|
||||
|
||||
Now, the same way that we add `WHERE` to a SQL statement to filter rows, we can add a `.where()` to a **SQLModel** `select()` statment to filter rows, which will filter the objects returned:
|
||||
Now, the same way that we add `WHERE` to a SQL statement to filter rows, we can add a `.where()` to a **SQLModel** `select()` statement to filter rows, which will filter the objects returned:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="5"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/where/tutorial001_py310.py[ln:34-39]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="5"
|
||||
# Code above omitted 👆
|
||||
@@ -216,14 +271,27 @@ Now, the same way that we add `WHERE` to a SQL statement to filter rows, we can
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
It's a very small change, but it's packed of details. Let's explore them.
|
||||
|
||||
@@ -268,11 +336,14 @@ So, what's happening there?
|
||||
|
||||
In the example above we are using two equal signs (`==`). That's called the "**equality operator**".
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
An **operator** is just a symbol that is put beside one value or in the middle of two values to do something with them.
|
||||
|
||||
`==` is called the **equality** operator because it checks if two things are **equal**.
|
||||
|
||||
///
|
||||
|
||||
When writing Python, if you write something using this equality operator (`==`) like:
|
||||
|
||||
```Python
|
||||
@@ -291,9 +362,12 @@ True
|
||||
False
|
||||
```
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
`<`, `>`, `==`, `>=`, `<=`, and `!=` are all **operators** used for **comparisons**.
|
||||
|
||||
///
|
||||
|
||||
But SQLAlchemy adds some magic to the columns/fields in a **model class** to make those Python comparisons have super powers.
|
||||
|
||||
So, if you write something like:
|
||||
@@ -311,7 +385,7 @@ Instead, it results in a special type of object. If you tried that in an interac
|
||||
<sqlalchemy.sql.elements.BinaryExpression object at 0x7f4aec0d6c90>
|
||||
```
|
||||
|
||||
So, that result value is an **expession** object. 💡
|
||||
So, that result value is an **expression** object. 💡
|
||||
|
||||
And `.where()` takes one (or more) of these **expression** objects to update the SQL statement.
|
||||
|
||||
@@ -421,7 +495,7 @@ Of course, the keyword arguments would have been a bit shorter.
|
||||
|
||||
But with the **expressions** your editor can help you a lot with autocompletion and inline error checks. ✨
|
||||
|
||||
Let me give you an example. Let's imagine that keword arguments were supported in SQLModel and you wanted to filter using the secret identity of Spider-Boy.
|
||||
Let me give you an example. Let's imagine that keyword arguments were supported in SQLModel and you wanted to filter using the secret identity of Spider-Boy.
|
||||
|
||||
You could write:
|
||||
|
||||
@@ -436,7 +510,7 @@ Maybe your code could even run and seem like it's all fine, and then some months
|
||||
|
||||
And maybe finally you would realize that we wrote the code using `secret_identity` which is not a column in the table. We should have written `secret_name` instead.
|
||||
|
||||
Now, with the the expressions, your editor would show you an error right away if you tried this:
|
||||
Now, with the expressions, your editor would show you an error right away if you tried this:
|
||||
|
||||
```Python
|
||||
# Expression ✨
|
||||
@@ -451,15 +525,32 @@ select(Hero).where(Hero.secret_name == "Pedro Parqueador")
|
||||
|
||||
I think that alone, having better editor support, autocompletion, and inline errors, is enough to make it worth having expressions instead of keyword arguments. ✨
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
**Expressions** also provide more features for other types of comparisons, shown down below. 👇
|
||||
|
||||
///
|
||||
|
||||
## Exec the Statement
|
||||
|
||||
Now that we know how `.where()` works, let's finish the code.
|
||||
|
||||
It's actually the same as in previous chapters for selecting data:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="6-8"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/where/tutorial001_py310.py[ln:34-39]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="6-8"
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -468,14 +559,27 @@ It's actually the same as in previous chapters for selecting data:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
We take that statement, that now includes a `WHERE`, and we `exec()` it to get the results.
|
||||
|
||||
@@ -502,13 +606,16 @@ secret_name='Dive Wilson' age=None id=1 name='Deadpond'
|
||||
</div>
|
||||
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
The `results` object is an iterable to be used in a `for` loop.
|
||||
|
||||
Even if we got only one row, we iterate over that `results` object. Just as if it was a list of one element.
|
||||
|
||||
We'll see other ways to get the data later.
|
||||
|
||||
///
|
||||
|
||||
## Other Comparisons
|
||||
|
||||
Here's another great advantage of these special **expressions** passed to `.where()`.
|
||||
@@ -521,6 +628,20 @@ But we can use other standard Python comparisons. ✨
|
||||
|
||||
We could get the rows where a column is **not** equal to a value using `!=`:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="5"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/where/tutorial002_py310.py[ln:34-39]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="5"
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -529,14 +650,27 @@ We could get the rows where a column is **not** equal to a value using `!=`:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// details | 👀 Full file preview
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/where/tutorial002_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python
|
||||
{!./docs_src/tutorial/where/tutorial002.py!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
That would output:
|
||||
|
||||
@@ -549,6 +683,20 @@ secret_name='Tommy Sharp' age=48 id=3 name='Rusty-Man'
|
||||
|
||||
Let's update the function `create_heroes()` and add some more rows to make the next comparison examples clearer:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="4-10 13-19"
|
||||
# Code above omitted 👆
|
||||
|
||||
{!./docs_src/tutorial/where/tutorial003_py310.py[ln:21-39]!}
|
||||
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.7+
|
||||
|
||||
```Python hl_lines="4-10 13-19"
|
||||
# Code above omitted 👆
|
||||
|
||||
@@ -557,14 +705,27 @@ Let's update the function `create_heroes()` and add some more rows to make the n
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Now that we have several heroes with different ages, it's gonna be more obvious what the next comparisons do.
|
||||
|
||||
@@ -572,6 +733,20 @@ Now that we have several heroes with different ages, it's gonna be more obvious
|
||||
|
||||
Now let's use `>` 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 👆
|
||||
|
||||
@@ -580,14 +755,27 @@ Now let's use `>` to get the rows where a column is **more than** a value:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
That would output:
|
||||
|
||||
@@ -597,13 +785,30 @@ age=36 id=6 name='Dr. Weird' secret_name='Steve Weird'
|
||||
age=93 id=7 name='Captain North America' secret_name='Esteban Rogelios'
|
||||
```
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
Notice that it didn't select `Black Lion`, because the age is not *strictly* greater than `35`.
|
||||
|
||||
///
|
||||
|
||||
### More Than or Equal
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -612,14 +817,27 @@ Let's do that again, but with `>=` to get the rows where a column is **more than
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Because we are using `>=`, the age `35` will be included in the output:
|
||||
|
||||
@@ -630,13 +848,30 @@ age=36 id=6 name='Dr. Weird' secret_name='Steve Weird'
|
||||
age=93 id=7 name='Captain North America' secret_name='Esteban Rogelios'
|
||||
```
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
This time we got `Black Lion` too because although the age is not *strictly* greater than `35`it is *equal* to `35`.
|
||||
|
||||
///
|
||||
|
||||
### Less Than
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -645,14 +880,27 @@ Similarly, we can use `<` to get the rows where a column is **less than** a valu
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
And we get the younger one with an age in the database:
|
||||
|
||||
@@ -660,13 +908,30 @@ And we get the younger one with an age in the database:
|
||||
age=32 id=4 name='Tarantula' secret_name='Natalia Roman-on'
|
||||
```
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
We could imagine that **Spider-Boy** is even **younger**. But because we don't know the age, it is `NULL` in the database (`None` in Python), it doesn't match any of these age comparisons with numbers.
|
||||
|
||||
///
|
||||
|
||||
### Less Than or Equal
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -675,14 +940,27 @@ Finally, we can use `<=` to get the rows where a column is **less than or equal*
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
And we get the younger ones, `35` and below:
|
||||
|
||||
@@ -691,10 +969,13 @@ age=32 id=4 name='Tarantula' secret_name='Natalia Roman-on'
|
||||
age=35 id=5 name='Black Lion' secret_name='Trevor Challa'
|
||||
```
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
We get `Black Lion` here too because although the age is not *strictly* less than `35` it is *equal* to `35`.
|
||||
|
||||
### Benefits of Expresions
|
||||
///
|
||||
|
||||
### Benefits of Expressions
|
||||
|
||||
Here's a good moment to see that being able to use these pure Python expressions instead of keyword arguments can help a lot. ✨
|
||||
|
||||
@@ -704,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 👆
|
||||
|
||||
@@ -712,14 +1007,27 @@ Because `.where()` returns the same special select object back, we can add more
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This will select the rows `WHERE` the `age` is **greater than or equal** to `35`, `AND` also the `age` is **less than** `40`.
|
||||
|
||||
@@ -748,7 +1056,7 @@ FROM hero
|
||||
WHERE hero.age >= ? AND hero.age < ?
|
||||
INFO Engine [no key 0.00014s] (35, 40)
|
||||
|
||||
// The two heros printed
|
||||
// The two heroes printed
|
||||
age=35 id=5 name='Black Lion' secret_name='Trevor Challa'
|
||||
age=36 id=6 name='Dr. Weird' secret_name='Steve Weird'
|
||||
|
||||
@@ -760,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 👆
|
||||
|
||||
@@ -768,14 +1090,27 @@ As an alternative to using multiple `.where()` we can also pass several expressi
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
This is the same as the above, and will result in the same output with the two heroes:
|
||||
|
||||
@@ -792,25 +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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -819,14 +1193,27 @@ For example, here we select the heroes that are the youngest OR the oldest:
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
When we run it, this generates the output:
|
||||
|
||||
@@ -877,23 +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>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
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 👆
|
||||
|
||||
@@ -902,14 +1328,27 @@ And then put the **class attribute** inside `col()` when using it in a `.where()
|
||||
# Code below omitted 👇
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>👀 Full file preview</summary>
|
||||
////
|
||||
|
||||
/// 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!}
|
||||
```
|
||||
|
||||
</details>
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
So, now the comparison is not:
|
||||
|
||||
@@ -925,11 +1364,14 @@ col(Hero.age) > 35
|
||||
|
||||
And with that the editor knows this code is actually fine, because this is a special **SQLModel** column.
|
||||
|
||||
!!! tip
|
||||
/// tip
|
||||
|
||||
That `col()` will come handy later, giving autocompletion to several other things we can do with these special **class attributes** for columns.
|
||||
|
||||
But we'll get there later.
|
||||
|
||||
///
|
||||
|
||||
## Recap
|
||||
|
||||
You can use `.where()` with powerful expressions using **SQLModel** columns (the special class attributes) to filter the rows that you want. 🚀
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import condecimal
|
||||
from sqlmodel import Field, Session, SQLModel, create_engine, select
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ class Hero(SQLModel, table=True):
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: Optional[int] = Field(default=None, index=True)
|
||||
money: condecimal(max_digits=5, decimal_places=3) = Field(default=0)
|
||||
money: Decimal = Field(default=0, max_digits=5, decimal_places=3)
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
|
||||
60
docs_src/advanced/decimal/tutorial001_py310.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from sqlmodel import Field, Session, SQLModel, create_engine, select
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: int | None = Field(default=None, index=True)
|
||||
money: Decimal = Field(default=0, max_digits=5, decimal_places=3)
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
|
||||
def create_heroes():
|
||||
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson", money=1.1)
|
||||
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador", money=0.001)
|
||||
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48, money=2.2)
|
||||
|
||||
with Session(engine) as session:
|
||||
session.add(hero_1)
|
||||
session.add(hero_2)
|
||||
session.add(hero_3)
|
||||
|
||||
session.commit()
|
||||
|
||||
|
||||
def select_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Hero).where(Hero.name == "Deadpond")
|
||||
results = session.exec(statement)
|
||||
hero_1 = results.one()
|
||||
print("Hero 1:", hero_1)
|
||||
|
||||
statement = select(Hero).where(Hero.name == "Rusty-Man")
|
||||
results = session.exec(statement)
|
||||
hero_2 = results.one()
|
||||
print("Hero 2:", hero_2)
|
||||
|
||||
total_money = hero_1.money + hero_2.money
|
||||
print(f"Total money: {total_money}")
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
select_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -157,7 +157,7 @@
|
||||
Hero 3:
|
||||
```
|
||||
|
||||
21. Print the line `"After commiting the session, show IDs"`.
|
||||
21. Print the line `"After committing the session, show IDs"`.
|
||||
|
||||
Generates the output:
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
from sqlmodel import Field, Session, SQLModel, create_engine
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str
|
||||
secret_name: str
|
||||
age: int | None = None
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
|
||||
def create_heroes():
|
||||
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
|
||||
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
|
||||
|
||||
print("Before interacting with the database")
|
||||
print("Hero 1:", hero_1)
|
||||
print("Hero 2:", hero_2)
|
||||
print("Hero 3:", hero_3)
|
||||
|
||||
with Session(engine) as session:
|
||||
session.add(hero_1)
|
||||
session.add(hero_2)
|
||||
session.add(hero_3)
|
||||
|
||||
print("After adding to the session")
|
||||
print("Hero 1:", hero_1)
|
||||
print("Hero 2:", hero_2)
|
||||
print("Hero 3:", hero_3)
|
||||
|
||||
session.commit()
|
||||
|
||||
print("After committing the session")
|
||||
print("Hero 1:", hero_1)
|
||||
print("Hero 2:", hero_2)
|
||||
print("Hero 3:", hero_3)
|
||||
|
||||
print("After committing the session, show IDs")
|
||||
print("Hero 1 ID:", hero_1.id)
|
||||
print("Hero 2 ID:", hero_2.id)
|
||||
print("Hero 3 ID:", hero_3.id)
|
||||
|
||||
print("After committing the session, show names")
|
||||
print("Hero 1 name:", hero_1.name)
|
||||
print("Hero 2 name:", hero_2.name)
|
||||
print("Hero 3 name:", hero_3.name)
|
||||
|
||||
session.refresh(hero_1)
|
||||
session.refresh(hero_2)
|
||||
session.refresh(hero_3)
|
||||
|
||||
print("After refreshing the heroes")
|
||||
print("Hero 1:", hero_1)
|
||||
print("Hero 2:", hero_2)
|
||||
print("Hero 3:", hero_3)
|
||||
|
||||
print("After the session closes")
|
||||
print("Hero 1:", hero_1)
|
||||
print("Hero 2:", hero_2)
|
||||
print("Hero 3:", hero_3)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -21,56 +21,56 @@ def create_db_and_tables():
|
||||
|
||||
|
||||
def create_heroes():
|
||||
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson") # (1)
|
||||
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador") # (2)
|
||||
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48) # (3)
|
||||
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson") # (1)!
|
||||
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador") # (2)!
|
||||
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48) # (3)!
|
||||
|
||||
print("Before interacting with the database") # (4)
|
||||
print("Hero 1:", hero_1) # (5)
|
||||
print("Hero 2:", hero_2) # (6)
|
||||
print("Hero 3:", hero_3) # (7)
|
||||
print("Before interacting with the database") # (4)!
|
||||
print("Hero 1:", hero_1) # (5)!
|
||||
print("Hero 2:", hero_2) # (6)!
|
||||
print("Hero 3:", hero_3) # (7)!
|
||||
|
||||
with Session(engine) as session: # (8)
|
||||
session.add(hero_1) # (9)
|
||||
session.add(hero_2) # (10)
|
||||
session.add(hero_3) # (11)
|
||||
with Session(engine) as session: # (8)!
|
||||
session.add(hero_1) # (9)!
|
||||
session.add(hero_2) # (10)!
|
||||
session.add(hero_3) # (11)!
|
||||
|
||||
print("After adding to the session") # (12)
|
||||
print("Hero 1:", hero_1) # (13)
|
||||
print("Hero 2:", hero_2) # (14)
|
||||
print("Hero 3:", hero_3) # (15)
|
||||
print("After adding to the session") # (12)!
|
||||
print("Hero 1:", hero_1) # (13)!
|
||||
print("Hero 2:", hero_2) # (14)!
|
||||
print("Hero 3:", hero_3) # (15)!
|
||||
|
||||
session.commit() # (16)
|
||||
session.commit() # (16)!
|
||||
|
||||
print("After committing the session") # (17)
|
||||
print("Hero 1:", hero_1) # (18)
|
||||
print("Hero 2:", hero_2) # (19)
|
||||
print("Hero 3:", hero_3) # (20)
|
||||
print("After committing the session") # (17)!
|
||||
print("Hero 1:", hero_1) # (18)!
|
||||
print("Hero 2:", hero_2) # (19)!
|
||||
print("Hero 3:", hero_3) # (20)!
|
||||
|
||||
print("After committing the session, show IDs") # (21)
|
||||
print("Hero 1 ID:", hero_1.id) # (22)
|
||||
print("Hero 2 ID:", hero_2.id) # (23)
|
||||
print("Hero 3 ID:", hero_3.id) # (24)
|
||||
print("After committing the session, show IDs") # (21)!
|
||||
print("Hero 1 ID:", hero_1.id) # (22)!
|
||||
print("Hero 2 ID:", hero_2.id) # (23)!
|
||||
print("Hero 3 ID:", hero_3.id) # (24)!
|
||||
|
||||
print("After committing the session, show names") # (25)
|
||||
print("Hero 1 name:", hero_1.name) # (26)
|
||||
print("Hero 2 name:", hero_2.name) # (27)
|
||||
print("Hero 3 name:", hero_3.name) # (28)
|
||||
print("After committing the session, show names") # (25)!
|
||||
print("Hero 1 name:", hero_1.name) # (26)!
|
||||
print("Hero 2 name:", hero_2.name) # (27)!
|
||||
print("Hero 3 name:", hero_3.name) # (28)!
|
||||
|
||||
session.refresh(hero_1) # (29)
|
||||
session.refresh(hero_2) # (30)
|
||||
session.refresh(hero_3) # (31)
|
||||
session.refresh(hero_1) # (29)!
|
||||
session.refresh(hero_2) # (30)!
|
||||
session.refresh(hero_3) # (31)!
|
||||
|
||||
print("After refreshing the heroes") # (32)
|
||||
print("Hero 1:", hero_1) # (33)
|
||||
print("Hero 2:", hero_2) # (34)
|
||||
print("Hero 3:", hero_3) # (35)
|
||||
# (36)
|
||||
print("After refreshing the heroes") # (32)!
|
||||
print("Hero 1:", hero_1) # (33)!
|
||||
print("Hero 2:", hero_2) # (34)!
|
||||
print("Hero 3:", hero_3) # (35)!
|
||||
# (36)!
|
||||
|
||||
print("After the session closes") # (37)
|
||||
print("Hero 1:", hero_1) # (38)
|
||||
print("Hero 2:", hero_2) # (39)
|
||||
print("Hero 3:", hero_3) # (40)
|
||||
print("After the session closes") # (37)!
|
||||
print("Hero 1:", hero_1) # (38)!
|
||||
print("Hero 2:", hero_2) # (39)!
|
||||
print("Hero 3:", hero_3) # (40)!
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
from sqlmodel import Field, Session, SQLModel, create_engine
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str
|
||||
secret_name: str
|
||||
age: int | None = None
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
|
||||
def create_heroes():
|
||||
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson") # (1)!
|
||||
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador") # (2)!
|
||||
hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48) # (3)!
|
||||
|
||||
print("Before interacting with the database") # (4)!
|
||||
print("Hero 1:", hero_1) # (5)!
|
||||
print("Hero 2:", hero_2) # (6)!
|
||||
print("Hero 3:", hero_3) # (7)!
|
||||
|
||||
with Session(engine) as session: # (8)!
|
||||
session.add(hero_1) # (9)!
|
||||
session.add(hero_2) # (10)!
|
||||
session.add(hero_3) # (11)!
|
||||
|
||||
print("After adding to the session") # (12)!
|
||||
print("Hero 1:", hero_1) # (13)!
|
||||
print("Hero 2:", hero_2) # (14)!
|
||||
print("Hero 3:", hero_3) # (15)!
|
||||
|
||||
session.commit() # (16)!
|
||||
|
||||
print("After committing the session") # (17)!
|
||||
print("Hero 1:", hero_1) # (18)!
|
||||
print("Hero 2:", hero_2) # (19)!
|
||||
print("Hero 3:", hero_3) # (20)!
|
||||
|
||||
print("After committing the session, show IDs") # (21)!
|
||||
print("Hero 1 ID:", hero_1.id) # (22)!
|
||||
print("Hero 2 ID:", hero_2.id) # (23)!
|
||||
print("Hero 3 ID:", hero_3.id) # (24)!
|
||||
|
||||
print("After committing the session, show names") # (25)!
|
||||
print("Hero 1 name:", hero_1.name) # (26)!
|
||||
print("Hero 2 name:", hero_2.name) # (27)!
|
||||
print("Hero 3 name:", hero_3.name) # (28)!
|
||||
|
||||
session.refresh(hero_1) # (29)!
|
||||
session.refresh(hero_2) # (30)!
|
||||
session.refresh(hero_3) # (31)!
|
||||
|
||||
print("After refreshing the heroes") # (32)!
|
||||
print("Hero 1:", hero_1) # (33)!
|
||||
print("Hero 2:", hero_2) # (34)!
|
||||
print("Hero 3:", hero_3) # (35)!
|
||||
# (36)!
|
||||
|
||||
print("After the session closes") # (37)!
|
||||
print("Hero 1:", hero_1) # (38)!
|
||||
print("Hero 2:", hero_2) # (39)!
|
||||
print("Hero 3:", hero_3) # (40)!
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -6,7 +6,7 @@ from .models import Hero, Team
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret’s Bar")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
|
||||
29
docs_src/tutorial/code_structure/tutorial001_py310/app.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from sqlmodel import Session
|
||||
|
||||
from .database import create_db_and_tables, engine
|
||||
from .models import Hero, Team
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
session.add(hero_deadpond)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Hero's team:", hero_deadpond.team)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,10 @@
|
||||
from sqlmodel import SQLModel, create_engine
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
19
docs_src/tutorial/code_structure/tutorial001_py310/models.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from sqlmodel import Field, Relationship, SQLModel
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: list["Hero"] = Relationship(back_populates="team")
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: int | None = Field(default=None, index=True)
|
||||
|
||||
team_id: int | None = Field(default=None, foreign_key="team.id")
|
||||
team: Team | None = Relationship(back_populates="heroes")
|
||||
29
docs_src/tutorial/code_structure/tutorial001_py39/app.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from sqlmodel import Session
|
||||
|
||||
from .database import create_db_and_tables, engine
|
||||
from .models import Hero, Team
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
session.add(hero_deadpond)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Hero's team:", hero_deadpond.team)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,10 @@
|
||||
from sqlmodel import SQLModel, create_engine
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
21
docs_src/tutorial/code_structure/tutorial001_py39/models.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from typing import Optional
|
||||
|
||||
from sqlmodel import Field, Relationship, SQLModel
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: list["Hero"] = Relationship(back_populates="team")
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: Optional[int] = Field(default=None, index=True)
|
||||
|
||||
team_id: Optional[int] = Field(default=None, foreign_key="team.id")
|
||||
team: Optional[Team] = Relationship(back_populates="heroes")
|
||||
@@ -7,7 +7,7 @@ from .team_model import Team
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret’s Bar")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
|
||||