diff --git a/.github/actions/comment-docs-preview-in-pr/app/main.py b/.github/actions/comment-docs-preview-in-pr/app/main.py index 3b10e0ee08..c9fb7cbbef 100644 --- a/.github/actions/comment-docs-preview-in-pr/app/main.py +++ b/.github/actions/comment-docs-preview-in-pr/app/main.py @@ -48,9 +48,7 @@ class PartialGithubEvent(BaseModel): 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()}" diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index cf5295aae0..879cc7c5d8 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -17,7 +17,7 @@ jobs: outputs: docs: ${{ steps.filter.outputs.docs }} steps: - - uses: actions/checkout@v3 + - 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 @@ -41,7 +41,7 @@ jobs: env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 25cd1ff369..f9035d89a7 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -14,14 +14,14 @@ jobs: env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Clean site run: | rm -rf ./site mkdir ./site - name: Download Artifact Docs id: download - uses: dawidd6/action-download-artifact@v2.27.0 + uses: dawidd6/action-download-artifact@v2.28.0 with: if_no_artifact_found: ignore github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/latest-changes.yml b/.github/workflows/latest-changes.yml index 357767bba9..7e8403972d 100644 --- a/.github/workflows/latest-changes.yml +++ b/.github/workflows/latest-changes.yml @@ -20,7 +20,7 @@ jobs: latest-changes: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: # To allow latest-changes to commit to the main branch token: ${{ secrets.SQLMODEL_LATEST_CHANGES }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1c21cd4a87..42a0eec3ce 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,7 +15,7 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml index 7ee17cac55..d2c274ff29 100644 --- a/.github/workflows/smokeshow.yml +++ b/.github/workflows/smokeshow.yml @@ -20,7 +20,7 @@ jobs: - run: pip install smokeshow - - uses: dawidd6/action-download-artifact@v2.24.2 + - uses: dawidd6/action-download-artifact@v2.28.0 with: workflow: test.yml commit: ${{ github.event.workflow_run.head_sha }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 871557a6fe..201abc7c22 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,7 +28,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: @@ -74,7 +74,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..022ef24a0f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,35 @@ +# 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/asottile/pyupgrade + rev: v3.15.0 + hooks: + - id: pyupgrade + args: + - --py3-plus + - --keep-runtime-typing +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.1 + hooks: + - id: ruff + args: + - --fix +- repo: https://github.com/psf/black + rev: 23.10.0 + hooks: + - id: black +ci: + autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks + autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000000..978031d58e --- /dev/null +++ b/CITATION.cff @@ -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 diff --git a/README.md b/README.md index f85bb97a6d..a9387c510a 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ It combines SQLAlchemy and Pydantic and tries to simplify the code you write as ## Requirements -A recent and currently supported version of Python Python. +A recent and currently supported version of Python. As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. They will be automatically installed when you install SQLModel. diff --git a/docs/contributing.md b/docs/contributing.md index 1cd62d42dc..217ed61c56 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -112,7 +112,7 @@ There is a script that you can run locally to test all the code and generate cov
```console -$ bash scripts/test-cov-html.sh +$ bash scripts/test.sh ```
diff --git a/docs/db-to-code.md b/docs/db-to-code.md index 2e0fb1babc..980c457148 100644 --- a/docs/db-to-code.md +++ b/docs/db-to-code.md @@ -111,7 +111,7 @@ DROP TABLE hero; That is how you tell the database in SQL to delete the entire table `hero`. -Nooooo! We lost all the data in the `hero` table! 💥😱 +Nooooo! We lost all the data in the `hero` table! 💥😱 ### SQL Sanitization @@ -305,4 +305,4 @@ 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 - You can also override the table name. You can read about it in the Advanced User Guide. \ No newline at end of file + You can also override the table name. You can read about it in the Advanced User Guide. diff --git a/docs/img/databases/external-server.drawio b/docs/img/databases/external-server.drawio index 4af8e30d6c..7631d02662 100644 --- a/docs/img/databases/external-server.drawio +++ b/docs/img/databases/external-server.drawio @@ -90,4 +90,4 @@ - \ No newline at end of file + diff --git a/docs/img/databases/external-server.svg b/docs/img/databases/external-server.svg index cf0f541bfa..4a85c58225 100644 --- a/docs/img/databases/external-server.svg +++ b/docs/img/databases/external-server.svg @@ -1 +1 @@ -
Machine / Computer
Machine / Computer
Database application
Database application
File
File
Data
Data
File
File
Data
Data
File
File
Data
Data
Machine / Computer
Machine / Computer
Your code
Your code
Viewer does not support full SVG 1.1
\ No newline at end of file +
Machine / Computer
Machine / Computer
Database application
Database application
File
File
Data
Data
File
File
Data
Data
File
File
Data
Data
Machine / Computer
Machine / Computer
Your code
Your code
Viewer does not support full SVG 1.1
diff --git a/docs/img/databases/multiple-servers.drawio b/docs/img/databases/multiple-servers.drawio index efceed9c08..9a4fd542f2 100644 --- a/docs/img/databases/multiple-servers.drawio +++ b/docs/img/databases/multiple-servers.drawio @@ -202,4 +202,4 @@ - \ No newline at end of file + diff --git a/docs/img/databases/multiple-servers.svg b/docs/img/databases/multiple-servers.svg index 039260a608..083d0e73bd 100644 --- a/docs/img/databases/multiple-servers.svg +++ b/docs/img/databases/multiple-servers.svg @@ -1 +1 @@ -
Machine / Computer
Machine / Computer
Database application
Database application
File
File
Data
Data
File
File
Data
Data
Machine / Computer
Machine / Computer
Your code
Your code
Machine / Computer
Machine / Computer
Database application
Database application
File
File
Data
Data
File
File
Data
Data
Machine / Computer
Machine / Computer
Database application
Database application
File
File
Data
Data
File
File
Data
Data
Viewer does not support full SVG 1.1
\ No newline at end of file +
Machine / Computer
Machine / Computer
Database application
Database application
File
File
Data
Data
File
File
Data
Data
Machine / Computer
Machine / Computer
Your code
Your code
Machine / Computer
Machine / Computer
Database application
Database application
File
File
Data
Data
File
File
Data
Data
Machine / Computer
Machine / Computer
Database application
Database application
File
File
Data
Data
File
File
Data
Data
Viewer does not support full SVG 1.1
diff --git a/docs/img/databases/relationships.drawio b/docs/img/databases/relationships.drawio index 9ae668cf99..fb8ef8d128 100644 --- a/docs/img/databases/relationships.drawio +++ b/docs/img/databases/relationships.drawio @@ -148,4 +148,4 @@ - \ No newline at end of file + diff --git a/docs/img/databases/relationships.svg b/docs/img/databases/relationships.svg index fbecdac05a..055727579f 100644 --- a/docs/img/databases/relationships.svg +++ b/docs/img/databases/relationships.svg @@ -54,4 +54,4 @@ src: url("https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Mu4mxK.woff2") format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } -
hero
hero
id
id
name
name
secret_name
secret_name
age
age
team_id
team_id
1
1
Deadpond
Deadpond
Dive Wilson
Dive Wilson
null
null
2
2
2
2
Spider-Boy
Spider-Boy
Pedro Parqueador
Pedro Parqueador
null
null
1
1
3
3
Rusty-Man
Rusty-Man
Tommy Sharp
Tommy Sharp
48
48
1
1
team
team
id
id
name
name
headquarters
headquarters
1
1
Preventers
Preventers
Sharp Tower
Sharp Tower
2
2
Z-Force
Z-Force

Sister Margaret’s Bar

Sister Margaret’s Bar
Viewer does not support full SVG 1.1 \ No newline at end of file +
hero
hero
id
id
name
name
secret_name
secret_name
age
age
team_id
team_id
1
1
Deadpond
Deadpond
Dive Wilson
Dive Wilson
null
null
2
2
2
2
Spider-Boy
Spider-Boy
Pedro Parqueador
Pedro Parqueador
null
null
1
1
3
3
Rusty-Man
Rusty-Man
Tommy Sharp
Tommy Sharp
48
48
1
1
team
team
id
id
name
name
headquarters
headquarters
1
1
Preventers
Preventers
Sharp Tower
Sharp Tower
2
2
Z-Force
Z-Force

Sister Margaret’s Bar

Sister Margaret’s Bar
Viewer does not support full SVG 1.1 diff --git a/docs/img/databases/same-server.drawio b/docs/img/databases/same-server.drawio index 596c64956c..4f43be43cb 100644 --- a/docs/img/databases/same-server.drawio +++ b/docs/img/databases/same-server.drawio @@ -81,4 +81,4 @@ - \ No newline at end of file + diff --git a/docs/img/databases/same-server.svg b/docs/img/databases/same-server.svg index 7f2a77b73c..a3f6dff36b 100644 --- a/docs/img/databases/same-server.svg +++ b/docs/img/databases/same-server.svg @@ -1 +1 @@ -
Machine / Computer
Machine / Computer
Database application
Database application
File
File
Data
Data
File
File
Data
Data
File
File
Data
Data
Your code
Your code
Viewer does not support full SVG 1.1
\ No newline at end of file +
Machine / Computer
Machine / Computer
Database application
Database application
File
File
Data
Data
File
File
Data
Data
File
File
Data
Data
Your code
Your code
Viewer does not support full SVG 1.1
diff --git a/docs/img/databases/single-file.drawio b/docs/img/databases/single-file.drawio index 52ce703c4c..c379f71c1c 100644 --- a/docs/img/databases/single-file.drawio +++ b/docs/img/databases/single-file.drawio @@ -34,4 +34,4 @@ - \ No newline at end of file + diff --git a/docs/img/databases/single-file.svg b/docs/img/databases/single-file.svg index f2526d952f..52c91e573d 100644 --- a/docs/img/databases/single-file.svg +++ b/docs/img/databases/single-file.svg @@ -1 +1 @@ -
Machine / Computer
Machine / Computer
Your code
Your code
File: heroes.db
File: heroes.db
Data
Data
Viewer does not support full SVG 1.1
\ No newline at end of file +
Machine / Computer
Machine / Computer
Your code
Your code
File: heroes.db
File: heroes.db
Data
Data
Viewer does not support full SVG 1.1
diff --git a/docs/img/db-to-code/mapper.drawio b/docs/img/db-to-code/mapper.drawio index 072b629b54..3b002eb3f8 100644 --- a/docs/img/db-to-code/mapper.drawio +++ b/docs/img/db-to-code/mapper.drawio @@ -61,4 +61,4 @@ - \ No newline at end of file + diff --git a/docs/img/db-to-code/mapper.svg b/docs/img/db-to-code/mapper.svg index 496c907464..e31a464bba 100644 --- a/docs/img/db-to-code/mapper.svg +++ b/docs/img/db-to-code/mapper.svg @@ -1 +1 @@ -
Set of triangles
Set of triangles
Set of squares
Set of squares
Squares to Triangles Mapper
Squares to Triangles Mapp...
Viewer does not support full SVG 1.1
\ No newline at end of file +
Set of triangles
Set of triangles
Set of squares
Set of squares
Squares to Triangles Mapper
Squares to Triangles Mapp...
Viewer does not support full SVG 1.1
diff --git a/docs/img/tutorial/indexes/dictionary001.drawio b/docs/img/tutorial/indexes/dictionary001.drawio index 659f6b52a4..84992d0213 100644 --- a/docs/img/tutorial/indexes/dictionary001.drawio +++ b/docs/img/tutorial/indexes/dictionary001.drawio @@ -94,4 +94,4 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/indexes/dictionary001.svg b/docs/img/tutorial/indexes/dictionary001.svg index b543793a25..59fc39294e 100644 --- a/docs/img/tutorial/indexes/dictionary001.svg +++ b/docs/img/tutorial/indexes/dictionary001.svg @@ -54,4 +54,4 @@ src: url("https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu4mxK.woff2") format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } -
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
M
M
Viewer does not support full SVG 1.1 \ No newline at end of file +
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
M
M
Viewer does not support full SVG 1.1 diff --git a/docs/img/tutorial/indexes/dictionary002.drawio b/docs/img/tutorial/indexes/dictionary002.drawio index cb1857b1ad..52544abde9 100644 --- a/docs/img/tutorial/indexes/dictionary002.drawio +++ b/docs/img/tutorial/indexes/dictionary002.drawio @@ -94,4 +94,4 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/indexes/dictionary002.svg b/docs/img/tutorial/indexes/dictionary002.svg index 677687d248..d612925125 100644 --- a/docs/img/tutorial/indexes/dictionary002.svg +++ b/docs/img/tutorial/indexes/dictionary002.svg @@ -1 +1 @@ -
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
M
M
Viewer does not support full SVG 1.1
\ No newline at end of file +
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
M
M
Viewer does not support full SVG 1.1
diff --git a/docs/img/tutorial/indexes/dictionary003.drawio b/docs/img/tutorial/indexes/dictionary003.drawio index 845eb065cd..d37353364c 100644 --- a/docs/img/tutorial/indexes/dictionary003.drawio +++ b/docs/img/tutorial/indexes/dictionary003.drawio @@ -94,4 +94,4 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/indexes/dictionary003.svg b/docs/img/tutorial/indexes/dictionary003.svg index d667a68893..0eafd5c561 100644 --- a/docs/img/tutorial/indexes/dictionary003.svg +++ b/docs/img/tutorial/indexes/dictionary003.svg @@ -1 +1 @@ -
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
Viewer does not support full SVG 1.1
\ No newline at end of file +
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
Viewer does not support full SVG 1.1
diff --git a/docs/img/tutorial/indexes/dictionary004.drawio b/docs/img/tutorial/indexes/dictionary004.drawio index 14bbb1e26e..6c8590d6ea 100644 --- a/docs/img/tutorial/indexes/dictionary004.drawio +++ b/docs/img/tutorial/indexes/dictionary004.drawio @@ -97,4 +97,4 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/indexes/dictionary004.svg b/docs/img/tutorial/indexes/dictionary004.svg index f881d6c9c2..dcfcfc5a59 100644 --- a/docs/img/tutorial/indexes/dictionary004.svg +++ b/docs/img/tutorial/indexes/dictionary004.svg @@ -1 +1 @@ -
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
F
F
Viewer does not support full SVG 1.1
\ No newline at end of file +
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
F
F
Viewer does not support full SVG 1.1
diff --git a/docs/img/tutorial/indexes/dictionary005.drawio b/docs/img/tutorial/indexes/dictionary005.drawio index 9e339c177e..33e21c6d90 100644 --- a/docs/img/tutorial/indexes/dictionary005.drawio +++ b/docs/img/tutorial/indexes/dictionary005.drawio @@ -94,4 +94,4 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/indexes/dictionary005.svg b/docs/img/tutorial/indexes/dictionary005.svg index 9d376245c0..4bfa8c0ca4 100644 --- a/docs/img/tutorial/indexes/dictionary005.svg +++ b/docs/img/tutorial/indexes/dictionary005.svg @@ -1 +1 @@ -
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
Viewer does not support full SVG 1.1
\ No newline at end of file +
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
Viewer does not support full SVG 1.1
diff --git a/docs/img/tutorial/indexes/dictionary006.drawio b/docs/img/tutorial/indexes/dictionary006.drawio index 3c669d323f..84366e87b0 100644 --- a/docs/img/tutorial/indexes/dictionary006.drawio +++ b/docs/img/tutorial/indexes/dictionary006.drawio @@ -97,4 +97,4 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/indexes/dictionary006.svg b/docs/img/tutorial/indexes/dictionary006.svg index 30be80ea8b..f893ca4cd4 100644 --- a/docs/img/tutorial/indexes/dictionary006.svg +++ b/docs/img/tutorial/indexes/dictionary006.svg @@ -1 +1 @@ -
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
C
C
Viewer does not support full SVG 1.1
\ No newline at end of file +
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
C
C
Viewer does not support full SVG 1.1
diff --git a/docs/img/tutorial/indexes/dictionary007.drawio b/docs/img/tutorial/indexes/dictionary007.drawio index 89b32cabaf..62ad844200 100644 --- a/docs/img/tutorial/indexes/dictionary007.drawio +++ b/docs/img/tutorial/indexes/dictionary007.drawio @@ -97,4 +97,4 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/indexes/dictionary007.svg b/docs/img/tutorial/indexes/dictionary007.svg index 79e795060e..8f71aa8311 100644 --- a/docs/img/tutorial/indexes/dictionary007.svg +++ b/docs/img/tutorial/indexes/dictionary007.svg @@ -1 +1 @@ -
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
Viewer does not support full SVG 1.1
\ No newline at end of file +
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
Viewer does not support full SVG 1.1
diff --git a/docs/img/tutorial/indexes/dictionary008.drawio b/docs/img/tutorial/indexes/dictionary008.drawio index ac08ad04d4..30a1f66277 100644 --- a/docs/img/tutorial/indexes/dictionary008.drawio +++ b/docs/img/tutorial/indexes/dictionary008.drawio @@ -100,4 +100,4 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/indexes/dictionary008.svg b/docs/img/tutorial/indexes/dictionary008.svg index 013a4d64a3..5a48e18a20 100644 --- a/docs/img/tutorial/indexes/dictionary008.svg +++ b/docs/img/tutorial/indexes/dictionary008.svg @@ -1 +1 @@ -
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
D
D
Viewer does not support full SVG 1.1
\ No newline at end of file +
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
D
D
Viewer does not support full SVG 1.1
diff --git a/docs/img/tutorial/indexes/techbook001.drawio b/docs/img/tutorial/indexes/techbook001.drawio index de1c25668c..5fffe3c3ea 100644 --- a/docs/img/tutorial/indexes/techbook001.drawio +++ b/docs/img/tutorial/indexes/techbook001.drawio @@ -89,4 +89,4 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/indexes/techbook001.svg b/docs/img/tutorial/indexes/techbook001.svg index 8b0c09ddcf..3f44f50742 100644 --- a/docs/img/tutorial/indexes/techbook001.svg +++ b/docs/img/tutorial/indexes/techbook001.svg @@ -1 +1 @@ -
Technical Book
Technical Book
Chapter 1
Chapter 1
Chapter 2
Chapter 2
Chapter 3
Chapter 3
Chapter 4
Chapter 4
Chapter 5
Chapter 5
Chapter 6
Chapter 6
Chapter 7
Chapter 7
Book Index
Book Index
Database
Database
Python
Python
Files
Files
Editors
Editors
Viewer does not support full SVG 1.1
\ No newline at end of file +
Technical Book
Technical Book
Chapter 1
Chapter 1
Chapter 2
Chapter 2
Chapter 3
Chapter 3
Chapter 4
Chapter 4
Chapter 5
Chapter 5
Chapter 6
Chapter 6
Chapter 7
Chapter 7
Book Index
Book Index
Database
Database
Python
Python
Files
Files
Editors
Editors
Viewer does not support full SVG 1.1
diff --git a/docs/img/tutorial/many-to-many/many-to-many.drawio b/docs/img/tutorial/many-to-many/many-to-many.drawio index d585e5e0e6..bbe1176076 100644 --- a/docs/img/tutorial/many-to-many/many-to-many.drawio +++ b/docs/img/tutorial/many-to-many/many-to-many.drawio @@ -217,4 +217,4 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/many-to-many/many-to-many.svg b/docs/img/tutorial/many-to-many/many-to-many.svg index d35c280352..aa0c6cc457 100644 --- a/docs/img/tutorial/many-to-many/many-to-many.svg +++ b/docs/img/tutorial/many-to-many/many-to-many.svg @@ -54,4 +54,4 @@ src: url("https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Mu4mxK.woff2") format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } -
hero
hero
id
id
name
name
secret_name
secret_name
age
age
1
1
Deadpond
Deadpond
Dive Wilson
Dive Wilson
null
null
2
2
Spider-Boy
Spider-Boy
Pedro Parqueador
Pedro Parqueador
null
null
3
3
Rusty-Man
Rusty-Man
Tommy Sharp
Tommy Sharp
48
48
team
team
id
id
name
name
headquarters
headquarters
1
1
Preventers
Preventers
Sharp Tower
Sharp Tower
2
2
Z-Force
Z-Force

Sister Margaret’s Bar

Sister Margaret’s Bar
heroteamlink
heroteamlink
hero_id
hero_id
team_id
team_id
1
1
1
1
1
1
2
2
2
2
1
1
3
3
1
1
Viewer does not support full SVG 1.1 \ No newline at end of file +
hero
hero
id
id
name
name
secret_name
secret_name
age
age
1
1
Deadpond
Deadpond
Dive Wilson
Dive Wilson
null
null
2
2
Spider-Boy
Spider-Boy
Pedro Parqueador
Pedro Parqueador
null
null
3
3
Rusty-Man
Rusty-Man
Tommy Sharp
Tommy Sharp
48
48
team
team
id
id
name
name
headquarters
headquarters
1
1
Preventers
Preventers
Sharp Tower
Sharp Tower
2
2
Z-Force
Z-Force

Sister Margaret’s Bar

Sister Margaret’s Bar
heroteamlink
heroteamlink
hero_id
hero_id
team_id
team_id
1
1
1
1
1
1
2
2
2
2
1
1
3
3
1
1
Viewer does not support full SVG 1.1 diff --git a/docs/img/tutorial/offset-and-limit/limit.drawio b/docs/img/tutorial/offset-and-limit/limit.drawio index 569dfc5437..da7f17ce99 100644 --- a/docs/img/tutorial/offset-and-limit/limit.drawio +++ b/docs/img/tutorial/offset-and-limit/limit.drawio @@ -130,4 +130,4 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/offset-and-limit/limit.svg b/docs/img/tutorial/offset-and-limit/limit.svg index 3e31086a10..d05669e3c5 100644 --- a/docs/img/tutorial/offset-and-limit/limit.svg +++ b/docs/img/tutorial/offset-and-limit/limit.svg @@ -54,4 +54,4 @@ src: url("https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Mu4mxK.woff2") format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } -
hero
hero
id
id
name
name
secret_name
secret_name
age
age
1
1
Deadpond
Deadpond
Dive Wilson
Dive Wilson
null
null
2
2
Spider-Boy
Spider-Boy
Pedro Parqueador
Pedro Parqueador
null
null
3
3
Rusty-Man
Rusty-Man
Tommy Sharp
Tommy Sharp
48
48
4
4
Tarantula
Tarantula
Natalia Roman-on
Natalia Roman-on
32
32
5
5
Black Lion
Black Lion
Trevor Challa
Trevor Challa
35
35
6
6
Dr. Weird
Dr. Weird
Steve Weird
Steve Weird
36
36
7
7
Captain North America
Captain North America
Esteban Rogelios
Esteban Rogelios
93
93
Viewer does not support full SVG 1.1 \ No newline at end of file +
hero
hero
id
id
name
name
secret_name
secret_name
age
age
1
1
Deadpond
Deadpond
Dive Wilson
Dive Wilson
null
null
2
2
Spider-Boy
Spider-Boy
Pedro Parqueador
Pedro Parqueador
null
null
3
3
Rusty-Man
Rusty-Man
Tommy Sharp
Tommy Sharp
48
48
4
4
Tarantula
Tarantula
Natalia Roman-on
Natalia Roman-on
32
32
5
5
Black Lion
Black Lion
Trevor Challa
Trevor Challa
35
35
6
6
Dr. Weird
Dr. Weird
Steve Weird
Steve Weird
36
36
7
7
Captain North America
Captain North America
Esteban Rogelios
Esteban Rogelios
93
93
Viewer does not support full SVG 1.1 diff --git a/docs/img/tutorial/offset-and-limit/limit2.drawio b/docs/img/tutorial/offset-and-limit/limit2.drawio index a329235f71..68f9817052 100644 --- a/docs/img/tutorial/offset-and-limit/limit2.drawio +++ b/docs/img/tutorial/offset-and-limit/limit2.drawio @@ -130,4 +130,4 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/offset-and-limit/limit2.svg b/docs/img/tutorial/offset-and-limit/limit2.svg index a3a7e81aa7..f574f13640 100644 --- a/docs/img/tutorial/offset-and-limit/limit2.svg +++ b/docs/img/tutorial/offset-and-limit/limit2.svg @@ -54,4 +54,4 @@ src: url("https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Mu4mxK.woff2") format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } -
hero
hero
id
id
name
name
secret_name
secret_name
age
age
1
1
Deadpond
Deadpond
Dive Wilson
Dive Wilson
null
null
2
2
Spider-Boy
Spider-Boy
Pedro Parqueador
Pedro Parqueador
null
null
3
3
Rusty-Man
Rusty-Man
Tommy Sharp
Tommy Sharp
48
48
4
4
Tarantula
Tarantula
Natalia Roman-on
Natalia Roman-on
32
32
5
5
Black Lion
Black Lion
Trevor Challa
Trevor Challa
35
35
6
6
Dr. Weird
Dr. Weird
Steve Weird
Steve Weird
36
36
7
7
Captain North America
Captain North America
Esteban Rogelios
Esteban Rogelios
93
93
Viewer does not support full SVG 1.1 \ No newline at end of file +
hero
hero
id
id
name
name
secret_name
secret_name
age
age
1
1
Deadpond
Deadpond
Dive Wilson
Dive Wilson
null
null
2
2
Spider-Boy
Spider-Boy
Pedro Parqueador
Pedro Parqueador
null
null
3
3
Rusty-Man
Rusty-Man
Tommy Sharp
Tommy Sharp
48
48
4
4
Tarantula
Tarantula
Natalia Roman-on
Natalia Roman-on
32
32
5
5
Black Lion
Black Lion
Trevor Challa
Trevor Challa
35
35
6
6
Dr. Weird
Dr. Weird
Steve Weird
Steve Weird
36
36
7
7
Captain North America
Captain North America
Esteban Rogelios
Esteban Rogelios
93
93
Viewer does not support full SVG 1.1 diff --git a/docs/img/tutorial/offset-and-limit/limit3.drawio b/docs/img/tutorial/offset-and-limit/limit3.drawio index 2b2b20db2c..93e4c2df53 100644 --- a/docs/img/tutorial/offset-and-limit/limit3.drawio +++ b/docs/img/tutorial/offset-and-limit/limit3.drawio @@ -130,4 +130,4 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/offset-and-limit/limit3.svg b/docs/img/tutorial/offset-and-limit/limit3.svg index 5858359964..8a1a560b00 100644 --- a/docs/img/tutorial/offset-and-limit/limit3.svg +++ b/docs/img/tutorial/offset-and-limit/limit3.svg @@ -54,4 +54,4 @@ src: url("https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Mu4mxK.woff2") format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } -
hero
hero
id
id
name
name
secret_name
secret_name
age
age
1
1
Deadpond
Deadpond
Dive Wilson
Dive Wilson
null
null
2
2
Spider-Boy
Spider-Boy
Pedro Parqueador
Pedro Parqueador
null
null
3
3
Rusty-Man
Rusty-Man
Tommy Sharp
Tommy Sharp
48
48
4
4
Tarantula
Tarantula
Natalia Roman-on
Natalia Roman-on
32
32
5
5
Black Lion
Black Lion
Trevor Challa
Trevor Challa
35
35
6
6
Dr. Weird
Dr. Weird
Steve Weird
Steve Weird
36
36
7
7
Captain North America
Captain North America
Esteban Rogelios
Esteban Rogelios
93
93
Viewer does not support full SVG 1.1 \ No newline at end of file +
hero
hero
id
id
name
name
secret_name
secret_name
age
age
1
1
Deadpond
Deadpond
Dive Wilson
Dive Wilson
null
null
2
2
Spider-Boy
Spider-Boy
Pedro Parqueador
Pedro Parqueador
null
null
3
3
Rusty-Man
Rusty-Man
Tommy Sharp
Tommy Sharp
48
48
4
4
Tarantula
Tarantula
Natalia Roman-on
Natalia Roman-on
32
32
5
5
Black Lion
Black Lion
Trevor Challa
Trevor Challa
35
35
6
6
Dr. Weird
Dr. Weird
Steve Weird
Steve Weird
36
36
7
7
Captain North America
Captain North America
Esteban Rogelios
Esteban Rogelios
93
93
Viewer does not support full SVG 1.1 diff --git a/docs/img/tutorial/relationships/attributes/back-populates.drawio b/docs/img/tutorial/relationships/attributes/back-populates.drawio index 5ef9c03264..5fc8c6fa73 100644 --- a/docs/img/tutorial/relationships/attributes/back-populates.drawio +++ b/docs/img/tutorial/relationships/attributes/back-populates.drawio @@ -38,4 +38,4 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/relationships/attributes/back-populates.svg b/docs/img/tutorial/relationships/attributes/back-populates.svg index dabf36de1f..631f73c808 100644 --- a/docs/img/tutorial/relationships/attributes/back-populates.svg +++ b/docs/img/tutorial/relationships/attributes/back-populates.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/relationships/attributes/back-populates2.drawio b/docs/img/tutorial/relationships/attributes/back-populates2.drawio index 7313c464e9..c5d283feb0 100644 --- a/docs/img/tutorial/relationships/attributes/back-populates2.drawio +++ b/docs/img/tutorial/relationships/attributes/back-populates2.drawio @@ -49,4 +49,4 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/relationships/attributes/back-populates2.svg b/docs/img/tutorial/relationships/attributes/back-populates2.svg index 83ed8154a6..929cb0a042 100644 --- a/docs/img/tutorial/relationships/attributes/back-populates2.svg +++ b/docs/img/tutorial/relationships/attributes/back-populates2.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/relationships/select/relationships2.drawio b/docs/img/tutorial/relationships/select/relationships2.drawio index 0086af56c4..afd77f4e9e 100644 --- a/docs/img/tutorial/relationships/select/relationships2.drawio +++ b/docs/img/tutorial/relationships/select/relationships2.drawio @@ -139,4 +139,4 @@ - \ No newline at end of file + diff --git a/docs/img/tutorial/relationships/select/relationships2.svg b/docs/img/tutorial/relationships/select/relationships2.svg index e2f987eba7..c0987c7c32 100644 --- a/docs/img/tutorial/relationships/select/relationships2.svg +++ b/docs/img/tutorial/relationships/select/relationships2.svg @@ -54,4 +54,4 @@ src: url("https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Mu4mxK.woff2") format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } -
hero
hero
id
id
name
name
secret_name
secret_name
age
age
team_id
team_id
1
1
Deadpond
Deadpond
Dive Wilson
Dive Wilson
null
null
2
2
2
2
Spider-Boy
Spider-Boy
Pedro Parqueador
Pedro Parqueador
null
null
null
null
3
3
Rusty-Man
Rusty-Man
Tommy Sharp
Tommy Sharp
48
48
1
1
team
team
id
id
name
name
headquarters
headquarters
1
1
Preventers
Preventers
Sharp Tower
Sharp Tower
2
2
Z-Force
Z-Force

Sister Margaret’s Bar

Sister Margaret’s Bar
Viewer does not support full SVG 1.1 \ No newline at end of file +
hero
hero
id
id
name
name
secret_name
secret_name
age
age
team_id
team_id
1
1
Deadpond
Deadpond
Dive Wilson
Dive Wilson
null
null
2
2
2
2
Spider-Boy
Spider-Boy
Pedro Parqueador
Pedro Parqueador
null
null
null
null
3
3
Rusty-Man
Rusty-Man
Tommy Sharp
Tommy Sharp
48
48
1
1
team
team
id
id
name
name
headquarters
headquarters
1
1
Preventers
Preventers
Sharp Tower
Sharp Tower
2
2
Z-Force
Z-Force

Sister Margaret’s Bar

Sister Margaret’s Bar
Viewer does not support full SVG 1.1 diff --git a/docs/index.md b/docs/index.md index f85bb97a6d..524ef992a3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -50,7 +50,7 @@ It combines SQLAlchemy and Pydantic and tries to simplify the code you write as ## Requirements -A recent and currently supported version of Python Python. +A recent and currently supported version of Python. As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. They will be automatically installed when you install SQLModel. @@ -68,7 +68,7 @@ Successfully installed sqlmodel ## Example -For an introduction to databases, SQL, and everything else, see the SQLModel documentation. +For an introduction to databases, SQL, and everything else, see the SQLModel documentation. Here's a quick example. ✨ diff --git a/docs/js/termynal.js b/docs/js/termynal.js index c21e437501..45bb371c83 100644 --- a/docs/js/termynal.js +++ b/docs/js/termynal.js @@ -72,14 +72,14 @@ class Termynal { * Initialise the widget, get lines, clear container and start animation. */ init() { - /** + /** * Calculates width and height of Termynal container. * If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS. - */ + */ const containerStyle = getComputedStyle(this.container); - this.container.style.width = containerStyle.width !== '0px' ? + this.container.style.width = containerStyle.width !== '0px' ? containerStyle.width : undefined; - this.container.style.minHeight = containerStyle.height !== '0px' ? + this.container.style.minHeight = containerStyle.height !== '0px' ? containerStyle.height : undefined; this.container.setAttribute('data-termynal', ''); @@ -138,7 +138,7 @@ class Termynal { restart.innerHTML = "restart ↻" return restart } - + generateFinish() { const finish = document.createElement('a') finish.onclick = (e) => { @@ -215,7 +215,7 @@ class Termynal { /** * Converts line data objects into line elements. - * + * * @param {Object[]} lineData - Dynamically loaded lines. * @param {Object} line - Line data object. * @returns {Element[]} - Array of line elements. @@ -231,7 +231,7 @@ class Termynal { /** * Helper function for generating attributes string. - * + * * @param {Object} line - Line data object. * @returns {string} - String of attributes. */ diff --git a/docs/overrides/main.html b/docs/overrides/main.html index fc5bce873f..7440b084ad 100644 --- a/docs/overrides/main.html +++ b/docs/overrides/main.html @@ -12,9 +12,9 @@ }); }); - -{%- endblock %} \ No newline at end of file +{%- endblock %} diff --git a/docs/release-notes.md b/docs/release-notes.md index 8b0cf528dd..900804d8d1 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,22 +2,69 @@ ## Latest Changes +## 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). -* 🗑️ 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). * 👷 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 help SQLModel docs. PR [#548](https://github.com/tiangolo/sqlmodel/pull/548) 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). -* ✏️ Fix typo in internal function name `get_sqlachemy_type()`. PR [#496](https://github.com/tiangolo/sqlmodel/pull/496) by [@cmarqu](https://github.com/cmarqu). * ⬆ Bump actions/cache from 2 to 3. PR [#497](https://github.com/tiangolo/sqlmodel/pull/497) by [@dependabot[bot]](https://github.com/apps/dependabot). -* ✏️ Fix typo in docs. PR [#446](https://github.com/tiangolo/sqlmodel/pull/446) by [@davidbrochart](https://github.com/davidbrochart). * ⬆ 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). -* ✏️ 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). * 🔧 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). @@ -37,7 +84,7 @@ ### 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). +* 🐛 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 diff --git a/docs/tutorial/automatic-id-none-refresh.md b/docs/tutorial/automatic-id-none-refresh.md index bbf74dd307..c7cf975ad4 100644 --- a/docs/tutorial/automatic-id-none-refresh.md +++ b/docs/tutorial/automatic-id-none-refresh.md @@ -36,7 +36,7 @@ When we create a new `Hero` instance, we don't set the `id`: {!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:23-26]!} -# Code below ommitted 👇 +# Code below omitted 👇 ```
@@ -125,7 +125,7 @@ We can verify by creating a session using a `with` block and adding the objects. {!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:23-41]!} -# Code below ommitted 👇 +# Code below omitted 👇 ```
@@ -198,9 +198,9 @@ INFO Engine COMMIT // And now our prints After committing the session -Hero 1: -Hero 2: -Hero 3: +Hero 1: +Hero 2: +Hero 3: // What is happening here? 😱 ``` @@ -238,7 +238,7 @@ To confirm and understand how this **automatic expiration and refresh** of data {!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:33-58]!} -# Code below ommitted 👇 +# Code below omitted 👇 ```
@@ -271,21 +271,21 @@ 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 -Hero 1: -Hero 2: -Hero 3: +Hero 1: +Hero 2: +Hero 3: // Now we will access an attribute like the ID, this is the first print After committing the session, show IDs // Notice that before printing the first ID, the Session makes the Engine go to the database to refresh the data 🤓 INFO Engine BEGIN (implicit) -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero WHERE hero.id = ? INFO Engine [generated in 0.00017s] (1,) @@ -293,8 +293,8 @@ INFO Engine [generated in 0.00017s] (1,) Hero 1 ID: 1 // Before the next print, refresh the data for the second object -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.001245s ago] (2,) @@ -302,8 +302,8 @@ INFO Engine [cached since 0.001245s ago] (2,) Hero 2 ID: 2 // Before the third print, refresh its data -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.002215s ago] (3,) @@ -335,7 +335,7 @@ You can do that too with `session.refresh(object)`: {!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:33-67]!} -# Code below ommitted 👇 +# Code below omitted 👇 ```
@@ -362,23 +362,23 @@ 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 -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.id = ? INFO Engine [generated in 0.00024s] (1,) // The second refresh -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.001487s ago] (2,) // The third refresh -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.002377s ago] (3,) @@ -427,7 +427,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 @@ -468,12 +468,12 @@ INFO Engine PRAGMA main.table_info("hero") INFO Engine [raw sql] () INFO Engine PRAGMA temp.table_info("hero") INFO Engine [raw sql] () -INFO Engine +INFO Engine CREATE TABLE hero ( - id INTEGER, - name VARCHAR NOT NULL, - secret_name VARCHAR NOT NULL, - age INTEGER, + id INTEGER, + name VARCHAR NOT NULL, + secret_name VARCHAR NOT NULL, + age INTEGER, PRIMARY KEY (id) ) @@ -497,23 +497,23 @@ INFO Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?) INFO Engine [cached since 0.001483s ago] ('Rusty-Man', 'Tommy Sharp', 48) INFO Engine COMMIT After committing the session -Hero 1: -Hero 2: -Hero 3: +Hero 1: +Hero 2: +Hero 3: After committing the session, show IDs INFO Engine BEGIN (implicit) -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero WHERE hero.id = ? INFO Engine [generated in 0.00029s] (1,) Hero 1 ID: 1 -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.002132s ago] (2,) Hero 2 ID: 2 -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.003367s ago] (3,) Hero 3 ID: 3 @@ -521,16 +521,16 @@ After committing the session, show names Hero 1 name: Deadpond Hero 2 name: Spider-Boy Hero 3 name: Rusty-Man -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.id = ? INFO Engine [generated in 0.00025s] (1,) -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.001583s ago] (2,) -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.002722s ago] (3,) After refreshing the heroes diff --git a/docs/tutorial/code-structure.md b/docs/tutorial/code-structure.md index 59a9e4bd9a..502c8bf958 100644 --- a/docs/tutorial/code-structure.md +++ b/docs/tutorial/code-structure.md @@ -168,7 +168,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 *runtime*. +The problem with circular imports is that Python can't resolve them at *runtime*. 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. diff --git a/docs/tutorial/connect/create-connected-rows.md b/docs/tutorial/connect/create-connected-rows.md index 657edbde43..beda24a515 100644 --- a/docs/tutorial/connect/create-connected-rows.md +++ b/docs/tutorial/connect/create-connected-rows.md @@ -159,7 +159,7 @@ As the `Hero` class model now has a field (column, attribute) `team_id`, we can We haven't committed this hero to the database yet, but there are already a couple of things to pay **attention** to. -If the database already had some teams, we wouldn't even know **what is the ID** that is going to be automatically assigned to each team by the database, for example, we couldn't just guess `1` or `2`. +If the database already had some teams, we wouldn't even know **what is the ID** that is going to be automatically assigned to each team by the database, for example, we couldn't just guess `1` or `2`. But once the team is created and committed to the database, we can access the object's `id` field to get that ID. @@ -171,8 +171,8 @@ That line alone would generate an output of: ``` INFO Engine BEGIN (implicit) -INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters -FROM team +INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters +FROM team WHERE team.id = ? INFO Engine [generated in 0.00025s] (2,) ``` @@ -199,8 +199,8 @@ Let's now create two more heroes: 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: ``` -INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters -FROM team +INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters +FROM team WHERE team.id = ? INFO Engine [cached since 0.001795s ago] (1,) ``` @@ -256,18 +256,18 @@ $ python app.py INFO Engine BEGIN (implicit) // Refresh the first hero -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id +FROM hero WHERE hero.id = ? INFO Engine [generated in 0.00021s] (1,) // Refresh the second hero -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.001575s ago] (2,) // Refresh the third hero -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.002518s ago] (3,) diff --git a/docs/tutorial/connect/create-connected-tables.md b/docs/tutorial/connect/create-connected-tables.md index 452c904ebe..5a9a420a1b 100644 --- a/docs/tutorial/connect/create-connected-tables.md +++ b/docs/tutorial/connect/create-connected-tables.md @@ -106,7 +106,7 @@ This is the same model we have been using up to now, we are just adding the new 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. @@ -191,24 +191,24 @@ INFO Engine PRAGMA temp.table_info("hero") INFO Engine [raw sql] () // Create the tables -INFO Engine +INFO Engine CREATE TABLE team ( - id INTEGER, - name VARCHAR NOT NULL, - headquarters VARCHAR NOT NULL, + id INTEGER, + name VARCHAR NOT NULL, + headquarters VARCHAR NOT NULL, PRIMARY KEY (id) ) INFO Engine [no key 0.00010s] () -INFO Engine +INFO Engine CREATE TABLE hero ( - id INTEGER, - name VARCHAR NOT NULL, - secret_name VARCHAR NOT NULL, - age INTEGER, - team_id INTEGER, - PRIMARY KEY (id), + id INTEGER, + name VARCHAR NOT NULL, + secret_name VARCHAR NOT NULL, + age INTEGER, + team_id INTEGER, + PRIMARY KEY (id), FOREIGN KEY(team_id) REFERENCES team (id) ) @@ -229,9 +229,9 @@ So, the first SQL could also be written as: ```SQL CREATE TABLE team ( - id INTEGER, - name TEXT NOT NULL, - headquarters TEXT NOT NULL, + id INTEGER, + name TEXT NOT NULL, + headquarters TEXT NOT NULL, PRIMARY KEY (id) ) ``` @@ -240,12 +240,12 @@ And the second table could be written as: ```SQL hl_lines="8" CREATE TABLE hero ( - id INTEGER, - name TEXT NOT NULL, - secret_name TEXT NOT NULL, - age INTEGER, - team_id INTEGER, - PRIMARY KEY (id), + id INTEGER, + name TEXT NOT NULL, + secret_name TEXT NOT NULL, + age INTEGER, + team_id INTEGER, + PRIMARY KEY (id), FOREIGN KEY(team_id) REFERENCES team (id) ) ``` diff --git a/docs/tutorial/connect/read-connected-data.md b/docs/tutorial/connect/read-connected-data.md index 88cd754607..0ef3bccf43 100644 --- a/docs/tutorial/connect/read-connected-data.md +++ b/docs/tutorial/connect/read-connected-data.md @@ -203,8 +203,8 @@ $ python app.py // Previous output omitted 😉 // Get the heroes with their teams -2021-08-09 08:55:50,682 INFO sqlalchemy.engine.Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters -FROM hero, team +2021-08-09 08:55:50,682 INFO sqlalchemy.engine.Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters +FROM hero, team WHERE hero.team_id = team.id 2021-08-09 08:55:50,682 INFO sqlalchemy.engine.Engine [no key 0.00015s] () @@ -323,7 +323,7 @@ $ python app.py // Previous output omitted 😉 // Select using a JOIN with automatic ON -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters FROM hero JOIN team ON team.id = hero.team_id INFO Engine [no key 0.00032s] () @@ -458,7 +458,7 @@ $ python app.py // Previous output omitted 😉 // SELECT using LEFT OUTER JOIN -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters FROM hero LEFT OUTER JOIN team ON team.id = hero.team_id INFO Engine [no key 0.00051s] () @@ -522,9 +522,9 @@ If we run that, it would output: $ python app.py // Select only the hero data -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id // But still join with the team table -FROM hero JOIN team ON team.id = hero.team_id +FROM hero JOIN team ON team.id = hero.team_id // And filter with WHERE to get only the Preventers WHERE team.name = ? INFO Engine [no key 0.00066s] ('Preventers',) @@ -564,9 +564,9 @@ And if we run that, it will output: $ python app.py // Select the hero and the team data -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters // Join the hero with the team table -FROM hero JOIN team ON team.id = hero.team_id +FROM hero JOIN team ON team.id = hero.team_id // Filter with WHERE to get only Preventers WHERE team.name = ? INFO Engine [no key 0.00018s] ('Preventers',) diff --git a/docs/tutorial/connect/remove-data-connections.md b/docs/tutorial/connect/remove-data-connections.md index f44559b3d1..940a09f30d 100644 --- a/docs/tutorial/connect/remove-data-connections.md +++ b/docs/tutorial/connect/remove-data-connections.md @@ -56,7 +56,7 @@ We can simply set the `team_id` to `None`, and now it doesn't have a connection # Code above omitted 👆 {!./docs_src/tutorial/connect/delete/tutorial001.py[ln:31-32]!} - + # Previous code here omitted 👈 {!./docs_src/tutorial/connect/delete/tutorial001.py[ln:68-72]!} @@ -94,8 +94,8 @@ INFO Engine COMMIT // Automatically start a new transaction INFO Engine BEGIN (implicit) // Refresh the hero -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.1661s ago] (3,) diff --git a/docs/tutorial/connect/update-data-connections.md b/docs/tutorial/connect/update-data-connections.md index b7c8b0daa1..ccc430dd6e 100644 --- a/docs/tutorial/connect/update-data-connections.md +++ b/docs/tutorial/connect/update-data-connections.md @@ -56,7 +56,7 @@ Doing it is just like updating any other field: # Code above omitted 👆 {!./docs_src/tutorial/connect/update/tutorial001.py[ln:31-32]!} - + # Previous code here omitted 👈 {!./docs_src/tutorial/connect/update/tutorial001.py[ln:62-66]!} @@ -94,8 +94,8 @@ INFO Engine COMMIT // Automatically start a new transaction INFO Engine BEGIN (implicit) // Refresh the hero data -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.08837s ago] (3,) diff --git a/docs/tutorial/create-db-and-table-with-db-browser.md b/docs/tutorial/create-db-and-table-with-db-browser.md index a1bb394024..4437f15a6d 100644 --- a/docs/tutorial/create-db-and-table-with-db-browser.md +++ b/docs/tutorial/create-db-and-table-with-db-browser.md @@ -164,6 +164,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. 🔍 diff --git a/docs/tutorial/create-db-and-table.md b/docs/tutorial/create-db-and-table.md index abd73cb797..d0d27424d1 100644 --- a/docs/tutorial/create-db-and-table.md +++ b/docs/tutorial/create-db-and-table.md @@ -220,7 +220,7 @@ 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://` @@ -422,15 +422,15 @@ INFO Engine PRAGMA main.table_info("hero") INFO Engine [raw sql] () INFO Engine PRAGMA temp.table_info("hero") INFO Engine [raw sql] () -INFO Engine +INFO Engine // Finally, the glorious SQL to create the table ✨ CREATE TABLE hero ( - id INTEGER, - name VARCHAR NOT NULL, - secret_name VARCHAR NOT NULL, - age INTEGER, + id INTEGER, + name VARCHAR NOT NULL, + secret_name VARCHAR NOT NULL, + age INTEGER, PRIMARY KEY (id) ) diff --git a/docs/tutorial/delete.md b/docs/tutorial/delete.md index 0c9238d018..590b2ece52 100644 --- a/docs/tutorial/delete.md +++ b/docs/tutorial/delete.md @@ -108,8 +108,8 @@ $ python app.py // The SELECT with WHERE INFO Engine BEGIN (implicit) -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.name = ? INFO Engine [no key 0.00011s] ('Spider-Youngster',) @@ -272,8 +272,8 @@ $ python app.py INFO Engine BEGIN (implicit) // SQL to search for the hero -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.name = ? INFO Engine [no key 0.00013s] ('Spider-Youngster',) ``` diff --git a/docs/tutorial/fastapi/limit-and-offset.md b/docs/tutorial/fastapi/limit-and-offset.md index 92bbfc7ee0..8802f4ec99 100644 --- a/docs/tutorial/fastapi/limit-and-offset.md +++ b/docs/tutorial/fastapi/limit-and-offset.md @@ -42,7 +42,7 @@ 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. diff --git a/docs/tutorial/fastapi/multiple-models.md b/docs/tutorial/fastapi/multiple-models.md index c37fad386b..6845b9862d 100644 --- a/docs/tutorial/fastapi/multiple-models.md +++ b/docs/tutorial/fastapi/multiple-models.md @@ -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**. diff --git a/docs/tutorial/fastapi/relationships.md b/docs/tutorial/fastapi/relationships.md index 6921b5ac85..f152b231c7 100644 --- a/docs/tutorial/fastapi/relationships.md +++ b/docs/tutorial/fastapi/relationships.md @@ -84,7 +84,7 @@ 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 👇 ``` @@ -234,7 +234,7 @@ 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 👇 ``` diff --git a/docs/tutorial/fastapi/session-with-dependency.md b/docs/tutorial/fastapi/session-with-dependency.md index 52a800b9ea..195c2e1729 100644 --- a/docs/tutorial/fastapi/session-with-dependency.md +++ b/docs/tutorial/fastapi/session-with-dependency.md @@ -177,7 +177,7 @@ 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]!} ```
diff --git a/docs/tutorial/fastapi/teams.md b/docs/tutorial/fastapi/teams.md index 0b19a95cb3..52554cf4b6 100644 --- a/docs/tutorial/fastapi/teams.md +++ b/docs/tutorial/fastapi/teams.md @@ -92,7 +92,7 @@ These are equivalent and very similar to the **path operations** for the **heroe ```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.py[ln:138-192]!} # Code below omitted 👇 ``` diff --git a/docs/tutorial/fastapi/tests.md b/docs/tutorial/fastapi/tests.md index f817a883a1..cc6ad65c6f 100644 --- a/docs/tutorial/fastapi/tests.md +++ b/docs/tutorial/fastapi/tests.md @@ -82,7 +82,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. diff --git a/docs/tutorial/index.md b/docs/tutorial/index.md index 79fa670cfd..74107776c2 100644 --- a/docs/tutorial/index.md +++ b/docs/tutorial/index.md @@ -132,10 +132,10 @@ Here are the commands you could use:
```console - // Remember that you might need to use python3.9 or similar 💡 + // 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 @@ -157,7 +157,7 @@ Here are the commands you could use: ```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 diff --git a/docs/tutorial/indexes.md b/docs/tutorial/indexes.md index 6513d7d462..fef0081dc8 100644 --- a/docs/tutorial/indexes.md +++ b/docs/tutorial/indexes.md @@ -342,10 +342,10 @@ $ python app.py // Create the table CREATE TABLE hero ( - id INTEGER, - name VARCHAR NOT NULL, - secret_name VARCHAR NOT NULL, - age INTEGER, + id INTEGER, + name VARCHAR NOT NULL, + secret_name VARCHAR NOT NULL, + age INTEGER, PRIMARY KEY (id) ) @@ -353,8 +353,8 @@ CREATE TABLE hero ( CREATE INDEX ix_hero_name ON hero (name) // The SELECT with WHERE looks the same -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.name = ? INFO Engine [no key 0.00014s] ('Deadpond',) diff --git a/docs/tutorial/insert.md b/docs/tutorial/insert.md index 5947e1e5dc..ecf87adbad 100644 --- a/docs/tutorial/insert.md +++ b/docs/tutorial/insert.md @@ -171,7 +171,7 @@ The first step is to import the `Session` class: ```Python hl_lines="3" {!./docs_src/tutorial/insert/tutorial001.py[ln:1-3]!} -# Code below ommitted 👇 +# Code below omitted 👇 ```
diff --git a/docs/tutorial/limit-and-offset.md b/docs/tutorial/limit-and-offset.md index 3fb001cf97..c8b0ddf72f 100644 --- a/docs/tutorial/limit-and-offset.md +++ b/docs/tutorial/limit-and-offset.md @@ -93,7 +93,7 @@ $ python app.py // Previous output omitted 🙈 // Select with LIMIT -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age FROM hero LIMIT ? OFFSET ? INFO Engine [no key 0.00014s] (3, 0) @@ -165,7 +165,7 @@ $python app.py // Previous output omitted 🙈 // Select with LIMIT and OFFSET -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age FROM hero LIMIT ? OFFSET ? INFO Engine [no key 0.00020s] (3, 3) @@ -221,7 +221,7 @@ $ python app.py // Previous output omitted 🙈 // Select last batch with LIMIT and OFFSET -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age FROM hero LIMIT ? OFFSET ? INFO Engine [no key 0.00038s] (3, 6) @@ -241,7 +241,7 @@ You probably noticed the new SQL keywords `LIMIT` and `OFFSET`. You can use them in SQL, at the end of the other parts: ```SQL -SELECT id, name, secret_name, age +SELECT id, name, secret_name, age FROM hero LIMIT 3 OFFSET 6 ``` @@ -271,11 +271,11 @@ Of course, you can also combine `.limit()` and `.offset()` with `.where()` and o
-## 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:
@@ -284,18 +284,17 @@ $ python app.py // Previous output omitted 🙈 -// Select with WHERE and LIMIT -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +// 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') ] ``` diff --git a/docs/tutorial/many-to-many/create-data.md b/docs/tutorial/many-to-many/create-data.md index 22afb7ce7e..971659b9a6 100644 --- a/docs/tutorial/many-to-many/create-data.md +++ b/docs/tutorial/many-to-many/create-data.md @@ -122,16 +122,16 @@ INFO Engine COMMIT // Automatically start a new transaction INFO Engine BEGIN (implicit) // Refresh the data -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.id = ? INFO Engine [generated in 0.00019s] (1,) -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.001959s ago] (2,) -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.003215s ago] (3,) @@ -139,8 +139,8 @@ INFO Engine [cached since 0.003215s ago] (3,) Deadpond: name='Deadpond' age=None id=1 secret_name='Dive Wilson' // Accessing the .team attribute triggers a refresh -INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters -FROM team, heroteamlink +INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters +FROM team, heroteamlink WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id INFO Engine [generated in 0.00025s] (1,) @@ -151,8 +151,8 @@ Deadpond teams: [Team(id=1, name='Z-Force', headquarters='Sister Margaret’s Ba Rusty-Man: name='Rusty-Man' age=48 id=2 secret_name='Tommy Sharp' // Accessing the .team attribute triggers a refresh -INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters -FROM team, heroteamlink +INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters +FROM team, heroteamlink WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id INFO Engine [cached since 0.001716s ago] (2,) @@ -163,8 +163,8 @@ Rusty-Man Teams: [Team(id=2, name='Preventers', headquarters='Sharp Tower')] Spider-Boy: name='Spider-Boy' age=None id=3 secret_name='Pedro Parqueador' // Accessing the .team attribute triggers a refresh -INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters -FROM team, heroteamlink +INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters +FROM team, heroteamlink WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id INFO Engine [cached since 0.002739s ago] (3,) @@ -179,4 +179,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. ✨ diff --git a/docs/tutorial/many-to-many/create-models-with-link.md b/docs/tutorial/many-to-many/create-models-with-link.md index bc4481f73d..63cbf3eb82 100644 --- a/docs/tutorial/many-to-many/create-models-with-link.md +++ b/docs/tutorial/many-to-many/create-models-with-link.md @@ -151,35 +151,35 @@ $ python app.py // Boilerplate omitted 😉 -INFO Engine +INFO Engine CREATE TABLE team ( - id INTEGER, - name VARCHAR NOT NULL, - headquarters VARCHAR NOT NULL, + id INTEGER, + name VARCHAR NOT NULL, + headquarters VARCHAR NOT NULL, PRIMARY KEY (id) ) INFO Engine [no key 0.00033s] () -INFO Engine +INFO Engine CREATE TABLE hero ( - id INTEGER, - name VARCHAR NOT NULL, - secret_name VARCHAR NOT NULL, - age INTEGER, + id INTEGER, + name VARCHAR NOT NULL, + secret_name VARCHAR NOT NULL, + age INTEGER, PRIMARY KEY (id) ) INFO Engine [no key 0.00016s] () -INFO Engine +INFO Engine // Our shinny new link table ✨ CREATE TABLE heroteamlink ( - team_id INTEGER, - hero_id INTEGER, - PRIMARY KEY (team_id, hero_id), - FOREIGN KEY(team_id) REFERENCES team (id), + team_id INTEGER, + hero_id INTEGER, + PRIMARY KEY (team_id, hero_id), + FOREIGN KEY(team_id) REFERENCES team (id), FOREIGN KEY(hero_id) REFERENCES hero (id) ) diff --git a/docs/tutorial/many-to-many/link-with-extra-fields.md b/docs/tutorial/many-to-many/link-with-extra-fields.md index 9c3309da91..c998175a72 100644 --- a/docs/tutorial/many-to-many/link-with-extra-fields.md +++ b/docs/tutorial/many-to-many/link-with-extra-fields.md @@ -165,16 +165,16 @@ INFO Engine COMMIT INFO Engine BEGIN (implicit) // Automatically fetch the data on attribute access -INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters -FROM team +INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters +FROM team WHERE team.id = ? INFO Engine [generated in 0.00028s] (1,) -INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training -FROM heroteamlink +INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training +FROM heroteamlink WHERE ? = heroteamlink.team_id INFO Engine [generated in 0.00026s] (1,) -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero WHERE hero.id = ? INFO Engine [generated in 0.00024s] (1,) @@ -182,12 +182,12 @@ INFO Engine [generated in 0.00024s] (1,) Z-Force hero: name='Deadpond' age=None id=1 secret_name='Dive Wilson' is training: False // Automatically fetch the data on attribute access -INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters -FROM team +INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters +FROM team WHERE team.id = ? INFO Engine [cached since 0.008822s ago] (2,) -INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training -FROM heroteamlink +INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training +FROM heroteamlink WHERE ? = heroteamlink.team_id INFO Engine [cached since 0.005778s ago] (2,) @@ -195,8 +195,8 @@ INFO Engine [cached since 0.005778s ago] (2,) Preventers hero: name='Deadpond' age=None id=1 secret_name='Dive Wilson' is training: True // Automatically fetch the data on attribute access -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.004196s ago] (2,) @@ -204,8 +204,8 @@ INFO Engine [cached since 0.004196s ago] (2,) Preventers hero: name='Spider-Boy' age=None id=2 secret_name='Pedro Parqueador' is training: True // Automatically fetch the data on attribute access -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.006005s ago] (3,) @@ -253,14 +253,14 @@ $ python app.py INFO Engine BEGIN (implicit) // Select the hero -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.name = ? INFO Engine [no key 0.00014s] ('Spider-Boy',) // Select the team -INFO Engine SELECT team.id, team.name, team.headquarters -FROM team +INFO Engine SELECT team.id, team.name, team.headquarters +FROM team WHERE team.name = ? INFO Engine [no key 0.00012s] ('Z-Force',) @@ -269,18 +269,18 @@ INFO Engine INSERT INTO heroteamlink (team_id, hero_id, is_training) VALUES (?, INFO Engine [generated in 0.00023s] (1, 2, 1) // Automatically refresh the data on attribute access -INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training -FROM heroteamlink +INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training +FROM heroteamlink WHERE ? = heroteamlink.team_id INFO Engine [cached since 0.01514s ago] (1,) INFO Engine COMMIT INFO Engine BEGIN (implicit) -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.08953s ago] (2,) -INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training -FROM heroteamlink +INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training +FROM heroteamlink WHERE ? = heroteamlink.hero_id INFO Engine [generated in 0.00018s] (2,) @@ -291,18 +291,18 @@ Updated Spider-Boy's Teams: [ ] // Automatically refresh team data on attribute access -INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters -FROM team +INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters +FROM team WHERE team.id = ? INFO Engine [cached since 0.1084s ago] (1,) -INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training -FROM heroteamlink +INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training +FROM heroteamlink WHERE ? = heroteamlink.team_id INFO Engine [cached since 0.1054s ago] (1,) // Print team hero links Z-Force heroes: [ - HeroTeamLink(team_id=1, is_training=False, hero_id=1), + HeroTeamLink(team_id=1, is_training=False, hero_id=1), HeroTeamLink(team_id=1, is_training=True, hero_id=2) ] ``` @@ -350,8 +350,8 @@ $ python app.py // Previous output omitted 🙈 // Automatically fetch team data on attribute access -INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters -FROM team +INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters +FROM team WHERE team.id = ? INFO Engine [generated in 0.00015s] (2,) @@ -366,16 +366,16 @@ INFO Engine COMMIT INFO Engine BEGIN (implicit) // Automatically fetch data on attribute access -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.2004s ago] (2,) -INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training -FROM heroteamlink +INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training +FROM heroteamlink WHERE ? = heroteamlink.hero_id INFO Engine [cached since 0.1005s ago] (2,) -INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters -FROM team +INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters +FROM team WHERE team.id = ? INFO Engine [cached since 0.09707s ago] (2,) @@ -383,8 +383,8 @@ INFO Engine [cached since 0.09707s ago] (2,) Spider-Boy team: headquarters='Sharp Tower' id=2 name='Preventers' is training: False // Automatically fetch data on attribute access -INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters -FROM team +INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters +FROM team WHERE team.id = ? INFO Engine [cached since 0.2097s ago] (1,) diff --git a/docs/tutorial/many-to-many/update-remove-relationships.md b/docs/tutorial/many-to-many/update-remove-relationships.md index 5cc55e1f41..91f2f2c0e2 100644 --- a/docs/tutorial/many-to-many/update-remove-relationships.md +++ b/docs/tutorial/many-to-many/update-remove-relationships.md @@ -115,12 +115,12 @@ INFO Engine COMMIT INFO Engine BEGIN (implicit) // Automatically refresh the data while accessing the attribute .teams -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero WHERE hero.id = ? INFO Engine [generated in 0.00044s] (3,) -INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters -FROM team, heroteamlink +INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters +FROM team, heroteamlink WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id INFO Engine [cached since 0.1648s ago] (3,) @@ -131,8 +131,8 @@ Updated Spider-Boy's Teams: [ ] // Automatically refresh the data while accessing the attribute .heores -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero, heroteamlink +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero, heroteamlink WHERE ? = heroteamlink.team_id AND hero.id = heroteamlink.hero_id INFO Engine [cached since 0.1499s ago] (1,) @@ -208,12 +208,12 @@ INFO Engine COMMIT INFO Engine BEGIN (implicit) // Automatically refresh the data while accessing the attribute .heroes -INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters -FROM team +INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters +FROM team WHERE team.id = ? INFO Engine [generated in 0.00029s] (1,) -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero, heroteamlink +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero, heroteamlink WHERE ? = heroteamlink.team_id AND hero.id = heroteamlink.hero_id INFO Engine [cached since 0.5625s ago] (1,) @@ -223,12 +223,12 @@ Reverted Z-Force's heroes: [ ] // Automatically refresh the data while accessing the attribute .teams -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero WHERE hero.id = ? INFO Engine [cached since 0.4209s ago] (3,) -INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters -FROM team, heroteamlink +INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters +FROM team, heroteamlink WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id INFO Engine [cached since 0.5842s ago] (3,) diff --git a/docs/tutorial/one.md b/docs/tutorial/one.md index 3b60653ed9..eadfc62a37 100644 --- a/docs/tutorial/one.md +++ b/docs/tutorial/one.md @@ -12,7 +12,7 @@ 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.
👀 Full file preview @@ -86,8 +86,8 @@ $ python app.py // Some boilerplate output omitted 😉 // The SELECT with WHERE -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.age <= ? INFO Engine [no key 0.00021s] (35,) @@ -132,8 +132,8 @@ $ python app.py // Some boilerplate output omitted 😉 // The SELECT with WHERE -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.age <= ? INFO Engine [no key 0.00021s] (35,) @@ -180,8 +180,8 @@ $ python app.py // Some boilerplate output omitted 😉 // The SELECT with WHERE -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.name = ? INFO Engine [no key 0.00015s] ('Deadpond',) @@ -203,8 +203,8 @@ $ python app.py // Some boilerplate output omitted 😉 // The SELECT with WHERE -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.name = ? INFO Engine [no key 0.00015s] ('Deadpond',) @@ -274,8 +274,8 @@ $ python app.py // Some boilerplate output omitted 😉 // SELECT with WHERE -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.age < ? INFO Engine [no key 0.00014s] (25,) @@ -370,8 +370,8 @@ $ python app.py // Some boilerplate output omitted 😉 // SELECT with WHERE -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero WHERE hero.id = ? INFO Engine [generated in 0.00021s] (1,) @@ -413,8 +413,8 @@ $ python app.py // SELECT with WHERE INFO Engine BEGIN (implicit) -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age +FROM hero WHERE hero.id = ? INFO Engine [generated in 0.00024s] (9001,) diff --git a/docs/tutorial/relationship-attributes/back-populates.md b/docs/tutorial/relationship-attributes/back-populates.md index 86a4c2a70a..dbd3e8c1a3 100644 --- a/docs/tutorial/relationship-attributes/back-populates.md +++ b/docs/tutorial/relationship-attributes/back-populates.md @@ -123,7 +123,7 @@ Now let's update **Spider-Boy**, removing him from the team by setting `hero_spi
-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**. 🔥 @@ -144,10 +144,10 @@ But now, what happens when we print the `preventers_team.heroes`? ``` hl_lines="3" Preventers Team Heroes again: [ - Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2), - Hero(name='Spider-Boy', age=None, id=3, secret_name='Pedro Parqueador', team_id=2, team=None), - Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2), - Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2), + Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2), + Hero(name='Spider-Boy', age=None, id=3, secret_name='Pedro Parqueador', team_id=2, team=None), + Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2), + Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2), Hero(name='Captain North America', age=93, id=8, secret_name='Esteban Rogelios', team_id=2) ] ``` @@ -182,15 +182,15 @@ Now, if we commit it and print again: 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: ``` -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age, hero.team_id AS hero_team_id -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age, hero.team_id AS hero_team_id +FROM hero WHERE ? = hero.team_id 2021-08-13 11:15:24,658 INFO sqlalchemy.engine.Engine [cached since 0.1924s ago] (2,) Preventers Team Heroes after commit: [ - Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2), - Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2), - Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2), + Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2), + Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2), + Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2), Hero(name='Captain North America', age=93, id=8, secret_name='Esteban Rogelios', team_id=2) ] ``` @@ -260,9 +260,9 @@ That second print would output: ``` Preventers Team Heroes again: [ - Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2), - Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2), - Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2), + Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2), + Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2), + Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2), Hero(name='Captain North America', age=93, id=8, secret_name='Esteban Rogelios', team_id=2) ] ``` diff --git a/docs/tutorial/relationship-attributes/define-relationships-attributes.md b/docs/tutorial/relationship-attributes/define-relationships-attributes.md index 0531ec53e5..b6e77d9b45 100644 --- a/docs/tutorial/relationship-attributes/define-relationships-attributes.md +++ b/docs/tutorial/relationship-attributes/define-relationships-attributes.md @@ -115,9 +115,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 diff --git a/docs/tutorial/relationship-attributes/read-relationships.md b/docs/tutorial/relationship-attributes/read-relationships.md index 181b229589..78e4207ae5 100644 --- a/docs/tutorial/relationship-attributes/read-relationships.md +++ b/docs/tutorial/relationship-attributes/read-relationships.md @@ -52,7 +52,7 @@ With what we have learned **up to now**, we could use a `select()` statement, th ## 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: @@ -111,8 +111,8 @@ That would print a list with all the heroes in the Preventers team: $ python app.py // Automatically fetch the heroes -INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age, hero.team_id AS hero_team_id -FROM hero +INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age, hero.team_id AS hero_team_id +FROM hero WHERE ? = hero.team_id INFO Engine [cached since 0.8774s ago] (2,) diff --git a/docs/tutorial/select.md b/docs/tutorial/select.md index fb638c1212..e2f9af447c 100644 --- a/docs/tutorial/select.md +++ b/docs/tutorial/select.md @@ -190,7 +190,7 @@ First we have to import `select` from `sqlmodel` at the top of the file: ```Python hl_lines="3" {!./docs_src/tutorial/select/tutorial001.py[ln:1-3]!} -# More code below ommitted 👇 +# More code below omitted 👇 ```
@@ -274,7 +274,7 @@ This `session.exec(statement)` will generate this output: ``` INFO Engine BEGIN (implicit) -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age FROM hero INFO Engine [no key 0.00032s] () ``` @@ -455,7 +455,7 @@ In this chapter we are touching some of them. ### SQLModel's `select` -When importing from `sqlmodel` the `select()` function, you are using **SQLModel**'s version of `select`. +When importing from `sqlmodel` the `select()` function, you are using **SQLModel**'s version of `select`. SQLAchemy also has it's own `select`, and SQLModel's `select` uses SQLAlchemy's `select` internally. @@ -472,7 +472,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. ✨ diff --git a/docs/tutorial/update.md b/docs/tutorial/update.md index b3099f5a16..56cc7ac1a5 100644 --- a/docs/tutorial/update.md +++ b/docs/tutorial/update.md @@ -132,8 +132,8 @@ $ python app.py // Some boilerplate and previous output omitted 😉 // The SELECT with WHERE -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.name = ? INFO Engine [no key 0.00017s] ('Spider-Boy',) @@ -275,8 +275,8 @@ $ python app.py // Previous output omitted 🙈 // The SQL to SELECT the fresh hero data -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.id = ? INFO Engine [generated in 0.00018s] (2,) ``` diff --git a/docs/tutorial/where.md b/docs/tutorial/where.md index ca85a4dd00..a3bf6b0529 100644 --- a/docs/tutorial/where.md +++ b/docs/tutorial/where.md @@ -206,7 +206,7 @@ We care specially about the **select** statement: ## 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: ```Python hl_lines="5" # Code above omitted 👆 @@ -490,8 +490,8 @@ $ python app.py // Now the important part, the SELECT with WHERE 💡 -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.name = ? INFO Engine [no key 0.00014s] ('Deadpond',) @@ -726,8 +726,8 @@ This will select the rows `WHERE` the `age` is **greater than or equal** to `35` The equivalent SQL would be: ```SQL hl_lines="3" -SELECT id, name, secret_name, age -FROM hero +SELECT id, name, secret_name, age +FROM hero WHERE age >= 35 AND age < 40 ``` @@ -743,12 +743,12 @@ $ python app.py // Some boilerplate output omitted 😉 // The SELECT statement with WHERE, also using AND -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +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' @@ -838,8 +838,8 @@ $ python app.py // Some boilerplate output omitted 😉 // The SELECT statement with WHERE, also using OR 🔍 -INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age -FROM hero +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero WHERE hero.age <= ? OR hero.age > ? INFO Engine [no key 0.00021s] (35, 90) diff --git a/docs_src/tutorial/automatic_id_none_refresh/annotations/en/tutorial002.md b/docs_src/tutorial/automatic_id_none_refresh/annotations/en/tutorial002.md index fd33fec778..725fcb6601 100644 --- a/docs_src/tutorial/automatic_id_none_refresh/annotations/en/tutorial002.md +++ b/docs_src/tutorial/automatic_id_none_refresh/annotations/en/tutorial002.md @@ -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: @@ -181,8 +181,8 @@ ``` INFO Engine BEGIN (implicit) - INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age - FROM hero + INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age + FROM hero WHERE hero.id = ? INFO Engine [generated in 0.00017s] (1,) @@ -196,8 +196,8 @@ Generates the output: ``` - INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age - FROM hero + INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age + FROM hero WHERE hero.id = ? INFO Engine [cached since 0.001245s ago] (2,) @@ -211,8 +211,8 @@ Generates the output: ``` - INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age - FROM hero + INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age + FROM hero WHERE hero.id = ? INFO Engine [cached since 0.002215s ago] (3,) @@ -265,8 +265,8 @@ Generates the output: ``` - INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age - FROM hero + INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age + FROM hero WHERE hero.id = ? INFO Engine [generated in 0.00024s] (1,) ``` @@ -278,8 +278,8 @@ Generates the output: ``` - INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age - FROM hero + INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age + FROM hero WHERE hero.id = ? INFO Engine [cached since 0.001487s ago] (2,) ``` @@ -291,8 +291,8 @@ Generates the output: ``` - INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age - FROM hero + INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age + FROM hero WHERE hero.id = ? INFO Engine [cached since 0.002377s ago] (3,) ``` diff --git a/docs_src/tutorial/delete/annotations/en/tutorial002.md b/docs_src/tutorial/delete/annotations/en/tutorial002.md index 130016daec..28dcc50fb3 100644 --- a/docs_src/tutorial/delete/annotations/en/tutorial002.md +++ b/docs_src/tutorial/delete/annotations/en/tutorial002.md @@ -6,8 +6,8 @@ ``` INFO Engine BEGIN (implicit) - INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age - FROM hero + INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age + FROM hero WHERE hero.name = ? INFO Engine [no key 0.00011s] ('Spider-Youngster',) ``` @@ -65,8 +65,8 @@ ``` INFO Engine BEGIN (implicit) - INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age - FROM hero + INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age + FROM hero WHERE hero.name = ? INFO Engine [no key 0.00013s] ('Spider-Youngster',) ``` diff --git a/docs_src/tutorial/fastapi/app_testing/tutorial001/main.py b/docs_src/tutorial/fastapi/app_testing/tutorial001/main.py index 88b8fbbcea..3f0602e4b4 100644 --- a/docs_src/tutorial/fastapi/app_testing/tutorial001/main.py +++ b/docs_src/tutorial/fastapi/app_testing/tutorial001/main.py @@ -66,7 +66,7 @@ def read_heroes( *, session: Session = Depends(get_session), offset: int = 0, - limit: int = Query(default=100, lte=100), + limit: int = Query(default=100, le=100), ): heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes @@ -98,7 +98,6 @@ def update_hero( @app.delete("/heroes/{hero_id}") def delete_hero(*, session: Session = Depends(get_session), hero_id: int): - hero = session.get(Hero, hero_id) if not hero: raise HTTPException(status_code=404, detail="Hero not found") diff --git a/docs_src/tutorial/fastapi/delete/tutorial001.py b/docs_src/tutorial/fastapi/delete/tutorial001.py index 3c15efbb2d..3069fc5e87 100644 --- a/docs_src/tutorial/fastapi/delete/tutorial001.py +++ b/docs_src/tutorial/fastapi/delete/tutorial001.py @@ -58,7 +58,7 @@ def create_hero(hero: HeroCreate): @app.get("/heroes/", response_model=List[HeroRead]) -def read_heroes(offset: int = 0, limit: int = Query(default=100, lte=100)): +def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): with Session(engine) as session: heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes diff --git a/docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py b/docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py index aef21332a7..2b8739ca70 100644 --- a/docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py +++ b/docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py @@ -52,7 +52,7 @@ def create_hero(hero: HeroCreate): @app.get("/heroes/", response_model=List[HeroRead]) -def read_heroes(offset: int = 0, limit: int = Query(default=100, lte=100)): +def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): with Session(engine) as session: heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes diff --git a/docs_src/tutorial/fastapi/relationships/tutorial001.py b/docs_src/tutorial/fastapi/relationships/tutorial001.py index 97220b95e5..8477e4a2a0 100644 --- a/docs_src/tutorial/fastapi/relationships/tutorial001.py +++ b/docs_src/tutorial/fastapi/relationships/tutorial001.py @@ -104,7 +104,7 @@ def read_heroes( *, session: Session = Depends(get_session), offset: int = 0, - limit: int = Query(default=100, lte=100), + limit: int = Query(default=100, le=100), ): heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes @@ -136,7 +136,6 @@ def update_hero( @app.delete("/heroes/{hero_id}") def delete_hero(*, session: Session = Depends(get_session), hero_id: int): - hero = session.get(Hero, hero_id) if not hero: raise HTTPException(status_code=404, detail="Hero not found") @@ -159,7 +158,7 @@ def read_teams( *, session: Session = Depends(get_session), offset: int = 0, - limit: int = Query(default=100, lte=100), + limit: int = Query(default=100, le=100), ): teams = session.exec(select(Team).offset(offset).limit(limit)).all() return teams diff --git a/docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py b/docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py index 88b8fbbcea..3f0602e4b4 100644 --- a/docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py +++ b/docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py @@ -66,7 +66,7 @@ def read_heroes( *, session: Session = Depends(get_session), offset: int = 0, - limit: int = Query(default=100, lte=100), + limit: int = Query(default=100, le=100), ): heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes @@ -98,7 +98,6 @@ def update_hero( @app.delete("/heroes/{hero_id}") def delete_hero(*, session: Session = Depends(get_session), hero_id: int): - hero = session.get(Hero, hero_id) if not hero: raise HTTPException(status_code=404, detail="Hero not found") diff --git a/docs_src/tutorial/fastapi/teams/tutorial001.py b/docs_src/tutorial/fastapi/teams/tutorial001.py index 2a0bd600fc..1da0dad8a2 100644 --- a/docs_src/tutorial/fastapi/teams/tutorial001.py +++ b/docs_src/tutorial/fastapi/teams/tutorial001.py @@ -95,7 +95,7 @@ def read_heroes( *, session: Session = Depends(get_session), offset: int = 0, - limit: int = Query(default=100, lte=100), + limit: int = Query(default=100, le=100), ): heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes @@ -127,7 +127,6 @@ def update_hero( @app.delete("/heroes/{hero_id}") def delete_hero(*, session: Session = Depends(get_session), hero_id: int): - hero = session.get(Hero, hero_id) if not hero: raise HTTPException(status_code=404, detail="Hero not found") @@ -150,7 +149,7 @@ def read_teams( *, session: Session = Depends(get_session), offset: int = 0, - limit: int = Query(default=100, lte=100), + limit: int = Query(default=100, le=100), ): teams = session.exec(select(Team).offset(offset).limit(limit)).all() return teams diff --git a/docs_src/tutorial/fastapi/update/tutorial001.py b/docs_src/tutorial/fastapi/update/tutorial001.py index 35554878db..bb98efd581 100644 --- a/docs_src/tutorial/fastapi/update/tutorial001.py +++ b/docs_src/tutorial/fastapi/update/tutorial001.py @@ -58,7 +58,7 @@ def create_hero(hero: HeroCreate): @app.get("/heroes/", response_model=List[HeroRead]) -def read_heroes(offset: int = 0, limit: int = Query(default=100, lte=100)): +def read_heroes(offset: int = 0, limit: int = Query(default=100, le=100)): with Session(engine) as session: heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() return heroes diff --git a/docs_src/tutorial/offset_and_limit/tutorial004.py b/docs_src/tutorial/offset_and_limit/tutorial004.py index a95715cd98..43828b853f 100644 --- a/docs_src/tutorial/offset_and_limit/tutorial004.py +++ b/docs_src/tutorial/offset_and_limit/tutorial004.py @@ -43,7 +43,7 @@ def create_heroes(): def select_heroes(): with Session(engine) as session: - statement = select(Hero).where(Hero.age > 32).limit(3) + statement = select(Hero).where(Hero.age > 32).offset(1).limit(2) results = session.exec(statement) heroes = results.all() print(heroes) diff --git a/docs_src/tutorial/select/annotations/en/tutorial002.md b/docs_src/tutorial/select/annotations/en/tutorial002.md index 2570b542c4..312bd81a94 100644 --- a/docs_src/tutorial/select/annotations/en/tutorial002.md +++ b/docs_src/tutorial/select/annotations/en/tutorial002.md @@ -35,7 +35,7 @@ ``` INFO Engine BEGIN (implicit) - INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age + INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age FROM hero INFO Engine [no key 0.00032s] () ``` diff --git a/docs_src/tutorial/update/annotations/en/tutorial002.md b/docs_src/tutorial/update/annotations/en/tutorial002.md index 3a52bd9bdf..eb1a820c0b 100644 --- a/docs_src/tutorial/update/annotations/en/tutorial002.md +++ b/docs_src/tutorial/update/annotations/en/tutorial002.md @@ -5,8 +5,8 @@ This generates the output: ``` - INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age - FROM hero + INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age + FROM hero WHERE hero.name = ? INFO Engine [no key 0.00017s] ('Spider-Boy',) ``` @@ -53,8 +53,8 @@ This generates the output: ``` - INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age - FROM hero + INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age + FROM hero WHERE hero.id = ? INFO Engine [generated in 0.00018s] (2,) ``` diff --git a/docs_src/tutorial/update/annotations/en/tutorial004.md b/docs_src/tutorial/update/annotations/en/tutorial004.md index 55755cd88d..8378d1b3a7 100644 --- a/docs_src/tutorial/update/annotations/en/tutorial004.md +++ b/docs_src/tutorial/update/annotations/en/tutorial004.md @@ -5,8 +5,8 @@ This generates the output: ``` - INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age - FROM hero + INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age + FROM hero WHERE hero.name = ? INFO Engine [no key 0.00018s] ('Spider-Boy',) ``` @@ -29,8 +29,8 @@ ``` INFO Engine BEGIN (implicit) - INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age - FROM hero + INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age + FROM hero WHERE hero.name = ? INFO Engine [no key 0.00020s] ('Captain North America',) ``` @@ -109,8 +109,8 @@ ``` INFO Engine BEGIN (implicit) - INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age - FROM hero + INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age + FROM hero WHERE hero.id = ? INFO Engine [generated in 0.00023s] (2,) ``` @@ -123,8 +123,8 @@ This generates the output: ``` - INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age - FROM hero + INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age + FROM hero WHERE hero.id = ? INFO Engine [cached since 0.001709s ago] (7,) ``` @@ -132,7 +132,7 @@ !!! tip SQLAlchemy is still using the previous transaction, so it doesn't have to create a new one. -18. Print the first hero, now udpated. +18. Print the first hero, now updated. This generates the output: diff --git a/pyproject.toml b/pyproject.toml index 9561cd2761..aa78144892 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,25 +31,22 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.7" -SQLAlchemy = ">=1.4.17,<=1.4.41" +SQLAlchemy = ">=1.4.36,<2.0.0" pydantic = "^1.8.2" sqlalchemy2-stubs = {version = "*", allow-prereleases = true} -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] pytest = "^7.0.1" mypy = "0.971" -flake8 = "^5.0.4" black = "^22.10.0" -mkdocs = "^1.2.1" -mkdocs-material = "^8.1.4" +mkdocs-material = "9.1.21" pillow = "^9.3.0" cairosvg = "^2.5.2" mdx-include = "^1.4.1" coverage = {extras = ["toml"], version = "^6.2"} fastapi = "^0.68.1" requests = "^2.26.0" -autoflake = "^1.4" -isort = "^5.9.3" +ruff = "^0.1.1" testcontainers = "^3.7.1" psycopg2-binary = "^2.9.7" asyncpg = "^0.28.0" @@ -78,33 +75,30 @@ exclude_lines = [ "if TYPE_CHECKING:", ] -[tool.isort] -profile = "black" -known_third_party = ["sqlmodel"] -skip_glob = [ - "sqlmodel/__init__.py", - ] - - [tool.mypy] -# --strict -disallow_any_generics = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_defs = true -disallow_incomplete_defs = true -check_untyped_defs = true -disallow_untyped_decorators = true -no_implicit_optional = true -warn_redundant_casts = true -warn_unused_ignores = true -warn_return_any = true -implicit_reexport = false -strict_equality = true -# --strict end +strict = true [[tool.mypy.overrides]] module = "sqlmodel.sql.expression" warn_unused_ignores = false -# invalidate CI cache: 1 +[tool.ruff] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "C", # flake8-comprehensions + "B", # flake8-bugbear +] +ignore = [ + "E501", # line too long, handled by black + "B008", # do not perform function calls in argument defaults + "C901", # too complex +] + +[tool.ruff.per-file-ignores] +# "__init__.py" = ["F401"] + +[tool.ruff.isort] +known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"] diff --git a/scripts/docs-live.sh b/scripts/docs-live.sh index 5342a9e59f..f9d31ba361 100755 --- a/scripts/docs-live.sh +++ b/scripts/docs-live.sh @@ -2,4 +2,6 @@ set -e +export DYLD_FALLBACK_LIBRARY_PATH="/opt/homebrew/lib" + mkdocs serve --dev-addr 127.0.0.1:8008 diff --git a/scripts/format.sh b/scripts/format.sh index 0d456398fb..b6aebd10d4 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -1,6 +1,5 @@ #!/bin/sh -e set -x -autoflake --remove-all-unused-imports --recursive --remove-unused-variables --in-place sqlmodel docs_src tests --exclude=__init__.py -black sqlmodel tests docs_src -isort sqlmodel tests docs_src +ruff sqlmodel tests docs_src scripts --fix +black sqlmodel tests docs_src scripts diff --git a/scripts/lint.sh b/scripts/lint.sh index 4191d90f1f..b328e3d9ac 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -4,6 +4,5 @@ set -e set -x mypy sqlmodel -flake8 sqlmodel tests docs_src +ruff sqlmodel tests docs_src scripts black sqlmodel tests docs_src --check -isort sqlmodel tests docs_src scripts --check-only diff --git a/sqlmodel/__init__.py b/sqlmodel/__init__.py index 720aa8c929..b51084b3fc 100644 --- a/sqlmodel/__init__.py +++ b/sqlmodel/__init__.py @@ -1,16 +1,16 @@ -__version__ = "0.0.8" +__version__ = "0.0.9" # Re-export from SQLAlchemy from sqlalchemy.engine import create_mock_engine as create_mock_engine from sqlalchemy.engine import engine_from_config as engine_from_config from sqlalchemy.inspection import inspect as inspect from sqlalchemy.schema import BLANK_SCHEMA as BLANK_SCHEMA +from sqlalchemy.schema import DDL as DDL from sqlalchemy.schema import CheckConstraint as CheckConstraint from sqlalchemy.schema import Column as Column from sqlalchemy.schema import ColumnDefault as ColumnDefault from sqlalchemy.schema import Computed as Computed from sqlalchemy.schema import Constraint as Constraint -from sqlalchemy.schema import DDL as DDL from sqlalchemy.schema import DefaultClause as DefaultClause from sqlalchemy.schema import FetchedValue as FetchedValue from sqlalchemy.schema import ForeignKey as ForeignKey @@ -23,6 +23,14 @@ from sqlalchemy.schema import Table as Table from sqlalchemy.schema import ThreadLocalMetaData as ThreadLocalMetaData from sqlalchemy.schema import UniqueConstraint as UniqueConstraint +from sqlalchemy.sql import LABEL_STYLE_DEFAULT as LABEL_STYLE_DEFAULT +from sqlalchemy.sql import ( + LABEL_STYLE_DISAMBIGUATE_ONLY as LABEL_STYLE_DISAMBIGUATE_ONLY, +) +from sqlalchemy.sql import LABEL_STYLE_NONE as LABEL_STYLE_NONE +from sqlalchemy.sql import ( + LABEL_STYLE_TABLENAME_PLUS_COL as LABEL_STYLE_TABLENAME_PLUS_COL, +) from sqlalchemy.sql import alias as alias from sqlalchemy.sql import all_ as all_ from sqlalchemy.sql import and_ as and_ @@ -48,14 +56,6 @@ from sqlalchemy.sql import intersect as intersect from sqlalchemy.sql import intersect_all as intersect_all from sqlalchemy.sql import join as join -from sqlalchemy.sql import LABEL_STYLE_DEFAULT as LABEL_STYLE_DEFAULT -from sqlalchemy.sql import ( - LABEL_STYLE_DISAMBIGUATE_ONLY as LABEL_STYLE_DISAMBIGUATE_ONLY, -) -from sqlalchemy.sql import LABEL_STYLE_NONE as LABEL_STYLE_NONE -from sqlalchemy.sql import ( - LABEL_STYLE_TABLENAME_PLUS_COL as LABEL_STYLE_TABLENAME_PLUS_COL, -) from sqlalchemy.sql import lambda_stmt as lambda_stmt from sqlalchemy.sql import lateral as lateral from sqlalchemy.sql import literal as literal @@ -85,55 +85,53 @@ from sqlalchemy.sql import within_group as within_group from sqlalchemy.types import ARRAY as ARRAY from sqlalchemy.types import BIGINT as BIGINT -from sqlalchemy.types import BigInteger as BigInteger from sqlalchemy.types import BINARY as BINARY from sqlalchemy.types import BLOB as BLOB from sqlalchemy.types import BOOLEAN as BOOLEAN -from sqlalchemy.types import Boolean as Boolean from sqlalchemy.types import CHAR as CHAR from sqlalchemy.types import CLOB as CLOB from sqlalchemy.types import DATE as DATE -from sqlalchemy.types import Date as Date from sqlalchemy.types import DATETIME as DATETIME -from sqlalchemy.types import DateTime as DateTime from sqlalchemy.types import DECIMAL as DECIMAL -from sqlalchemy.types import Enum as Enum from sqlalchemy.types import FLOAT as FLOAT -from sqlalchemy.types import Float as Float from sqlalchemy.types import INT as INT from sqlalchemy.types import INTEGER as INTEGER -from sqlalchemy.types import Integer as Integer -from sqlalchemy.types import Interval as Interval from sqlalchemy.types import JSON as JSON -from sqlalchemy.types import LargeBinary as LargeBinary from sqlalchemy.types import NCHAR as NCHAR from sqlalchemy.types import NUMERIC as NUMERIC -from sqlalchemy.types import Numeric as Numeric from sqlalchemy.types import NVARCHAR as NVARCHAR -from sqlalchemy.types import PickleType as PickleType from sqlalchemy.types import REAL as REAL from sqlalchemy.types import SMALLINT as SMALLINT +from sqlalchemy.types import TEXT as TEXT +from sqlalchemy.types import TIME as TIME +from sqlalchemy.types import TIMESTAMP as TIMESTAMP +from sqlalchemy.types import VARBINARY as VARBINARY +from sqlalchemy.types import VARCHAR as VARCHAR +from sqlalchemy.types import BigInteger as BigInteger +from sqlalchemy.types import Boolean as Boolean +from sqlalchemy.types import Date as Date +from sqlalchemy.types import DateTime as DateTime +from sqlalchemy.types import Enum as Enum +from sqlalchemy.types import Float as Float +from sqlalchemy.types import Integer as Integer +from sqlalchemy.types import Interval as Interval +from sqlalchemy.types import LargeBinary as LargeBinary +from sqlalchemy.types import Numeric as Numeric +from sqlalchemy.types import PickleType as PickleType from sqlalchemy.types import SmallInteger as SmallInteger from sqlalchemy.types import String as String -from sqlalchemy.types import TEXT as TEXT from sqlalchemy.types import Text as Text -from sqlalchemy.types import TIME as TIME from sqlalchemy.types import Time as Time -from sqlalchemy.types import TIMESTAMP as TIMESTAMP from sqlalchemy.types import TypeDecorator as TypeDecorator from sqlalchemy.types import Unicode as Unicode from sqlalchemy.types import UnicodeText as UnicodeText -from sqlalchemy.types import VARBINARY as VARBINARY -from sqlalchemy.types import VARCHAR as VARCHAR -# Extensions and modifications of SQLAlchemy in SQLModel +# From SQLModel, modifications of SQLAlchemy or equivalents of Pydantic from .engine.create import create_engine as create_engine +from .main import Field as Field +from .main import Relationship as Relationship +from .main import SQLModel as SQLModel from .orm.session import Session as Session -from .sql.expression import select as select from .sql.expression import col as col +from .sql.expression import select as select from .sql.sqltypes import AutoString as AutoString - -# Export SQLModel specifics (equivalent to Pydantic) -from .main import SQLModel as SQLModel -from .main import Field as Field -from .main import Relationship as Relationship diff --git a/sqlmodel/default.py b/sqlmodel/default.py index bb44972e24..e8e37a5566 100644 --- a/sqlmodel/default.py +++ b/sqlmodel/default.py @@ -6,7 +6,7 @@ class _DefaultPlaceholder: You shouldn't use this class directly. It's used internally to recognize when a default value has been overwritten, even - if the overriden default value was truthy. + if the overridden default value was truthy. """ def __init__(self, value: Any): @@ -27,6 +27,6 @@ def Default(value: _TDefaultType) -> _TDefaultType: You shouldn't use this function directly. It's used internally to recognize when a default value has been overwritten, even - if the overriden default value was truthy. + if the overridden default value was truthy. """ return _DefaultPlaceholder(value) # type: ignore diff --git a/sqlmodel/ext/asyncio/session.py b/sqlmodel/ext/asyncio/session.py index 79dae568a6..f500c44dc2 100644 --- a/sqlmodel/ext/asyncio/session.py +++ b/sqlmodel/ext/asyncio/session.py @@ -1,17 +1,17 @@ -from typing import Any, Mapping, Optional, Sequence, TypeVar, Union +from typing import Any, Mapping, Optional, Sequence, TypeVar, Union, overload from sqlalchemy import util from sqlalchemy.ext.asyncio import AsyncSession as _AsyncSession from sqlalchemy.ext.asyncio import engine from sqlalchemy.ext.asyncio.engine import AsyncConnection, AsyncEngine from sqlalchemy.util.concurrency import greenlet_spawn -from sqlmodel.sql.base import Executable -from ...engine.result import ScalarResult +from ...engine.result import Result, ScalarResult from ...orm.session import Session +from ...sql.base import Executable from ...sql.expression import Select, SelectOfScalar -_T = TypeVar("_T") +_TSelectParam = TypeVar("_TSelectParam") class AsyncSession(_AsyncSession): @@ -40,14 +40,46 @@ def __init__( Session(bind=bind, binds=binds, **kw) # type: ignore ) + @overload async def exec( self, - statement: Union[Select[_T], SelectOfScalar[_T], Executable[_T]], + statement: Select[_TSelectParam], + *, + params: Optional[Union[Mapping[str, Any], Sequence[Mapping[str, Any]]]] = None, + execution_options: Mapping[str, Any] = util.EMPTY_DICT, + bind_arguments: Optional[Mapping[str, Any]] = None, + _parent_execute_state: Optional[Any] = None, + _add_event: Optional[Any] = None, + **kw: Any, + ) -> Result[_TSelectParam]: + ... + + @overload + async def exec( + self, + statement: SelectOfScalar[_TSelectParam], + *, + params: Optional[Union[Mapping[str, Any], Sequence[Mapping[str, Any]]]] = None, + execution_options: Mapping[str, Any] = util.EMPTY_DICT, + bind_arguments: Optional[Mapping[str, Any]] = None, + _parent_execute_state: Optional[Any] = None, + _add_event: Optional[Any] = None, + **kw: Any, + ) -> ScalarResult[_TSelectParam]: + ... + + async def exec( + self, + statement: Union[ + Select[_TSelectParam], + SelectOfScalar[_TSelectParam], + Executable[_TSelectParam], + ], params: Optional[Union[Mapping[str, Any], Sequence[Mapping[str, Any]]]] = None, execution_options: Mapping[Any, Any] = util.EMPTY_DICT, bind_arguments: Optional[Mapping[str, Any]] = None, **kw: Any, - ) -> ScalarResult[_T]: + ) -> Union[Result[_TSelectParam], ScalarResult[_TSelectParam]]: # TODO: the documentation says execution_options accepts a dict, but only # util.immutabledict has the union() method. Is this a bug in SQLAlchemy? execution_options = execution_options.union({"prebuffer_rows": True}) # type: ignore diff --git a/sqlmodel/main.py b/sqlmodel/main.py index 5b5950a811..07e600e4d4 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -26,15 +26,24 @@ from pydantic import BaseConfig, BaseModel from pydantic.errors import ConfigError, DictError -from pydantic.fields import SHAPE_SINGLETON +from pydantic.fields import SHAPE_SINGLETON, ModelField, Undefined, UndefinedType from pydantic.fields import FieldInfo as PydanticFieldInfo -from pydantic.fields import ModelField, Undefined, UndefinedType from pydantic.main import ModelMetaclass, validate_model from pydantic.typing import NoArgAnyCallable, resolve_annotations from pydantic.utils import ROOT_KEY, Representation -from sqlalchemy import Boolean, Column, Date, DateTime +from sqlalchemy import ( + Boolean, + Column, + Date, + DateTime, + Float, + ForeignKey, + Integer, + Interval, + Numeric, + inspect, +) from sqlalchemy import Enum as sa_Enum -from sqlalchemy import Float, ForeignKey, Integer, Interval, Numeric, inspect from sqlalchemy.orm import RelationshipProperty, declared_attr, registry, relationship from sqlalchemy.orm.attributes import set_attribute from sqlalchemy.orm.decl_api import DeclarativeMeta @@ -305,9 +314,9 @@ def get_config(name: str) -> Any: config_registry = cast(registry, config_registry) # If it was passed by kwargs, ensure it's also set in config new_cls.__config__.registry = config_table - setattr(new_cls, "_sa_registry", config_registry) - setattr(new_cls, "metadata", config_registry.metadata) - setattr(new_cls, "__abstract__", True) + setattr(new_cls, "_sa_registry", config_registry) # noqa: B010 + setattr(new_cls, "metadata", config_registry.metadata) # noqa: B010 + setattr(new_cls, "__abstract__", True) # noqa: B010 return new_cls # Override SQLAlchemy, allow both SQLAlchemy and plain Pydantic models @@ -320,19 +329,16 @@ def __init__( # triggers an error base_is_table = False for base in bases: - config = getattr(base, "__config__") + config = getattr(base, "__config__") # noqa: B009 if config and getattr(config, "table", False): base_is_table = True break if getattr(cls.__config__, "table", False) and not base_is_table: - dict_used = dict_.copy() - for field_name, field_value in cls.__fields__.items(): - dict_used[field_name] = get_column_from_field(field_value) for rel_name, rel_info in cls.__sqlmodel_relationships__.items(): if rel_info.sa_relationship: # There's a SQLAlchemy relationship declared, that takes precedence # over anything else, use that and continue with the next attribute - dict_used[rel_name] = rel_info.sa_relationship + setattr(cls, rel_name, rel_info.sa_relationship) # Fix #315 continue ann = cls.__annotations__[rel_name] temp_field = ModelField.infer( @@ -350,7 +356,7 @@ def __init__( rel_kwargs["back_populates"] = rel_info.back_populates if rel_info.link_model: ins = inspect(rel_info.link_model) - local_table = getattr(ins, "local_table") + local_table = getattr(ins, "local_table") # noqa: B009 if local_table is None: raise RuntimeError( "Couldn't find the secondary table for " @@ -365,53 +371,57 @@ def __init__( rel_value: RelationshipProperty = relationship( # type: ignore relationship_to, *rel_args, **rel_kwargs ) - dict_used[rel_name] = rel_value setattr(cls, rel_name, rel_value) # Fix #315 - DeclarativeMeta.__init__(cls, classname, bases, dict_used, **kw) + # SQLAlchemy no longer uses dict_ + # Ref: https://github.com/sqlalchemy/sqlalchemy/commit/428ea01f00a9cc7f85e435018565eb6da7af1b77 + # Tag: 1.4.36 + DeclarativeMeta.__init__(cls, classname, bases, dict_, **kw) else: ModelMetaclass.__init__(cls, classname, bases, dict_, **kw) def get_sqlalchemy_type(field: ModelField) -> Any: - if issubclass(field.type_, str): - if field.field_info.max_length: - return AutoString(length=field.field_info.max_length) - return AutoString - if issubclass(field.type_, float): - return Float - if issubclass(field.type_, bool): - return Boolean - if issubclass(field.type_, int): - return Integer - if issubclass(field.type_, datetime): - return DateTime - if issubclass(field.type_, date): - return Date - if issubclass(field.type_, timedelta): - return Interval - if issubclass(field.type_, time): - return Time - if issubclass(field.type_, Enum): - return sa_Enum(field.type_) - if issubclass(field.type_, bytes): - return LargeBinary - if issubclass(field.type_, Decimal): - return Numeric( - precision=getattr(field.type_, "max_digits", None), - scale=getattr(field.type_, "decimal_places", None), - ) - if issubclass(field.type_, ipaddress.IPv4Address): - return AutoString - if issubclass(field.type_, ipaddress.IPv4Network): - return AutoString - if issubclass(field.type_, ipaddress.IPv6Address): - return AutoString - if issubclass(field.type_, ipaddress.IPv6Network): - return AutoString - if issubclass(field.type_, Path): - return AutoString - if issubclass(field.type_, uuid.UUID): - return GUID + if isinstance(field.type_, type) and field.shape == SHAPE_SINGLETON: + # Check enums first as an enum can also be a str, needed by Pydantic/FastAPI + if issubclass(field.type_, Enum): + return sa_Enum(field.type_) + if issubclass(field.type_, str): + if field.field_info.max_length: + return AutoString(length=field.field_info.max_length) + return AutoString + if issubclass(field.type_, float): + return Float + if issubclass(field.type_, bool): + return Boolean + if issubclass(field.type_, int): + return Integer + if issubclass(field.type_, datetime): + return DateTime + if issubclass(field.type_, date): + return Date + if issubclass(field.type_, timedelta): + return Interval + if issubclass(field.type_, time): + return Time + if issubclass(field.type_, bytes): + return LargeBinary + if issubclass(field.type_, Decimal): + return Numeric( + precision=getattr(field.type_, "max_digits", None), + scale=getattr(field.type_, "decimal_places", None), + ) + if issubclass(field.type_, ipaddress.IPv4Address): + return AutoString + if issubclass(field.type_, ipaddress.IPv4Network): + return AutoString + if issubclass(field.type_, ipaddress.IPv6Address): + return AutoString + if issubclass(field.type_, ipaddress.IPv6Network): + return AutoString + if issubclass(field.type_, Path): + return AutoString + if issubclass(field.type_, uuid.UUID): + return GUID raise ValueError(f"The field {field.name} has no matching SQLAlchemy type") @@ -428,7 +438,7 @@ def get_column_from_field(field: ModelField) -> Column: # type: ignore # Override derived nullability if the nullable property is set explicitly # on the field if hasattr(field.field_info, "nullable"): - field_nullable = getattr(field.field_info, "nullable") + field_nullable = getattr(field.field_info, "nullable") # noqa: B009 if field_nullable != Undefined: nullable = field_nullable args = [] diff --git a/sqlmodel/orm/session.py b/sqlmodel/orm/session.py index 1692fdcbcb..0c70c290ae 100644 --- a/sqlmodel/orm/session.py +++ b/sqlmodel/orm/session.py @@ -4,11 +4,11 @@ from sqlalchemy.orm import Query as _Query from sqlalchemy.orm import Session as _Session from sqlalchemy.sql.base import Executable as _Executable -from sqlmodel.sql.expression import Select, SelectOfScalar from typing_extensions import Literal from ..engine.result import Result, ScalarResult from ..sql.base import Executable +from ..sql.expression import Select, SelectOfScalar _TSelectParam = TypeVar("_TSelectParam") diff --git a/sqlmodel/sql/sqltypes.py b/sqlmodel/sql/sqltypes.py index 09b8239476..17d9b06126 100644 --- a/sqlmodel/sql/sqltypes.py +++ b/sqlmodel/sql/sqltypes.py @@ -8,7 +8,6 @@ class AutoString(types.TypeDecorator): # type: ignore - impl = types.String cache_ok = True mysql_default_length = 255 diff --git a/tests/test_enums.py b/tests/test_enums.py index aeec6456da..194bdefea1 100644 --- a/tests/test_enums.py +++ b/tests/test_enums.py @@ -14,12 +14,12 @@ """ -class MyEnum1(enum.Enum): +class MyEnum1(str, enum.Enum): A = "A" B = "B" -class MyEnum2(enum.Enum): +class MyEnum2(str, enum.Enum): C = "C" D = "D" @@ -70,3 +70,43 @@ def test_sqlite_ddl_sql(capsys): captured = capsys.readouterr() assert "enum_field VARCHAR(1) NOT NULL" in captured.out assert "CREATE TYPE" not in captured.out + + +def test_json_schema_flat_model(): + assert FlatModel.schema() == { + "title": "FlatModel", + "type": "object", + "properties": { + "id": {"title": "Id", "type": "string", "format": "uuid"}, + "enum_field": {"$ref": "#/definitions/MyEnum1"}, + }, + "required": ["id", "enum_field"], + "definitions": { + "MyEnum1": { + "title": "MyEnum1", + "description": "An enumeration.", + "enum": ["A", "B"], + "type": "string", + } + }, + } + + +def test_json_schema_inherit_model(): + assert InheritModel.schema() == { + "title": "InheritModel", + "type": "object", + "properties": { + "id": {"title": "Id", "type": "string", "format": "uuid"}, + "enum_field": {"$ref": "#/definitions/MyEnum2"}, + }, + "required": ["id", "enum_field"], + "definitions": { + "MyEnum2": { + "title": "MyEnum2", + "description": "An enumeration.", + "enum": ["C", "D"], + "type": "string", + } + }, + } diff --git a/tests/test_main.py b/tests/test_main.py index 22c62327da..72465cda33 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,8 +1,9 @@ -from typing import Optional +from typing import List, Optional import pytest from sqlalchemy.exc import IntegrityError -from sqlmodel import Field, Session, SQLModel, create_engine +from sqlalchemy.orm import RelationshipProperty +from sqlmodel import Field, Relationship, Session, SQLModel, create_engine def test_should_allow_duplicate_row_if_unique_constraint_is_not_passed(clear_sqlmodel): @@ -91,3 +92,37 @@ class Hero(SQLModel, table=True): session.add(hero_2) session.commit() session.refresh(hero_2) + + +def test_sa_relationship_property(clear_sqlmodel): + """Test https://github.com/tiangolo/sqlmodel/issues/315#issuecomment-1272122306""" + + class Team(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + name: str = Field(unique=True) + heroes: List["Hero"] = Relationship( # noqa: F821 + sa_relationship=RelationshipProperty("Hero", back_populates="team") + ) + + class Hero(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + name: str = Field(unique=True) + team_id: Optional[int] = Field(default=None, foreign_key="team.id") + team: Optional[Team] = Relationship( + sa_relationship=RelationshipProperty("Team", back_populates="heroes") + ) + + team_preventers = Team(name="Preventers") + hero_rusty_man = Hero(name="Rusty-Man", team=team_preventers) + + engine = create_engine("sqlite://", echo=True) + + SQLModel.metadata.create_all(engine) + + with Session(engine) as session: + session.add(hero_rusty_man) + session.commit() + session.refresh(hero_rusty_man) + # The next statement should not raise an AttributeError + assert hero_rusty_man.team + assert hero_rusty_man.team.name == "Preventers" diff --git a/tests/test_sqlalchemy_type_errors.py b/tests/test_sqlalchemy_type_errors.py new file mode 100644 index 0000000000..e211c46a34 --- /dev/null +++ b/tests/test_sqlalchemy_type_errors.py @@ -0,0 +1,28 @@ +from typing import Any, Dict, List, Optional, Union + +import pytest +from sqlmodel import Field, SQLModel + + +def test_type_list_breaks() -> None: + with pytest.raises(ValueError): + + class Hero(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + tags: List[str] + + +def test_type_dict_breaks() -> None: + with pytest.raises(ValueError): + + class Hero(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + tags: Dict[str, Any] + + +def test_type_union_breaks() -> None: + with pytest.raises(ValueError): + + class Hero(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + tags: Union[int, str] diff --git a/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001.py index e560d04c0e..b08affb920 100644 --- a/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_delete/test_tutorial001.py @@ -2,255 +2,6 @@ from sqlmodel import create_engine from sqlmodel.pool import StaticPool -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/heroes/": { - "get": { - "summary": "Read Heroes", - "operationId": "read_heroes_heroes__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Offset", "type": "integer", "default": 0}, - "name": "offset", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - "lte": 100, - }, - "name": "limit", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Heroes Heroes Get", - "type": "array", - "items": {"$ref": "#/components/schemas/HeroRead"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "post": { - "summary": "Create Hero", - "operationId": "create_hero_heroes__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroCreate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - "/heroes/{hero_id}": { - "get": { - "summary": "Read Hero", - "operationId": "read_hero_heroes__hero_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Hero Id", "type": "integer"}, - "name": "hero_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "delete": { - "summary": "Delete Hero", - "operationId": "delete_hero_heroes__hero_id__delete", - "parameters": [ - { - "required": True, - "schema": {"title": "Hero Id", "type": "integer"}, - "name": "hero_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "patch": { - "summary": "Update Hero", - "operationId": "update_hero_heroes__hero_id__patch", - "parameters": [ - { - "required": True, - "schema": {"title": "Hero Id", "type": "integer"}, - "name": "hero_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroUpdate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "HeroCreate": { - "title": "HeroCreate", - "required": ["name", "secret_name"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - }, - }, - "HeroRead": { - "title": "HeroRead", - "required": ["name", "secret_name", "id"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - "id": {"title": "Id", "type": "integer"}, - }, - }, - "HeroUpdate": { - "title": "HeroUpdate", - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"type": "string"}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - def test_tutorial(clear_sqlmodel): from docs_src.tutorial.fastapi.delete import tutorial001 as mod @@ -261,7 +12,6 @@ def test_tutorial(clear_sqlmodel): ) with TestClient(mod.app) as client: - hero1_data = {"name": "Deadpond", "secret_name": "Dive Wilson"} hero2_data = { "name": "Spider-Boy", @@ -285,10 +35,6 @@ def test_tutorial(clear_sqlmodel): assert response.status_code == 200, response.text response = client.get("/heroes/9000") assert response.status_code == 404, response.text - response = client.get("/openapi.json") - data = response.json() - assert response.status_code == 200, response.text - assert data == openapi_schema response = client.get("/heroes/") assert response.status_code == 200, response.text data = response.json() @@ -309,3 +55,272 @@ def test_tutorial(clear_sqlmodel): response = client.delete("/heroes/9000") assert response.status_code == 404, response.text + + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/heroes/": { + "get": { + "summary": "Read Heroes", + "operationId": "read_heroes_heroes__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Offset", + "type": "integer", + "default": 0, + }, + "name": "offset", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "maximum": 100.0, + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Heroes Heroes Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/HeroRead" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "post": { + "summary": "Create Hero", + "operationId": "create_hero_heroes__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroCreate" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/heroes/{hero_id}": { + "get": { + "summary": "Read Hero", + "operationId": "read_hero_heroes__hero_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "delete": { + "summary": "Delete Hero", + "operationId": "delete_hero_heroes__hero_id__delete", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "patch": { + "summary": "Update Hero", + "operationId": "update_hero_heroes__hero_id__patch", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroUpdate" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "HeroCreate": { + "title": "HeroCreate", + "required": ["name", "secret_name"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + }, + }, + "HeroRead": { + "title": "HeroRead", + "required": ["name", "secret_name", "id"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + "id": {"title": "Id", "type": "integer"}, + }, + }, + "HeroUpdate": { + "title": "HeroUpdate", + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": {"type": "string"}, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001.py index b58afdf683..0aee3ca004 100644 --- a/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_limit_and_offset/test_tutorial001.py @@ -2,178 +2,6 @@ from sqlmodel import create_engine from sqlmodel.pool import StaticPool -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/heroes/": { - "get": { - "summary": "Read Heroes", - "operationId": "read_heroes_heroes__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Offset", "type": "integer", "default": 0}, - "name": "offset", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - "lte": 100, - }, - "name": "limit", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Heroes Heroes Get", - "type": "array", - "items": {"$ref": "#/components/schemas/HeroRead"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "post": { - "summary": "Create Hero", - "operationId": "create_hero_heroes__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroCreate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - "/heroes/{hero_id}": { - "get": { - "summary": "Read Hero", - "operationId": "read_hero_heroes__hero_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Hero Id", "type": "integer"}, - "name": "hero_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "HeroCreate": { - "title": "HeroCreate", - "required": ["name", "secret_name"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - }, - }, - "HeroRead": { - "title": "HeroRead", - "required": ["name", "secret_name", "id"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - "id": {"title": "Id", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"type": "string"}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - def test_tutorial(clear_sqlmodel): from docs_src.tutorial.fastapi.limit_and_offset import tutorial001 as mod @@ -184,7 +12,6 @@ def test_tutorial(clear_sqlmodel): ) with TestClient(mod.app) as client: - hero1_data = {"name": "Deadpond", "secret_name": "Dive Wilson"} hero2_data = { "name": "Spider-Boy", @@ -208,10 +35,6 @@ def test_tutorial(clear_sqlmodel): assert response.status_code == 200, response.text response = client.get("/heroes/9000") assert response.status_code == 404, response.text - response = client.get("/openapi.json") - data = response.json() - assert response.status_code == 200, response.text - assert data == openapi_schema response = client.get("/heroes/") assert response.status_code == 200, response.text @@ -237,3 +60,191 @@ def test_tutorial(clear_sqlmodel): data = response.json() assert len(data) == 1 assert data[0]["name"] == hero2_data["name"] + + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/heroes/": { + "get": { + "summary": "Read Heroes", + "operationId": "read_heroes_heroes__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Offset", + "type": "integer", + "default": 0, + }, + "name": "offset", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "maximum": 100.0, + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Heroes Heroes Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/HeroRead" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "post": { + "summary": "Create Hero", + "operationId": "create_hero_heroes__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroCreate" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/heroes/{hero_id}": { + "get": { + "summary": "Read Hero", + "operationId": "read_hero_heroes__hero_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "HeroCreate": { + "title": "HeroCreate", + "required": ["name", "secret_name"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + }, + }, + "HeroRead": { + "title": "HeroRead", + "required": ["name", "secret_name", "id"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + "id": {"title": "Id", "type": "integer"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": {"type": "string"}, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001.py index cf008563f4..8d99cf9f5b 100644 --- a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001.py @@ -123,7 +123,6 @@ def test_tutorial(clear_sqlmodel): ) with TestClient(mod.app) as client: - hero1_data = {"name": "Deadpond", "secret_name": "Dive Wilson"} hero2_data = { "name": "Spider-Boy", @@ -173,8 +172,18 @@ def test_tutorial(clear_sqlmodel): insp: Inspector = inspect(mod.engine) indexes = insp.get_indexes(str(mod.Hero.__tablename__)) expected_indexes = [ - {"name": "ix_hero_name", "column_names": ["name"], "unique": 0}, - {"name": "ix_hero_age", "column_names": ["age"], "unique": 0}, + { + "name": "ix_hero_name", + "dialect_options": {}, + "column_names": ["name"], + "unique": 0, + }, + { + "name": "ix_hero_age", + "dialect_options": {}, + "column_names": ["age"], + "unique": 0, + }, ] for index in expected_indexes: assert index in indexes, "This expected index should be in the indexes in DB" diff --git a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002.py b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002.py index 57393a7ddc..94a41b3076 100644 --- a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002.py +++ b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002.py @@ -123,7 +123,6 @@ def test_tutorial(clear_sqlmodel): ) with TestClient(mod.app) as client: - hero1_data = {"name": "Deadpond", "secret_name": "Dive Wilson"} hero2_data = { "name": "Spider-Boy", @@ -173,8 +172,18 @@ def test_tutorial(clear_sqlmodel): insp: Inspector = inspect(mod.engine) indexes = insp.get_indexes(str(mod.Hero.__tablename__)) expected_indexes = [ - {"name": "ix_hero_age", "column_names": ["age"], "unique": 0}, - {"name": "ix_hero_name", "column_names": ["name"], "unique": 0}, + { + "name": "ix_hero_age", + "dialect_options": {}, + "column_names": ["age"], + "unique": 0, + }, + { + "name": "ix_hero_name", + "dialect_options": {}, + "column_names": ["name"], + "unique": 0, + }, ] for index in expected_indexes: assert index in indexes, "This expected index should be in the indexes in DB" diff --git a/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001.py index 5b3c771bb9..0609ae41ff 100644 --- a/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_read_one/test_tutorial001.py @@ -155,7 +155,6 @@ def test_tutorial(clear_sqlmodel): ) with TestClient(mod.app) as client: - hero1_data = {"name": "Deadpond", "secret_name": "Dive Wilson"} hero2_data = { "name": "Spider-Boy", diff --git a/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001.py index 125e00179c..8869862e95 100644 --- a/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_relationships/test_tutorial001.py @@ -2,502 +2,6 @@ from sqlmodel import create_engine from sqlmodel.pool import StaticPool -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/heroes/": { - "get": { - "summary": "Read Heroes", - "operationId": "read_heroes_heroes__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Offset", "type": "integer", "default": 0}, - "name": "offset", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - "lte": 100, - }, - "name": "limit", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Heroes Heroes Get", - "type": "array", - "items": {"$ref": "#/components/schemas/HeroRead"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "post": { - "summary": "Create Hero", - "operationId": "create_hero_heroes__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroCreate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - "/heroes/{hero_id}": { - "get": { - "summary": "Read Hero", - "operationId": "read_hero_heroes__hero_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Hero Id", "type": "integer"}, - "name": "hero_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HeroReadWithTeam" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "delete": { - "summary": "Delete Hero", - "operationId": "delete_hero_heroes__hero_id__delete", - "parameters": [ - { - "required": True, - "schema": {"title": "Hero Id", "type": "integer"}, - "name": "hero_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "patch": { - "summary": "Update Hero", - "operationId": "update_hero_heroes__hero_id__patch", - "parameters": [ - { - "required": True, - "schema": {"title": "Hero Id", "type": "integer"}, - "name": "hero_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroUpdate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - "/teams/": { - "get": { - "summary": "Read Teams", - "operationId": "read_teams_teams__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Offset", "type": "integer", "default": 0}, - "name": "offset", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - "lte": 100, - }, - "name": "limit", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Teams Teams Get", - "type": "array", - "items": {"$ref": "#/components/schemas/TeamRead"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "post": { - "summary": "Create Team", - "operationId": "create_team_teams__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/TeamCreate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/TeamRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - "/teams/{team_id}": { - "get": { - "summary": "Read Team", - "operationId": "read_team_teams__team_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Team Id", "type": "integer"}, - "name": "team_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TeamReadWithHeroes" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "delete": { - "summary": "Delete Team", - "operationId": "delete_team_teams__team_id__delete", - "parameters": [ - { - "required": True, - "schema": {"title": "Team Id", "type": "integer"}, - "name": "team_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "patch": { - "summary": "Update Team", - "operationId": "update_team_teams__team_id__patch", - "parameters": [ - { - "required": True, - "schema": {"title": "Team Id", "type": "integer"}, - "name": "team_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/TeamUpdate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/TeamRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "HeroCreate": { - "title": "HeroCreate", - "required": ["name", "secret_name"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - "team_id": {"title": "Team Id", "type": "integer"}, - }, - }, - "HeroRead": { - "title": "HeroRead", - "required": ["name", "secret_name", "id"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - "team_id": {"title": "Team Id", "type": "integer"}, - "id": {"title": "Id", "type": "integer"}, - }, - }, - "HeroReadWithTeam": { - "title": "HeroReadWithTeam", - "required": ["name", "secret_name", "id"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - "team_id": {"title": "Team Id", "type": "integer"}, - "id": {"title": "Id", "type": "integer"}, - "team": {"$ref": "#/components/schemas/TeamRead"}, - }, - }, - "HeroUpdate": { - "title": "HeroUpdate", - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - "team_id": {"title": "Team Id", "type": "integer"}, - }, - }, - "TeamCreate": { - "title": "TeamCreate", - "required": ["name", "headquarters"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "headquarters": {"title": "Headquarters", "type": "string"}, - }, - }, - "TeamRead": { - "title": "TeamRead", - "required": ["name", "headquarters", "id"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "headquarters": {"title": "Headquarters", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - }, - }, - "TeamReadWithHeroes": { - "title": "TeamReadWithHeroes", - "required": ["name", "headquarters", "id"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "headquarters": {"title": "Headquarters", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - "heroes": { - "title": "Heroes", - "type": "array", - "items": {"$ref": "#/components/schemas/HeroRead"}, - "default": [], - }, - }, - }, - "TeamUpdate": { - "title": "TeamUpdate", - "type": "object", - "properties": { - "id": {"title": "Id", "type": "integer"}, - "name": {"title": "Name", "type": "string"}, - "headquarters": {"title": "Headquarters", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"type": "string"}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - def test_tutorial(clear_sqlmodel): from docs_src.tutorial.fastapi.relationships import tutorial001 as mod @@ -508,11 +12,6 @@ def test_tutorial(clear_sqlmodel): ) with TestClient(mod.app) as client: - response = client.get("/openapi.json") - data = response.json() - assert response.status_code == 200, response.text - assert data == openapi_schema - team_preventers = {"name": "Preventers", "headquarters": "Sharp Tower"} team_z_force = {"name": "Z-Force", "headquarters": "Sister Margaret’s Bar"} response = client.post("/teams/", json=team_preventers) @@ -604,3 +103,531 @@ def test_tutorial(clear_sqlmodel): assert response.status_code == 200, response.text data = response.json() assert len(data) == 1 + + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/heroes/": { + "get": { + "summary": "Read Heroes", + "operationId": "read_heroes_heroes__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Offset", + "type": "integer", + "default": 0, + }, + "name": "offset", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "maximum": 100.0, + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Heroes Heroes Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/HeroRead" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "post": { + "summary": "Create Hero", + "operationId": "create_hero_heroes__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroCreate" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/heroes/{hero_id}": { + "get": { + "summary": "Read Hero", + "operationId": "read_hero_heroes__hero_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroReadWithTeam" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "delete": { + "summary": "Delete Hero", + "operationId": "delete_hero_heroes__hero_id__delete", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "patch": { + "summary": "Update Hero", + "operationId": "update_hero_heroes__hero_id__patch", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroUpdate" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/teams/": { + "get": { + "summary": "Read Teams", + "operationId": "read_teams_teams__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Offset", + "type": "integer", + "default": 0, + }, + "name": "offset", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "maximum": 100.0, + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Teams Teams Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/TeamRead" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "post": { + "summary": "Create Team", + "operationId": "create_team_teams__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamCreate" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/teams/{team_id}": { + "get": { + "summary": "Read Team", + "operationId": "read_team_teams__team_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Team Id", "type": "integer"}, + "name": "team_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamReadWithHeroes" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "delete": { + "summary": "Delete Team", + "operationId": "delete_team_teams__team_id__delete", + "parameters": [ + { + "required": True, + "schema": {"title": "Team Id", "type": "integer"}, + "name": "team_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "patch": { + "summary": "Update Team", + "operationId": "update_team_teams__team_id__patch", + "parameters": [ + { + "required": True, + "schema": {"title": "Team Id", "type": "integer"}, + "name": "team_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamUpdate" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "HeroCreate": { + "title": "HeroCreate", + "required": ["name", "secret_name"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + "team_id": {"title": "Team Id", "type": "integer"}, + }, + }, + "HeroRead": { + "title": "HeroRead", + "required": ["name", "secret_name", "id"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + "team_id": {"title": "Team Id", "type": "integer"}, + "id": {"title": "Id", "type": "integer"}, + }, + }, + "HeroReadWithTeam": { + "title": "HeroReadWithTeam", + "required": ["name", "secret_name", "id"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + "team_id": {"title": "Team Id", "type": "integer"}, + "id": {"title": "Id", "type": "integer"}, + "team": {"$ref": "#/components/schemas/TeamRead"}, + }, + }, + "HeroUpdate": { + "title": "HeroUpdate", + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + "team_id": {"title": "Team Id", "type": "integer"}, + }, + }, + "TeamCreate": { + "title": "TeamCreate", + "required": ["name", "headquarters"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "headquarters": {"title": "Headquarters", "type": "string"}, + }, + }, + "TeamRead": { + "title": "TeamRead", + "required": ["name", "headquarters", "id"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "headquarters": {"title": "Headquarters", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + }, + }, + "TeamReadWithHeroes": { + "title": "TeamReadWithHeroes", + "required": ["name", "headquarters", "id"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "headquarters": {"title": "Headquarters", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + "heroes": { + "title": "Heroes", + "type": "array", + "items": {"$ref": "#/components/schemas/HeroRead"}, + "default": [], + }, + }, + }, + "TeamUpdate": { + "title": "TeamUpdate", + "type": "object", + "properties": { + "id": {"title": "Id", "type": "integer"}, + "name": {"title": "Name", "type": "string"}, + "headquarters": {"title": "Headquarters", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": {"type": "string"}, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_fastapi/test_response_model/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_response_model/test_tutorial001.py index 54fbbdccad..ebb3046ef3 100644 --- a/tests/test_tutorial/test_fastapi/test_response_model/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_response_model/test_tutorial001.py @@ -111,7 +111,6 @@ def test_tutorial(clear_sqlmodel): ) with TestClient(mod.app) as client: - hero_data = {"name": "Deadpond", "secret_name": "Dive Wilson"} response = client.post("/heroes/", json=hero_data) data = response.json() diff --git a/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001.py index d8dbe3f7fb..cb0a6f9282 100644 --- a/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_session_with_dependency/test_tutorial001.py @@ -2,255 +2,6 @@ from sqlmodel import create_engine from sqlmodel.pool import StaticPool -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/heroes/": { - "get": { - "summary": "Read Heroes", - "operationId": "read_heroes_heroes__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Offset", "type": "integer", "default": 0}, - "name": "offset", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - "lte": 100, - }, - "name": "limit", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Heroes Heroes Get", - "type": "array", - "items": {"$ref": "#/components/schemas/HeroRead"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "post": { - "summary": "Create Hero", - "operationId": "create_hero_heroes__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroCreate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - "/heroes/{hero_id}": { - "get": { - "summary": "Read Hero", - "operationId": "read_hero_heroes__hero_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Hero Id", "type": "integer"}, - "name": "hero_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "delete": { - "summary": "Delete Hero", - "operationId": "delete_hero_heroes__hero_id__delete", - "parameters": [ - { - "required": True, - "schema": {"title": "Hero Id", "type": "integer"}, - "name": "hero_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "patch": { - "summary": "Update Hero", - "operationId": "update_hero_heroes__hero_id__patch", - "parameters": [ - { - "required": True, - "schema": {"title": "Hero Id", "type": "integer"}, - "name": "hero_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroUpdate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "HeroCreate": { - "title": "HeroCreate", - "required": ["name", "secret_name"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - }, - }, - "HeroRead": { - "title": "HeroRead", - "required": ["name", "secret_name", "id"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - "id": {"title": "Id", "type": "integer"}, - }, - }, - "HeroUpdate": { - "title": "HeroUpdate", - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"type": "string"}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - def test_tutorial(clear_sqlmodel): from docs_src.tutorial.fastapi.session_with_dependency import tutorial001 as mod @@ -261,7 +12,6 @@ def test_tutorial(clear_sqlmodel): ) with TestClient(mod.app) as client: - hero1_data = {"name": "Deadpond", "secret_name": "Dive Wilson"} hero2_data = { "name": "Spider-Boy", @@ -285,10 +35,6 @@ def test_tutorial(clear_sqlmodel): assert response.status_code == 200, response.text response = client.get("/heroes/9000") assert response.status_code == 404, response.text - response = client.get("/openapi.json") - data = response.json() - assert response.status_code == 200, response.text - assert data == openapi_schema response = client.get("/heroes/") assert response.status_code == 200, response.text data = response.json() @@ -309,3 +55,272 @@ def test_tutorial(clear_sqlmodel): response = client.delete("/heroes/9000") assert response.status_code == 404, response.text + + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/heroes/": { + "get": { + "summary": "Read Heroes", + "operationId": "read_heroes_heroes__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Offset", + "type": "integer", + "default": 0, + }, + "name": "offset", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "maximum": 100.0, + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Heroes Heroes Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/HeroRead" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "post": { + "summary": "Create Hero", + "operationId": "create_hero_heroes__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroCreate" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/heroes/{hero_id}": { + "get": { + "summary": "Read Hero", + "operationId": "read_hero_heroes__hero_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "delete": { + "summary": "Delete Hero", + "operationId": "delete_hero_heroes__hero_id__delete", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "patch": { + "summary": "Update Hero", + "operationId": "update_hero_heroes__hero_id__patch", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroUpdate" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "HeroCreate": { + "title": "HeroCreate", + "required": ["name", "secret_name"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + }, + }, + "HeroRead": { + "title": "HeroRead", + "required": ["name", "secret_name", "id"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + "id": {"title": "Id", "type": "integer"}, + }, + }, + "HeroUpdate": { + "title": "HeroUpdate", + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": {"type": "string"}, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_fastapi/test_simple_hero_api/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_simple_hero_api/test_tutorial001.py index 2f87fafeff..eb834ec2a4 100644 --- a/tests/test_tutorial/test_fastapi/test_simple_hero_api/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_simple_hero_api/test_tutorial001.py @@ -99,7 +99,6 @@ def test_tutorial(clear_sqlmodel): ) with TestClient(mod.app) as client: - hero1_data = {"name": "Deadpond", "secret_name": "Dive Wilson"} hero2_data = { "name": "Spider-Boy", diff --git a/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001.py index 6ac1cffc5e..e66c975142 100644 --- a/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_teams/test_tutorial001.py @@ -2,468 +2,6 @@ from sqlmodel import create_engine from sqlmodel.pool import StaticPool -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/heroes/": { - "get": { - "summary": "Read Heroes", - "operationId": "read_heroes_heroes__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Offset", "type": "integer", "default": 0}, - "name": "offset", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - "lte": 100, - }, - "name": "limit", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Heroes Heroes Get", - "type": "array", - "items": {"$ref": "#/components/schemas/HeroRead"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "post": { - "summary": "Create Hero", - "operationId": "create_hero_heroes__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroCreate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - "/heroes/{hero_id}": { - "get": { - "summary": "Read Hero", - "operationId": "read_hero_heroes__hero_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Hero Id", "type": "integer"}, - "name": "hero_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "delete": { - "summary": "Delete Hero", - "operationId": "delete_hero_heroes__hero_id__delete", - "parameters": [ - { - "required": True, - "schema": {"title": "Hero Id", "type": "integer"}, - "name": "hero_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "patch": { - "summary": "Update Hero", - "operationId": "update_hero_heroes__hero_id__patch", - "parameters": [ - { - "required": True, - "schema": {"title": "Hero Id", "type": "integer"}, - "name": "hero_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroUpdate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - "/teams/": { - "get": { - "summary": "Read Teams", - "operationId": "read_teams_teams__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Offset", "type": "integer", "default": 0}, - "name": "offset", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - "lte": 100, - }, - "name": "limit", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Teams Teams Get", - "type": "array", - "items": {"$ref": "#/components/schemas/TeamRead"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "post": { - "summary": "Create Team", - "operationId": "create_team_teams__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/TeamCreate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/TeamRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - "/teams/{team_id}": { - "get": { - "summary": "Read Team", - "operationId": "read_team_teams__team_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Team Id", "type": "integer"}, - "name": "team_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/TeamRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "delete": { - "summary": "Delete Team", - "operationId": "delete_team_teams__team_id__delete", - "parameters": [ - { - "required": True, - "schema": {"title": "Team Id", "type": "integer"}, - "name": "team_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "patch": { - "summary": "Update Team", - "operationId": "update_team_teams__team_id__patch", - "parameters": [ - { - "required": True, - "schema": {"title": "Team Id", "type": "integer"}, - "name": "team_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/TeamUpdate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/TeamRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "HeroCreate": { - "title": "HeroCreate", - "required": ["name", "secret_name"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - "team_id": {"title": "Team Id", "type": "integer"}, - }, - }, - "HeroRead": { - "title": "HeroRead", - "required": ["name", "secret_name", "id"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - "team_id": {"title": "Team Id", "type": "integer"}, - "id": {"title": "Id", "type": "integer"}, - }, - }, - "HeroUpdate": { - "title": "HeroUpdate", - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - "team_id": {"title": "Team Id", "type": "integer"}, - }, - }, - "TeamCreate": { - "title": "TeamCreate", - "required": ["name", "headquarters"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "headquarters": {"title": "Headquarters", "type": "string"}, - }, - }, - "TeamRead": { - "title": "TeamRead", - "required": ["name", "headquarters", "id"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "headquarters": {"title": "Headquarters", "type": "string"}, - "id": {"title": "Id", "type": "integer"}, - }, - }, - "TeamUpdate": { - "title": "TeamUpdate", - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "headquarters": {"title": "Headquarters", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"type": "string"}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - def test_tutorial(clear_sqlmodel): from docs_src.tutorial.fastapi.teams import tutorial001 as mod @@ -474,7 +12,6 @@ def test_tutorial(clear_sqlmodel): ) with TestClient(mod.app) as client: - hero1_data = {"name": "Deadpond", "secret_name": "Dive Wilson"} hero2_data = { "name": "Spider-Boy", @@ -486,10 +23,6 @@ def test_tutorial(clear_sqlmodel): "secret_name": "Tommy Sharp", "age": 48, } - response = client.get("/openapi.json") - data = response.json() - assert response.status_code == 200, response.text - assert data == openapi_schema response = client.post("/heroes/", json=hero1_data) assert response.status_code == 200, response.text response = client.post("/heroes/", json=hero2_data) @@ -557,3 +90,501 @@ def test_tutorial(clear_sqlmodel): assert response.status_code == 200, response.text data = response.json() assert len(data) == 1 + + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/heroes/": { + "get": { + "summary": "Read Heroes", + "operationId": "read_heroes_heroes__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Offset", + "type": "integer", + "default": 0, + }, + "name": "offset", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "maximum": 100.0, + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Heroes Heroes Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/HeroRead" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "post": { + "summary": "Create Hero", + "operationId": "create_hero_heroes__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroCreate" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/heroes/{hero_id}": { + "get": { + "summary": "Read Hero", + "operationId": "read_hero_heroes__hero_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "delete": { + "summary": "Delete Hero", + "operationId": "delete_hero_heroes__hero_id__delete", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "patch": { + "summary": "Update Hero", + "operationId": "update_hero_heroes__hero_id__patch", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroUpdate" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/teams/": { + "get": { + "summary": "Read Teams", + "operationId": "read_teams_teams__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Offset", + "type": "integer", + "default": 0, + }, + "name": "offset", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "maximum": 100.0, + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Teams Teams Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/TeamRead" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "post": { + "summary": "Create Team", + "operationId": "create_team_teams__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamCreate" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/teams/{team_id}": { + "get": { + "summary": "Read Team", + "operationId": "read_team_teams__team_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Team Id", "type": "integer"}, + "name": "team_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "delete": { + "summary": "Delete Team", + "operationId": "delete_team_teams__team_id__delete", + "parameters": [ + { + "required": True, + "schema": {"title": "Team Id", "type": "integer"}, + "name": "team_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "patch": { + "summary": "Update Team", + "operationId": "update_team_teams__team_id__patch", + "parameters": [ + { + "required": True, + "schema": {"title": "Team Id", "type": "integer"}, + "name": "team_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamUpdate" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "HeroCreate": { + "title": "HeroCreate", + "required": ["name", "secret_name"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + "team_id": {"title": "Team Id", "type": "integer"}, + }, + }, + "HeroRead": { + "title": "HeroRead", + "required": ["name", "secret_name", "id"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + "team_id": {"title": "Team Id", "type": "integer"}, + "id": {"title": "Id", "type": "integer"}, + }, + }, + "HeroUpdate": { + "title": "HeroUpdate", + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + "team_id": {"title": "Team Id", "type": "integer"}, + }, + }, + "TeamCreate": { + "title": "TeamCreate", + "required": ["name", "headquarters"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "headquarters": {"title": "Headquarters", "type": "string"}, + }, + }, + "TeamRead": { + "title": "TeamRead", + "required": ["name", "headquarters", "id"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "headquarters": {"title": "Headquarters", "type": "string"}, + "id": {"title": "Id", "type": "integer"}, + }, + }, + "TeamUpdate": { + "title": "TeamUpdate", + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "headquarters": {"title": "Headquarters", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": {"type": "string"}, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_fastapi/test_update/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_update/test_tutorial001.py index e622fd37fb..49906256c9 100644 --- a/tests/test_tutorial/test_fastapi/test_update/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_update/test_tutorial001.py @@ -2,227 +2,6 @@ from sqlmodel import create_engine from sqlmodel.pool import StaticPool -openapi_schema = { - "openapi": "3.0.2", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/heroes/": { - "get": { - "summary": "Read Heroes", - "operationId": "read_heroes_heroes__get", - "parameters": [ - { - "required": False, - "schema": {"title": "Offset", "type": "integer", "default": 0}, - "name": "offset", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - "lte": 100, - }, - "name": "limit", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Heroes Heroes Get", - "type": "array", - "items": {"$ref": "#/components/schemas/HeroRead"}, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "post": { - "summary": "Create Hero", - "operationId": "create_hero_heroes__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroCreate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - "/heroes/{hero_id}": { - "get": { - "summary": "Read Hero", - "operationId": "read_hero_heroes__hero_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Hero Id", "type": "integer"}, - "name": "hero_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - "patch": { - "summary": "Update Hero", - "operationId": "update_hero_heroes__hero_id__patch", - "parameters": [ - { - "required": True, - "schema": {"title": "Hero Id", "type": "integer"}, - "name": "hero_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroUpdate"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/HeroRead"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "HeroCreate": { - "title": "HeroCreate", - "required": ["name", "secret_name"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - }, - }, - "HeroRead": { - "title": "HeroRead", - "required": ["name", "secret_name", "id"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - "id": {"title": "Id", "type": "integer"}, - }, - }, - "HeroUpdate": { - "title": "HeroUpdate", - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "secret_name": {"title": "Secret Name", "type": "string"}, - "age": {"title": "Age", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"type": "string"}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, -} - def test_tutorial(clear_sqlmodel): from docs_src.tutorial.fastapi.update import tutorial001 as mod @@ -233,7 +12,6 @@ def test_tutorial(clear_sqlmodel): ) with TestClient(mod.app) as client: - hero1_data = {"name": "Deadpond", "secret_name": "Dive Wilson"} hero2_data = { "name": "Spider-Boy", @@ -259,10 +37,6 @@ def test_tutorial(clear_sqlmodel): assert response.status_code == 200, response.text response = client.get("/heroes/9000") assert response.status_code == 404, response.text - response = client.get("/openapi.json") - data = response.json() - assert response.status_code == 200, response.text - assert data == openapi_schema response = client.get("/heroes/") assert response.status_code == 200, response.text data = response.json() @@ -288,3 +62,244 @@ def test_tutorial(clear_sqlmodel): response = client.patch("/heroes/9001", json={"name": "Dragon Cube X"}) assert response.status_code == 404, response.text + + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/heroes/": { + "get": { + "summary": "Read Heroes", + "operationId": "read_heroes_heroes__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Offset", + "type": "integer", + "default": 0, + }, + "name": "offset", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "maximum": 100.0, + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Heroes Heroes Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/HeroRead" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "post": { + "summary": "Create Hero", + "operationId": "create_hero_heroes__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroCreate" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/heroes/{hero_id}": { + "get": { + "summary": "Read Hero", + "operationId": "read_hero_heroes__hero_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + "patch": { + "summary": "Update Hero", + "operationId": "update_hero_heroes__hero_id__patch", + "parameters": [ + { + "required": True, + "schema": {"title": "Hero Id", "type": "integer"}, + "name": "hero_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroUpdate" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HeroRead" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "HeroCreate": { + "title": "HeroCreate", + "required": ["name", "secret_name"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + }, + }, + "HeroRead": { + "title": "HeroRead", + "required": ["name", "secret_name", "id"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + "id": {"title": "Id", "type": "integer"}, + }, + }, + "HeroUpdate": { + "title": "HeroUpdate", + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "secret_name": {"title": "Secret Name", "type": "string"}, + "age": {"title": "Age", "type": "integer"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": {"type": "string"}, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } diff --git a/tests/test_tutorial/test_indexes/test_tutorial001.py b/tests/test_tutorial/test_indexes/test_tutorial001.py index 596207737d..f33db5bcc7 100644 --- a/tests/test_tutorial/test_indexes/test_tutorial001.py +++ b/tests/test_tutorial/test_indexes/test_tutorial001.py @@ -25,8 +25,18 @@ def test_tutorial(clear_sqlmodel): insp: Inspector = inspect(mod.engine) indexes = insp.get_indexes(str(mod.Hero.__tablename__)) expected_indexes = [ - {"name": "ix_hero_name", "column_names": ["name"], "unique": 0}, - {"name": "ix_hero_age", "column_names": ["age"], "unique": 0}, + { + "name": "ix_hero_name", + "dialect_options": {}, + "column_names": ["name"], + "unique": 0, + }, + { + "name": "ix_hero_age", + "dialect_options": {}, + "column_names": ["age"], + "unique": 0, + }, ] for index in expected_indexes: assert index in indexes, "This expected index should be in the indexes in DB" diff --git a/tests/test_tutorial/test_indexes/test_tutorial006.py b/tests/test_tutorial/test_indexes/test_tutorial006.py index e26f8b2ed8..893043dad1 100644 --- a/tests/test_tutorial/test_indexes/test_tutorial006.py +++ b/tests/test_tutorial/test_indexes/test_tutorial006.py @@ -26,8 +26,18 @@ def test_tutorial(clear_sqlmodel): insp: Inspector = inspect(mod.engine) indexes = insp.get_indexes(str(mod.Hero.__tablename__)) expected_indexes = [ - {"name": "ix_hero_name", "column_names": ["name"], "unique": 0}, - {"name": "ix_hero_age", "column_names": ["age"], "unique": 0}, + { + "name": "ix_hero_name", + "dialect_options": {}, + "column_names": ["name"], + "unique": 0, + }, + { + "name": "ix_hero_age", + "dialect_options": {}, + "column_names": ["age"], + "unique": 0, + }, ] for index in expected_indexes: assert index in indexes, "This expected index should be in the indexes in DB" diff --git a/tests/test_tutorial/test_limit_and_offset/test_tutorial004.py b/tests/test_tutorial/test_limit_and_offset/test_tutorial004.py index 448b191249..eb15a1560e 100644 --- a/tests/test_tutorial/test_limit_and_offset/test_tutorial004.py +++ b/tests/test_tutorial/test_limit_and_offset/test_tutorial004.py @@ -4,16 +4,6 @@ from ...conftest import get_testing_print_function -expected_calls = [ - [ - [ - {"id": 5, "name": "Black Lion", "secret_name": "Trevor Challa", "age": 35}, - {"id": 6, "name": "Dr. Weird", "secret_name": "Steve Weird", "age": 36}, - {"id": 3, "name": "Rusty-Man", "secret_name": "Tommy Sharp", "age": 48}, - ] - ] -] - def test_tutorial(clear_sqlmodel): from docs_src.tutorial.offset_and_limit import tutorial004 as mod @@ -26,4 +16,11 @@ def test_tutorial(clear_sqlmodel): with patch("builtins.print", new=new_print): mod.main() - assert calls == expected_calls + assert calls == [ + [ + [ + {"name": "Dr. Weird", "secret_name": "Steve Weird", "age": 36, "id": 6}, + {"name": "Rusty-Man", "secret_name": "Tommy Sharp", "age": 48, "id": 3}, + ] + ] + ] diff --git a/tests/test_validation.py b/tests/test_validation.py index a3ff6e39ba..ad60fcb945 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -7,7 +7,7 @@ def test_validation(clear_sqlmodel): - """Test validation of implicit and explict None values. + """Test validation of implicit and explicit None values. # For consistency with pydantic, validators are not to be called on # arguments that are not explicitly provided.