Compare commits

..

50 Commits

Author SHA1 Message Date
github-actions
e86b5fcc84 📝 Update release notes 2024-11-08 09:27:42 +00:00
Evgeniy Lupashin
ab3d19f0ac
✏️ Fix typo in the release notes of v0.0.22 (#1195)
Fix typo in release-notes.md
2024-11-08 10:27:23 +01:00
github-actions
893f8bd039 📝 Update release notes 2024-10-27 08:08:55 +00:00
Sebastián Ramírez
4f9b2ea9eb
📝 Update includes for docs/advanced/uuid.md (#1151) 2024-10-27 08:08:30 +00:00
github-actions
06de217329 📝 Update release notes 2024-10-26 22:55:50 +00:00
Sebastián Ramírez
a3cfa7618d
⬆️ Upgrade markdown-include-variants to version 0.0.3 (#1152) 2024-10-26 22:55:33 +00:00
github-actions
3d8a870da4 📝 Update release notes 2024-10-26 22:05:26 +00:00
Sebastián Ramírez
45bc96e8a3
📝 Update includes for docs/tutorial/create-db-and-table.md (#1149) 2024-10-26 23:05:08 +01:00
github-actions
1406429fdf 📝 Update release notes 2024-10-26 17:29:19 +00:00
Sebastián Ramírez
1999c77481
📝 Fix internal links in docs (#1148) 2024-10-26 18:29:02 +01:00
github-actions
059970f024 📝 Update release notes 2024-10-15 10:39:42 +00:00
Alejandra
87257b08bf
👷 Update issue manager workflow (#1137)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2024-10-15 12:39:19 +02:00
github-actions
4aef35e81d 📝 Update release notes 2024-10-12 13:55:59 +00:00
Sebastián Ramírez
313783e294
👷 Fix smokeshow, checkout files on CI (#1136) 2024-10-12 15:54:21 +02:00
github-actions
74c53203fc 📝 Update release notes 2024-10-12 13:08:37 +00:00
Sebastián Ramírez
f1d5262ca6
👷 Use uv in CI (#1135) 2024-10-12 15:08:11 +02:00
github-actions
abbc92bc20 📝 Update release notes 2024-10-07 21:22:16 +00:00
Sebastián Ramírez
aa814e24bc
🚨 Fix types for new Pydantic (#1131) 2024-10-07 21:21:59 +00:00
github-actions
79ef8d0675 📝 Update release notes 2024-10-07 21:05:46 +00:00
Sebastián Ramírez
c92fe5018e
Add docs dependency markdown-include-variants (#1129) 2024-10-07 23:05:27 +02:00
github-actions
368bd664db 📝 Update release notes 2024-10-07 21:00:57 +00:00
Sebastián Ramírez
772b1a6fa1
🔨 Update script to standardize format (#1130) 2024-10-07 23:00:30 +02:00
github-actions
ad5b10f4e0 📝 Update release notes 2024-10-07 20:35:22 +00:00
Sebastián Ramírez
fed464a41c
👷 Update labeler.yml (#1128) 2024-10-07 22:34:07 +02:00
github-actions
321cd93e61 📝 Update release notes 2024-10-06 20:20:23 +00:00
Sebastián Ramírez
1eb70dfec9
👷 Update worfkow deploy-docs-notify URL (#1126) 2024-10-06 22:19:59 +02:00
github-actions
64276fcefb 📝 Update release notes 2024-10-05 13:05:04 +00:00
Sebastián Ramírez
5d682eda33
👷 Upgrade Cloudflare GitHub Action (#1124) 2024-10-05 15:04:46 +02:00
github-actions
7dbd1a96fc 📝 Update release notes 2024-09-21 18:45:35 +00:00
pre-commit-ci[bot]
97fa93f587
⬆ [pre-commit.ci] pre-commit autoupdate (#1097)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.2 → v0.6.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.2...v0.6.5)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-09-21 20:45:13 +02:00
github-actions
e6794c6c7f 📝 Update release notes 2024-09-21 18:44:43 +00:00
Solipsistmonkey
fa0ec972ce
✏️ Fix typo in documentation (#1106) 2024-09-21 20:44:22 +02:00
github-actions
b5ecb184cf 📝 Update release notes 2024-09-18 11:48:56 +00:00
dependabot[bot]
4173ba34e5
⬆ Bump tiangolo/issue-manager from 0.5.0 to 0.5.1 (#1107)
Bumps [tiangolo/issue-manager](https://github.com/tiangolo/issue-manager) from 0.5.0 to 0.5.1.
- [Release notes](https://github.com/tiangolo/issue-manager/releases)
- [Commits](https://github.com/tiangolo/issue-manager/compare/0.5.0...0.5.1)

---
updated-dependencies:
- dependency-name: tiangolo/issue-manager
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-18 13:46:16 +02:00
github-actions
9550bb9784 📝 Update release notes 2024-09-08 12:06:10 +00:00
Sebastián Ramírez
548a4d06d2
👷 Update issue-manager.yml (#1103) 2024-09-08 12:05:54 +00:00
github-actions
2aaa75261e 📝 Update release notes 2024-09-08 12:01:53 +00:00
Sebastián Ramírez
898e0902d4
👷 Fix coverage processing in CI, one name per matrix run (#1104) 2024-09-08 12:01:32 +00:00
github-actions
b083aa03f9 📝 Update release notes 2024-09-03 14:13:37 +00:00
Alejandra
e46572f2ab
📝 Remove highlights in indexes.md (#1100) 2024-09-03 16:13:17 +02:00
github-actions
6f6f50cfb1 📝 Update release notes 2024-09-02 20:13:56 +00:00
Sofie Van Landeghem
5c6688e945
💚 Set include-hidden-files to True when using the upload-artifact GH action (#1098)
set include-hidden-files to true for actions/upload-artifact@v4
2024-09-02 22:13:38 +02:00
github-actions
e2d3153dcc 📝 Update release notes 2024-09-01 06:50:00 +00:00
pre-commit-ci[bot]
76b9b5d109
⬆ [pre-commit.ci] pre-commit autoupdate (#1088)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.1 → v0.6.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.1...v0.6.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-09-01 08:49:40 +02:00
Sebastián Ramírez
016b2baaad 🔖 Release version 0.0.22 2024-08-31 11:40:12 +02:00
Sebastián Ramírez
5930fb0551 📝 Update release notes 2024-08-31 11:39:52 +02:00
github-actions
feb5ff1747 📝 Update release notes 2024-08-31 09:38:41 +00:00
Sebastián Ramírez
a14ab0bd3c
🐛 Fix support for types with Optional[Annoated[x, f()]], e.g. id: Optional[pydantic.UUID4] (#1093) 2024-08-31 11:38:19 +02:00
github-actions
4eaf8b9efb 📝 Update release notes 2024-08-31 09:33:38 +00:00
Sebastián Ramírez
e4f3ec7a80
Refactor test_enums to make them independent of previous imports (#1095) 2024-08-31 11:33:20 +02:00
26 changed files with 257 additions and 645 deletions

View File

@ -7,6 +7,11 @@ on:
types:
- opened
- synchronize
env:
UV_SYSTEM_PYTHON: 1
jobs:
changes:
runs-on: ubuntu-latest
@ -52,17 +57,19 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: "3.11"
- uses: actions/cache@v4
id: cache
- name: Setup uv
uses: astral-sh/setup-uv@v3
with:
path: ${{ env.pythonLocation }}
key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt', 'requirements-docs-insiders.txt', 'requirements-docs-tests.txt') }}-v02
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
- name: Install docs extras
if: steps.cache.outputs.cache-hit != 'true'
run: pip install -r requirements-docs.txt
run: uv pip install -r requirements-docs.txt
- name: Install Material for MkDocs Insiders
if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' ) && steps.cache.outputs.cache-hit != 'true'
run: pip install -r requirements-docs-insiders.txt
if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' )
run: uv pip install -r requirements-docs-insiders.txt
env:
TOKEN: ${{ secrets.SQLMODEL_MKDOCS_MATERIAL_INSIDERS }}
- uses: actions/cache@v4
@ -77,6 +84,7 @@ jobs:
with:
name: docs-site
path: ./site/**
include-hidden-files: true
# https://github.com/marketplace/actions/alls-green#why
docs-all-green: # This job does nothing and is only used for the branch protection

View File

@ -12,6 +12,9 @@ permissions:
pull-requests: write
statuses: write
env:
UV_SYSTEM_PYTHON: 1
jobs:
deploy-docs:
runs-on: ubuntu-latest
@ -25,14 +28,16 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: "3.11"
- uses: actions/cache@v4
id: cache
- name: Setup uv
uses: astral-sh/setup-uv@v3
with:
path: ${{ env.pythonLocation }}
key: ${{ runner.os }}-python-github-actions-${{ env.pythonLocation }}-${{ hashFiles('requirements-github-actions.txt') }}-v01
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
- name: Install GitHub Actions dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: pip install -r requirements-github-actions.txt
run: uv pip install -r requirements-github-actions.txt
- name: Deploy Docs Status Pending
run: python ./scripts/deploy_docs_status.py
env:
@ -55,19 +60,19 @@ jobs:
# hashFiles returns an empty string if there are no files
if: hashFiles('./site/*')
id: deploy
uses: cloudflare/pages-action@v1
env:
PROJECT_NAME: sqlmodel
BRANCH: ${{ ( github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'main' && 'main' ) || ( github.event.workflow_run.head_sha ) }}
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: sqlmodel
directory: './site'
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ ( github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'main' && 'main' ) || ( github.event.workflow_run.head_sha ) }}
command: pages deploy ./site --project-name=${{ env.PROJECT_NAME }} --branch=${{ env.BRANCH }}
- name: Comment Deploy
run: python ./scripts/deploy_docs_status.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEPLOY_URL: ${{ steps.deploy.outputs.url }}
DEPLOY_URL: ${{ steps.deploy.outputs.deployment-url }}
COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
RUN_ID: ${{ github.run_id }}
IS_DONE: "true"

View File

@ -2,7 +2,7 @@ name: Issue Manager
on:
schedule:
- cron: "11 4 * * *"
- cron: "13 18 * * *"
issue_comment:
types:
- created
@ -16,6 +16,7 @@ on:
permissions:
issues: write
pull-requests: write
jobs:
issue-manager:
@ -26,7 +27,7 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: tiangolo/issue-manager@0.5.0
- uses: tiangolo/issue-manager@0.5.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
config: >
@ -35,8 +36,12 @@ jobs:
"delay": 864000,
"message": "Assuming the original need was handled, this will be automatically closed now. But feel free to add more comments or create new issues or PRs."
},
"changes-requested": {
"waiting": {
"delay": 2628000,
"message": "As this PR had requested changes to be applied but has been inactive for a while, it's now going to be closed. But if there's anyone interested, feel free to create a new PR."
"message": "As this PR has been waiting for the original user for a while but seems to be inactive, it's now going to be closed. But if there's anyone interested, feel free to create a new PR."
},
"invalid": {
"delay": 0,
"message": "This was marked as invalid and will be closed now. If this is an error, please provide additional details."
}
}

View File

@ -17,6 +17,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5
if: ${{ github.event.action != 'labeled' && github.event.action != 'unlabeled' }}
- run: echo "Done adding labels"
# Run this after labeler applied labels
check-labels:
needs:

View File

@ -8,25 +8,33 @@ on:
permissions:
statuses: write
env:
UV_SYSTEM_PYTHON: 1
jobs:
smokeshow:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.9'
- run: pip install smokeshow
- name: Setup uv
uses: astral-sh/setup-uv@v3
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
- run: uv pip install -r requirements-github-actions.txt
- uses: actions/download-artifact@v4
with:
name: coverage-html
path: htmlcov
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- run: smokeshow upload htmlcov
env:
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage}

View File

@ -18,6 +18,9 @@ on:
# cron every week on monday
- cron: "0 0 * * 1"
env:
UV_SYSTEM_PYTHON: 1
jobs:
test:
runs-on: ubuntu-latest
@ -34,33 +37,34 @@ jobs:
- pydantic-v1
- pydantic-v2
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Setup uv
uses: astral-sh/setup-uv@v3
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- uses: actions/cache@v4
id: cache
with:
path: ${{ env.pythonLocation }}
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-v01
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: pip install -r requirements-tests.txt
run: uv pip install -r requirements-tests.txt
- name: Install Pydantic v1
if: matrix.pydantic-version == 'pydantic-v1'
run: pip install --upgrade "pydantic>=1.10.0,<2.0.0"
run: uv pip install --upgrade "pydantic>=1.10.0,<2.0.0"
- name: Install Pydantic v2
if: matrix.pydantic-version == 'pydantic-v2'
run: pip install --upgrade "pydantic>=2.0.2,<3.0.0" "typing-extensions==4.6.1"
run: uv pip install --upgrade "pydantic>=2.0.2,<3.0.0" "typing-extensions==4.6.1"
- name: Lint
# Do not run on Python 3.7 as mypy behaves differently
if: matrix.python-version != '3.7' && matrix.pydantic-version == 'pydantic-v2'
@ -69,44 +73,50 @@ jobs:
- name: Test
run: bash scripts/test.sh
env:
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-${{ matrix.pydantic-version }}
CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}
- name: Store coverage files
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.python-version }}-${{ matrix.pydantic-version }}
path: coverage
include-hidden-files: true
coverage-combine:
needs:
- test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Setup uv
uses: astral-sh/setup-uv@v3
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
- name: Get coverage files
uses: actions/download-artifact@v4
with:
pattern: coverage-*
path: coverage
merge-multiple: true
- run: pip install coverage[toml]
- name: Install Dependencies
run: uv pip install -r requirements-tests.txt
- run: ls -la coverage
- run: coverage combine coverage
- run: coverage report
- run: coverage html --title "Coverage for ${{ github.sha }}"
- name: Store coverage HTML
uses: actions/upload-artifact@v4
with:
name: coverage-html
path: htmlcov
include-hidden-files: true
# https://github.com/marketplace/actions/alls-green#why
alls-green: # This job does nothing and is only used for the branch protection

View File

@ -14,7 +14,7 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.1
rev: v0.6.5
hooks:
- id: ruff
args:

View File

@ -80,45 +80,7 @@ We don't call `uuid.uuid4()` ourselves in the code (we don't put the parenthesis
This means that the UUID will be generated in the Python code, **before sending the data to the database**.
//// tab | Python 3.10+
```Python hl_lines="1 7"
{!./docs_src/advanced/uuid/tutorial001_py310.py[ln:1-10]!}
# Code below omitted 👇
```
////
//// tab | Python 3.7+
```Python hl_lines="1 8"
{!./docs_src/advanced/uuid/tutorial001.py[ln:1-11]!}
# Code below omitted 👇
```
////
/// details | 👀 Full file preview
//// tab | Python 3.10+
```Python
{!./docs_src/advanced/uuid/tutorial001_py310.py!}
```
////
//// tab | Python 3.7+
```Python
{!./docs_src/advanced/uuid/tutorial001.py!}
```
////
///
{* ./docs_src/advanced/uuid/tutorial001_py310.py ln[1:10] hl[1,7] *}
Pydantic has support for <a href="https://docs.pydantic.dev/latest/api/standard_library_types/#uuid" class="external-link" target="_blank">`UUID` types</a>.
@ -132,49 +94,7 @@ As `uuid.uuid4` will be called when creating the model instance, even before sen
And that **same ID (a UUID)** will be saved in the database.
//// tab | Python 3.10+
```Python hl_lines="5 7 9 14"
# Code above omitted 👆
{!./docs_src/advanced/uuid/tutorial001_py310.py[ln:23-34]!}
# Code below omitted 👇
```
////
//// tab | Python 3.7+
```Python hl_lines="5 7 9 14"
# Code above omitted 👆
{!./docs_src/advanced/uuid/tutorial001.py[ln:24-35]!}
# Code below omitted 👇
```
////
/// details | 👀 Full file preview
//// tab | Python 3.10+
```Python
{!./docs_src/advanced/uuid/tutorial001_py310.py!}
```
////
//// tab | Python 3.7+
```Python
{!./docs_src/advanced/uuid/tutorial001.py!}
```
////
///
{* ./docs_src/advanced/uuid/tutorial001_py310.py ln[23:34] hl[25,27,29,34] *}
### Select a Hero
@ -182,49 +102,7 @@ We can do the same operations we could do with other fields.
For example we can **select a hero by ID**:
//// tab | Python 3.10+
```Python hl_lines="15"
# Code above omitted 👆
{!./docs_src/advanced/uuid/tutorial001_py310.py[ln:37-54]!}
# Code below omitted 👇
```
////
//// tab | Python 3.7+
```Python hl_lines="15"
# Code above omitted 👆
{!./docs_src/advanced/uuid/tutorial001.py[ln:38-55]!}
# Code below omitted 👇
```
////
/// details | 👀 Full file preview
//// tab | Python 3.10+
```Python
{!./docs_src/advanced/uuid/tutorial001_py310.py!}
```
////
//// tab | Python 3.7+
```Python
{!./docs_src/advanced/uuid/tutorial001.py!}
```
////
///
{* ./docs_src/advanced/uuid/tutorial001_py310.py ln[37:54] hl[49] *}
/// tip
@ -238,49 +116,7 @@ SQLModel (actually SQLAlchemy) will take care of making it work. ✨
We could also select by ID with `session.get()`:
//// tab | Python 3.10+
```Python hl_lines="15"
# Code above omitted 👆
{!./docs_src/advanced/uuid/tutorial002_py310.py[ln:37-54]!}
# Code below omitted 👇
```
////
//// tab | Python 3.7+
```Python hl_lines="15"
# Code above omitted 👆
{!./docs_src/advanced/uuid/tutorial002.py[ln:38-55]!}
# Code below omitted 👇
```
////
/// details | 👀 Full file preview
//// tab | Python 3.10+
```Python
{!./docs_src/advanced/uuid/tutorial002_py310.py!}
```
////
//// tab | Python 3.7+
```Python
{!./docs_src/advanced/uuid/tutorial002.py!}
```
////
///
{* ./docs_src/advanced/uuid/tutorial002_py310.py ln[37:53] hl[49] *}
The same way as with other fields, we could update, delete, etc. 🚀

View File

@ -68,7 +68,7 @@ There are many databases of many types.
A database could be a single file called `heroes.db`, managed with code in a very efficient way. An example would be SQLite, more about that on a bit.
![database as a single file](/img/databases/single-file.svg)
![database as a single file](img/databases/single-file.svg)
### A server database
@ -80,11 +80,11 @@ In this case, your code would talk to this server application instead of reading
The database could be located in a different server/machine:
![database in an external server](/img/databases/external-server.svg)
![database in an external server](img/databases/external-server.svg)
Or the database could be located in the same server/machine:
![database in the same server](/img/databases/same-server.svg)
![database in the same server](img/databases/same-server.svg)
The most important aspect of these types of databases is that **your code doesn't read or modify** the files containing the data directly.
@ -98,7 +98,7 @@ In some cases, the database could even be a group of server applications running
In this case, your code would talk to one or more of these server applications running on different machines.
![distributed database in multiple servers](/img/databases/multiple-servers.svg)
![distributed database in multiple servers](img/databases/multiple-servers.svg)
Most of the databases that work as server applications also support multiple servers in one way or another.
@ -257,7 +257,7 @@ For example, the table for the teams has the ID `1` for the team `Preventers` an
As these **primary key** IDs can uniquely identify each row on the table for teams, we can now go to the table for heroes and refer to those IDs in the table for teams.
<img alt="table relationships" src="/img/databases/relationships.svg">
![table relationships](img/databases/relationships.svg)
So, in the table for heroes, we use the `team_id` column to define a relationship to the *foreign* table for teams. Each value in the `team_id` column on the table with heroes will be the same value as the `id` column of one row in the table with teams.

View File

@ -236,8 +236,7 @@ database.execute(
).all()
```
<img class="shadow" src="/img/db-to-code/autocompletion01.png">
![](img/db-to-code/autocompletion01.png){class="shadow"}
## ORMs and SQL
@ -280,7 +279,7 @@ For example this **Relation** or table:
* **Mapper**: this comes from Math, when there's something that can convert from some set of things to another, that's called a "**mapping function**". That's where the **Mapper** comes from.
![Squares to Triangles Mapper](/img/db-to-code/mapper.svg)
![Squares to Triangles Mapper](img/db-to-code/mapper.svg)
We could also write a **mapping function** in Python that converts from the *set of lowercase letters* to the *set of uppercase letters*, like this:

View File

@ -16,11 +16,11 @@ As **SQLModel** is built on top of <a href="https://www.sqlalchemy.org/" class="
## Install DB Browser for SQLite
Remember that [SQLite is a simple database in a single file](../databases.md#a-single-file-database){.internal-link target=_blank}?
Remember that [SQLite is a simple database in a single file](databases.md#a-single-file-database){.internal-link target=_blank}?
For most of the tutorial I'll use SQLite for the examples.
Python has integrated support for SQLite, it is a single file read and processed from Python. And it doesn't need an [External Database Server](../databases.md#a-server-database){.internal-link target=_blank}, so it will be perfect for learning.
Python has integrated support for SQLite, it is a single file read and processed from Python. And it doesn't need an [External Database Server](databases.md#a-server-database){.internal-link target=_blank}, so it will be perfect for learning.
In fact, SQLite is perfectly capable of handling quite big applications. At some point you might want to migrate to a server-based database like <a href="https://www.postgresql.org/" class="external-link" target="_blank">PostgreSQL</a> (which is also free). But for now we'll stick to SQLite.

View File

@ -2,6 +2,43 @@
## Latest Changes
### Refactors
* 🚨 Fix types for new Pydantic. PR [#1131](https://github.com/fastapi/sqlmodel/pull/1131) by [@tiangolo](https://github.com/tiangolo).
### Docs
* ✏️ Fix typo in the release notes of v0.0.22. PR [#1195](https://github.com/fastapi/sqlmodel/pull/1195) by [@PipeKnight](https://github.com/PipeKnight).
* 📝 Update includes for `docs/advanced/uuid.md`. PR [#1151](https://github.com/fastapi/sqlmodel/pull/1151) by [@tiangolo](https://github.com/tiangolo).
* 📝 Update includes for `docs/tutorial/create-db-and-table.md`. PR [#1149](https://github.com/fastapi/sqlmodel/pull/1149) by [@tiangolo](https://github.com/tiangolo).
* 📝 Fix internal links in docs. PR [#1148](https://github.com/fastapi/sqlmodel/pull/1148) by [@tiangolo](https://github.com/tiangolo).
* ✏️ Fix typo in documentation. PR [#1106](https://github.com/fastapi/sqlmodel/pull/1106) by [@Solipsistmonkey](https://github.com/Solipsistmonkey).
* 📝 Remove highlights in `indexes.md` . PR [#1100](https://github.com/fastapi/sqlmodel/pull/1100) by [@alejsdev](https://github.com/alejsdev).
### Internal
* ⬆️ Upgrade markdown-include-variants to version 0.0.3. PR [#1152](https://github.com/fastapi/sqlmodel/pull/1152) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update issue manager workflow. PR [#1137](https://github.com/fastapi/sqlmodel/pull/1137) by [@alejsdev](https://github.com/alejsdev).
* 👷 Fix smokeshow, checkout files on CI. PR [#1136](https://github.com/fastapi/sqlmodel/pull/1136) by [@tiangolo](https://github.com/tiangolo).
* 👷 Use uv in CI. PR [#1135](https://github.com/fastapi/sqlmodel/pull/1135) by [@tiangolo](https://github.com/tiangolo).
* Add docs dependency markdown-include-variants. PR [#1129](https://github.com/fastapi/sqlmodel/pull/1129) by [@tiangolo](https://github.com/tiangolo).
* 🔨 Update script to standardize format. PR [#1130](https://github.com/fastapi/sqlmodel/pull/1130) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update `labeler.yml`. PR [#1128](https://github.com/fastapi/sqlmodel/pull/1128) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update worfkow deploy-docs-notify URL. PR [#1126](https://github.com/fastapi/sqlmodel/pull/1126) by [@tiangolo](https://github.com/tiangolo).
* 👷 Upgrade Cloudflare GitHub Action. PR [#1124](https://github.com/fastapi/sqlmodel/pull/1124) by [@tiangolo](https://github.com/tiangolo).
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#1097](https://github.com/fastapi/sqlmodel/pull/1097) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
* ⬆ Bump tiangolo/issue-manager from 0.5.0 to 0.5.1. PR [#1107](https://github.com/fastapi/sqlmodel/pull/1107) by [@dependabot[bot]](https://github.com/apps/dependabot).
* 👷 Update `issue-manager.yml`. PR [#1103](https://github.com/fastapi/sqlmodel/pull/1103) by [@tiangolo](https://github.com/tiangolo).
* 👷 Fix coverage processing in CI, one name per matrix run. PR [#1104](https://github.com/fastapi/sqlmodel/pull/1104) by [@tiangolo](https://github.com/tiangolo).
* 💚 Set `include-hidden-files` to `True` when using the `upload-artifact` GH action. PR [#1098](https://github.com/fastapi/sqlmodel/pull/1098) by [@svlandeg](https://github.com/svlandeg).
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#1088](https://github.com/fastapi/sqlmodel/pull/1088) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
## 0.0.22
### Fixes
* 🐛 Fix support for types with `Optional[Annotated[x, f()]]`, e.g. `id: Optional[pydantic.UUID4]`. PR [#1093](https://github.com/fastapi/sqlmodel/pull/1093) by [@tiangolo](https://github.com/tiangolo).
### Docs
* ✏️ Fix a typo in `docs/virtual-environments.md`. PR [#1085](https://github.com/fastapi/sqlmodel/pull/1085) by [@tiangolo](https://github.com/tiangolo).
@ -11,6 +48,7 @@
### Internal
* ✅ Refactor test_enums to make them independent of previous imports. PR [#1095](https://github.com/fastapi/sqlmodel/pull/1095) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update `latest-changes` GitHub Action. PR [#1087](https://github.com/fastapi/sqlmodel/pull/1087) by [@tiangolo](https://github.com/tiangolo).
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#1028](https://github.com/fastapi/sqlmodel/pull/1028) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
* ⬆ Bump ruff from 0.4.7 to 0.6.2. PR [#1081](https://github.com/fastapi/sqlmodel/pull/1081) by [@dependabot[bot]](https://github.com/apps/dependabot).

View File

@ -41,45 +41,7 @@ That's why this package is called `SQLModel`. Because it's mainly used to create
For that, we will import `SQLModel` (plus other things we will also use) and create a class `Hero` that inherits from `SQLModel` and represents the **table model** for our heroes:
//// tab | Python 3.10+
```Python hl_lines="1 4"
{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py[ln:1-8]!}
# More code here later 👇
```
////
//// tab | Python 3.7+
```Python hl_lines="3 6"
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-10]!}
# More code here later 👇
```
////
/// details | 👀 Full file preview
//// tab | Python 3.10+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!}
```
////
//// tab | Python 3.7+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
```
////
///
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:8] hl[1,4] *}
This class `Hero` **represents the table** for our heroes. And each instance we create later will **represent a row** in the table.
@ -101,45 +63,7 @@ The name of each of these variables will be the name of the column in the table.
And the type of each of them will also be the type of table column:
//// tab | Python 3.10+
```Python hl_lines="1 5-8"
{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py[ln:1-8]!}
# More code here later 👇
```
////
//// tab | Python 3.7+
```Python hl_lines="1 3 7-10"
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-10]!}
# More code here later 👇
```
////
/// details | 👀 Full file preview
//// tab | Python 3.10+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!}
```
////
//// tab | Python 3.7+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
```
////
///
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:8] hl[1,5:8] *}
Let's now see with more detail these field/column declarations.
@ -153,45 +77,7 @@ That is the standard way to declare that something "could be an `int` or `None`"
And we also set the default value of `age` to `None`.
//// tab | Python 3.10+
```Python hl_lines="8"
{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py[ln:1-8]!}
# More code here later 👇
```
////
//// tab | Python 3.7+
```Python hl_lines="1 10"
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-10]!}
# More code here later 👇
```
////
/// details | 👀 Full file preview
//// tab | Python 3.10+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!}
```
////
//// tab | Python 3.7+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
```
////
///
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:8] hl[8] *}
/// tip
@ -221,45 +107,7 @@ So, we need to mark `id` as the **primary key**.
To do that, we use the special `Field` function from `sqlmodel` and set the argument `primary_key=True`:
//// tab | Python 3.10+
```Python hl_lines="1 5"
{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py[ln:1-8]!}
# More code here later 👇
```
////
//// tab | Python 3.7+
```Python hl_lines="3 7"
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-10]!}
# More code here later 👇
```
////
/// details | 👀 Full file preview
//// tab | Python 3.10+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!}
```
////
//// tab | Python 3.7+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
```
////
///
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:8] hl[1,5] *}
That way, we tell **SQLModel** that this `id` field/column is the primary key of the table.
@ -302,45 +150,7 @@ If you have a server database (for example PostgreSQL or MySQL), the **engine**
Creating the **engine** is very simple, just call `create_engine()` with a URL for the database to use:
//// tab | Python 3.10+
```Python hl_lines="1 14"
{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py[ln:1-16]!}
# More code here later 👇
```
////
//// tab | Python 3.7+
```Python hl_lines="3 16"
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-18]!}
# More code here later 👇
```
////
/// details | 👀 Full file preview
//// tab | Python 3.10+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!}
```
////
//// tab | Python 3.7+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
```
////
///
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:16] hl[1,14] *}
You should normally have a single **engine** object for your whole application and re-use it everywhere.
@ -364,45 +174,7 @@ SQLite supports a special database that lives all *in memory*. Hence, it's very
* `sqlite://`
//// tab | Python 3.10+
```Python hl_lines="11-12 14"
{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py[ln:1-16]!}
# More code here later 👇
```
////
//// tab | Python 3.7+
```Python hl_lines="13-14 16"
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-18]!}
# More code here later 👇
```
////
/// details | 👀 Full file preview
//// tab | Python 3.10+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!}
```
////
//// tab | Python 3.7+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
```
////
///
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:16] hl[11:12,14] *}
You can read a lot more about all the databases supported by **SQLAlchemy** (and that way supported by **SQLModel**) in the <a href="https://docs.sqlalchemy.org/en/14/core/engines.html" class="external-link" target="_blank">SQLAlchemy documentation</a>.
@ -414,45 +186,7 @@ It will make the engine print all the SQL statements it executes, which can help
It is particularly useful for **learning** and **debugging**:
//// tab | Python 3.10+
```Python hl_lines="14"
{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py[ln:1-16]!}
# More code here later 👇
```
////
//// tab | Python 3.7+
```Python hl_lines="16"
{!./docs_src/tutorial/create_db_and_table/tutorial001.py[ln:1-18]!}
# More code here later 👇
```
////
/// details | 👀 Full file preview
//// tab | Python 3.10+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!}
```
////
//// tab | Python 3.7+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
```
////
///
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py ln[1:16] hl[14] *}
But in production, you would probably want to remove `echo=True`:
@ -478,21 +212,7 @@ And SQLModel's version of `create_engine()` is type annotated internally, so you
Now everything is in place to finally create the database and table:
//// tab | Python 3.10+
```Python hl_lines="16"
{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!}
```
////
//// tab | Python 3.7+
```Python hl_lines="18"
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
```
////
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py hl[16] *}
/// tip
@ -603,25 +323,7 @@ Let's run the program to see it all working.
Put the code it in a file `app.py` if you haven't already.
/// details | 👀 Full file preview
//// tab | Python 3.10+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial001_py310.py!}
```
////
//// tab | Python 3.7+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
```
////
///
{* ./docs_src/tutorial/create_db_and_table/tutorial001_py310.py *}
/// tip
@ -726,45 +428,7 @@ In this example it's just the `SQLModel.metadata.create_all(engine)`.
Let's put it in a function `create_db_and_tables()`:
//// tab | Python 3.10+
```Python hl_lines="17-18"
{!./docs_src/tutorial/create_db_and_table/tutorial002_py310.py[ln:1-18]!}
# More code here later 👇
```
////
//// tab | Python 3.7+
```Python hl_lines="19-20"
{!./docs_src/tutorial/create_db_and_table/tutorial002.py[ln:1-20]!}
# More code here later 👇
```
////
/// details | 👀 Full file preview
//// tab | Python 3.10+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial002_py310.py!}
```
////
//// tab | Python 3.7+
```Python
{!./docs_src/tutorial/create_db_and_table/tutorial002.py!}
```
////
///
{* ./docs_src/tutorial/create_db_and_table/tutorial002_py310.py ln[1:18] hl[17:18] *}
If `SQLModel.metadata.create_all(engine)` was not in a function and we tried to import something from this module (from this file) in another, it would try to create the database and table **every time** we executed that other file that imported this module.
@ -794,21 +458,7 @@ The word **script** often implies that the code could be run independently and e
For that we can use the special variable `__name__` in an `if` block:
//// tab | Python 3.10+
```Python hl_lines="21-22"
{!./docs_src/tutorial/create_db_and_table/tutorial002_py310.py!}
```
////
//// tab | Python 3.7+
```Python hl_lines="23-24"
{!./docs_src/tutorial/create_db_and_table/tutorial002.py!}
```
////
{* ./docs_src/tutorial/create_db_and_table/tutorial002_py310.py hl[21:22] *}
### About `__name__ == "__main__"`

View File

@ -24,7 +24,7 @@ Fine, in that case, you can **sneak peek** the final code to create indexes here
//// tab | Python 3.10+
```Python hl_lines="8 10"
```Python
{!./docs_src/tutorial/indexes/tutorial002_py310.py!}
```
@ -32,7 +32,7 @@ Fine, in that case, you can **sneak peek** the final code to create indexes here
//// tab | Python 3.7+
```Python hl_lines="8 10"
```Python
{!./docs_src/tutorial/indexes/tutorial002.py!}
```

View File

@ -710,4 +710,4 @@ Hero: None
## Recap
As querying the SQL database for a single row is a common operation, you know have several tools to do it in a short and simple way. 🎉
As querying the SQL database for a single row is a common operation, you now have several tools to do it in a short and simple way. 🎉

View File

@ -184,6 +184,7 @@ markdown_extensions:
# Other extensions
mdx_include:
markdown_include_variants:
extra:
analytics:

View File

@ -16,3 +16,4 @@ cairosvg==2.7.1
# For griffe, it formats with black
typer == 0.12.3
mkdocs-macros-plugin==1.0.5
markdown-include-variants==0.0.3

View File

@ -2,3 +2,4 @@ PyGithub>=2.3.0,<3.0.0
pydantic>=2.5.3,<3.0.0
pydantic-settings>=2.1.0,<3.0.0
httpx>=0.27.0,<0.28.0
smokeshow

View File

@ -8,7 +8,7 @@ ruff ==0.6.2
fastapi >=0.103.2
httpx ==0.24.1
# TODO: upgrade when deprecating Python 3.7
dirty-equals ==0.8.0
dirty-equals ==0.6.0
jinja2 ==3.1.4
# Pin typing-extensions until Python 3.8 is deprecated or the issue with dirty-equals
# is fixed, maybe fixed after dropping Python 3.7 and upgrading dirty-equals

View File

@ -1,4 +1,6 @@
#!/bin/sh -e
#!/usr/bin/env bash
set -e
set -x
ruff check sqlmodel tests docs_src scripts --fix

View File

@ -1,4 +1,4 @@
__version__ = "0.0.21"
__version__ = "0.0.22"
# Re-export from SQLAlchemy
from sqlalchemy.engine import create_engine as create_engine

View File

@ -21,7 +21,7 @@ from typing import (
from pydantic import VERSION as P_VERSION
from pydantic import BaseModel
from pydantic.fields import FieldInfo
from typing_extensions import get_args, get_origin
from typing_extensions import Annotated, get_args, get_origin
# Reassign variable to make it reexported for mypy
PYDANTIC_VERSION = P_VERSION
@ -177,16 +177,17 @@ if IS_PYDANTIC_V2:
return False
return False
def get_type_from_field(field: Any) -> Any:
type_: Any = field.annotation
def get_sa_type_from_type_annotation(annotation: Any) -> Any:
# Resolve Optional fields
if type_ is None:
if annotation is None:
raise ValueError("Missing field type")
origin = get_origin(type_)
origin = get_origin(annotation)
if origin is None:
return type_
return annotation
elif origin is Annotated:
return get_sa_type_from_type_annotation(get_args(annotation)[0])
if _is_union_type(origin):
bases = get_args(type_)
bases = get_args(annotation)
if len(bases) > 2:
raise ValueError(
"Cannot have a (non-optional) union as a SQLAlchemy field"
@ -197,9 +198,14 @@ if IS_PYDANTIC_V2:
"Cannot have a (non-optional) union as a SQLAlchemy field"
)
# Optional unions are allowed
return bases[0] if bases[0] is not NoneType else bases[1]
use_type = bases[0] if bases[0] is not NoneType else bases[1]
return get_sa_type_from_type_annotation(use_type)
return origin
def get_sa_type_from_field(field: Any) -> Any:
type_: Any = field.annotation
return get_sa_type_from_type_annotation(type_)
def get_field_metadata(field: Any) -> Any:
for meta in field.metadata:
if isinstance(meta, (PydanticMetadata, MaxLen)):
@ -444,7 +450,7 @@ else:
)
return field.allow_none # type: ignore[no-any-return, attr-defined]
def get_type_from_field(field: Any) -> Any:
def get_sa_type_from_field(field: Any) -> Any:
if isinstance(field.type_, type) and field.shape == SHAPE_SINGLETON:
return field.type_
raise ValueError(f"The field {field.name} has no matching SQLAlchemy type")

View File

@ -52,7 +52,7 @@ from sqlalchemy.orm.decl_api import DeclarativeMeta
from sqlalchemy.orm.instrumentation import is_instrumented
from sqlalchemy.sql.schema import MetaData
from sqlalchemy.sql.sqltypes import LargeBinary, Time, Uuid
from typing_extensions import Literal, deprecated, get_origin
from typing_extensions import Literal, TypeAlias, deprecated, get_origin
from ._compat import ( # type: ignore[attr-defined]
IS_PYDANTIC_V2,
@ -71,7 +71,7 @@ from ._compat import ( # type: ignore[attr-defined]
get_field_metadata,
get_model_fields,
get_relationship_to,
get_type_from_field,
get_sa_type_from_field,
init_pydantic_private_attrs,
is_field_noneable,
is_table_model_class,
@ -90,7 +90,12 @@ if TYPE_CHECKING:
_T = TypeVar("_T")
NoArgAnyCallable = Callable[[], Any]
IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any], None]
IncEx: TypeAlias = Union[
Set[int],
Set[str],
Mapping[int, Union["IncEx", Literal[True]]],
Mapping[str, Union["IncEx", Literal[True]]],
]
OnDeleteType = Literal["CASCADE", "SET NULL", "RESTRICT"]
@ -649,7 +654,7 @@ def get_sqlalchemy_type(field: Any) -> Any:
if sa_type is not Undefined:
return sa_type
type_ = get_type_from_field(field)
type_ = get_sa_type_from_field(field)
metadata = get_field_metadata(field)
# Check enums first as an enum can also be a str, needed by Pydantic/FastAPI
@ -858,8 +863,8 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry
self,
*,
mode: Union[Literal["json", "python"], str] = "python",
include: IncEx = None,
exclude: IncEx = None,
include: Union[IncEx, None] = None,
exclude: Union[IncEx, None] = None,
context: Union[Dict[str, Any], None] = None,
by_alias: bool = False,
exclude_unset: bool = False,
@ -908,8 +913,8 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry
def dict(
self,
*,
include: IncEx = None,
exclude: IncEx = None,
include: Union[IncEx, None] = None,
exclude: Union[IncEx, None] = None,
by_alias: bool = False,
exclude_unset: bool = False,
exclude_defaults: bool = False,

View File

@ -0,0 +1,26 @@
import uuid
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine, select
from tests.conftest import needs_pydanticv2
@needs_pydanticv2
def test_annotated_optional_types(clear_sqlmodel) -> None:
from pydantic import UUID4
class Hero(SQLModel, table=True):
# Pydantic UUID4 is: Annotated[UUID, UuidVersion(4)]
id: Optional[UUID4] = Field(default_factory=uuid.uuid4, primary_key=True)
engine = create_engine("sqlite:///:memory:")
SQLModel.metadata.create_all(engine)
with Session(engine) as db:
hero = Hero()
db.add(hero)
db.commit()
statement = select(Hero)
result = db.exec(statement).all()
assert len(result) == 1
assert isinstance(hero.id, uuid.UUID)

View File

@ -1,10 +1,11 @@
import enum
import uuid
import importlib
import pytest
from sqlalchemy import create_mock_engine
from sqlalchemy.sql.type_api import TypeEngine
from sqlmodel import Field, SQLModel
from sqlmodel import SQLModel
from . import test_enums_models
from .conftest import needs_pydanticv1, needs_pydanticv2
"""
@ -16,30 +17,6 @@ Associated issues:
"""
class MyEnum1(str, enum.Enum):
A = "A"
B = "B"
class MyEnum2(str, enum.Enum):
C = "C"
D = "D"
class BaseModel(SQLModel):
id: uuid.UUID = Field(primary_key=True)
enum_field: MyEnum2
class FlatModel(SQLModel, table=True):
id: uuid.UUID = Field(primary_key=True)
enum_field: MyEnum1
class InheritModel(BaseModel, table=True):
pass
def pg_dump(sql: TypeEngine, *args, **kwargs):
dialect = sql.compile(dialect=postgres_engine.dialect)
sql_str = str(dialect).rstrip()
@ -58,7 +35,9 @@ postgres_engine = create_mock_engine("postgresql://", pg_dump)
sqlite_engine = create_mock_engine("sqlite://", sqlite_dump)
def test_postgres_ddl_sql(capsys):
def test_postgres_ddl_sql(clear_sqlmodel, capsys: pytest.CaptureFixture[str]):
assert test_enums_models, "Ensure the models are imported and registered"
importlib.reload(test_enums_models)
SQLModel.metadata.create_all(bind=postgres_engine, checkfirst=False)
captured = capsys.readouterr()
@ -66,17 +45,19 @@ def test_postgres_ddl_sql(capsys):
assert "CREATE TYPE myenum2 AS ENUM ('C', 'D');" in captured.out
def test_sqlite_ddl_sql(capsys):
def test_sqlite_ddl_sql(clear_sqlmodel, capsys: pytest.CaptureFixture[str]):
assert test_enums_models, "Ensure the models are imported and registered"
importlib.reload(test_enums_models)
SQLModel.metadata.create_all(bind=sqlite_engine, checkfirst=False)
captured = capsys.readouterr()
assert "enum_field VARCHAR(1) NOT NULL" in captured.out
assert "enum_field VARCHAR(1) NOT NULL" in captured.out, captured
assert "CREATE TYPE" not in captured.out
@needs_pydanticv1
def test_json_schema_flat_model_pydantic_v1():
assert FlatModel.schema() == {
assert test_enums_models.FlatModel.schema() == {
"title": "FlatModel",
"type": "object",
"properties": {
@ -97,7 +78,7 @@ def test_json_schema_flat_model_pydantic_v1():
@needs_pydanticv1
def test_json_schema_inherit_model_pydantic_v1():
assert InheritModel.schema() == {
assert test_enums_models.InheritModel.schema() == {
"title": "InheritModel",
"type": "object",
"properties": {
@ -118,7 +99,7 @@ def test_json_schema_inherit_model_pydantic_v1():
@needs_pydanticv2
def test_json_schema_flat_model_pydantic_v2():
assert FlatModel.model_json_schema() == {
assert test_enums_models.FlatModel.model_json_schema() == {
"title": "FlatModel",
"type": "object",
"properties": {
@ -134,7 +115,7 @@ def test_json_schema_flat_model_pydantic_v2():
@needs_pydanticv2
def test_json_schema_inherit_model_pydantic_v2():
assert InheritModel.model_json_schema() == {
assert test_enums_models.InheritModel.model_json_schema() == {
"title": "InheritModel",
"type": "object",
"properties": {

View File

@ -0,0 +1,28 @@
import enum
import uuid
from sqlmodel import Field, SQLModel
class MyEnum1(str, enum.Enum):
A = "A"
B = "B"
class MyEnum2(str, enum.Enum):
C = "C"
D = "D"
class BaseModel(SQLModel):
id: uuid.UUID = Field(primary_key=True)
enum_field: MyEnum2
class FlatModel(SQLModel, table=True):
id: uuid.UUID = Field(primary_key=True)
enum_field: MyEnum1
class InheritModel(BaseModel, table=True):
pass