Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3f31a9698 | ||
|
|
458e088170 | ||
|
|
49735a1c93 | ||
|
|
86ab09f7ec | ||
|
|
19c736766e | ||
|
|
b8718c3b4d | ||
|
|
179b7781c2 | ||
|
|
54d8a685cc | ||
|
|
eb5fb78114 | ||
|
|
eaa9023049 | ||
|
|
50b16972fe | ||
|
|
814d3a4b14 | ||
|
|
58d9d2c3a6 | ||
|
|
eb92b8315d | ||
|
|
8792c442d7 | ||
|
|
a23e36bec9 | ||
|
|
3c75056c6e | ||
|
|
da1253c21f | ||
|
|
b8d7f4ff67 | ||
|
|
438480f128 | ||
|
|
7ba80e47e7 | ||
|
|
1920f07052 | ||
|
|
09adc76e3d | ||
|
|
690f9cf5e1 | ||
|
|
fca0621098 | ||
|
|
95936bb508 | ||
|
|
3b889e09f7 | ||
|
|
600da0a25c | ||
|
|
96bfd855f8 | ||
|
|
8416508d79 | ||
|
|
f1bfebc9e2 | ||
|
|
1263024be5 | ||
|
|
23869cab0d | ||
|
|
24e76c7a13 | ||
|
|
6e7e553963 | ||
|
|
e7c62fc9d9 | ||
|
|
8703539bf0 | ||
|
|
f6ad19b1a7 | ||
|
|
4590963e88 | ||
|
|
883cbe3a8d | ||
|
|
b560e9deb8 | ||
|
|
e2f646dea5 | ||
|
|
b93dd95125 | ||
|
|
ceac7bc2e8 | ||
|
|
1d43bd8b1e | ||
|
|
9f3af8507e | ||
|
|
d165e4b5ad | ||
|
|
d5cba6e358 | ||
|
|
bd1641c9a2 | ||
|
|
71de44daba | ||
|
|
1b275bd6a7 | ||
|
|
866d9ecb29 | ||
|
|
5bb4cffd49 | ||
|
|
a319952be1 | ||
|
|
662bd641b8 | ||
|
|
dcf4f58e81 | ||
|
|
e4013acc54 | ||
|
|
df0f834227 | ||
|
|
5e592c9a0d | ||
|
|
c13b71056e | ||
|
|
900e0d3371 | ||
|
|
a280b58c10 | ||
|
|
9ebbf255f7 | ||
|
|
39cbf27904 | ||
|
|
28d0e76370 |
8
.github/workflows/build-docs.yml
vendored
8
.github/workflows/build-docs.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# For pull requests it's not necessary to checkout the code but for the main branch it is
|
||||
- uses: dorny/paths-filter@v2
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: cache
|
||||
with:
|
||||
path: ${{ env.pythonLocation }}
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
pip install git+https://${{ secrets.SQLMODEL_MKDOCS_MATERIAL_INSIDERS }}@github.com/squidfunk/mkdocs-material-insiders.git
|
||||
pip install git+https://${{ secrets.SQLMODEL_MKDOCS_MATERIAL_INSIDERS }}@github.com/pawamoy-insiders/griffe-typing-deprecated.git
|
||||
pip install git+https://${{ secrets.SQLMODEL_MKDOCS_MATERIAL_INSIDERS }}@github.com/pawamoy-insiders/mkdocstrings-python.git
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
key: mkdocs-cards-${{ github.ref }}
|
||||
path: .cache
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
run: python ./scripts/docs.py verify-readme
|
||||
- name: Build Docs
|
||||
run: python ./scripts/docs.py build
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docs-site
|
||||
path: ./site/**
|
||||
|
||||
16
.github/workflows/deploy-docs.yml
vendored
16
.github/workflows/deploy-docs.yml
vendored
@@ -19,18 +19,16 @@ jobs:
|
||||
run: |
|
||||
rm -rf ./site
|
||||
mkdir ./site
|
||||
- name: Download Artifact Docs
|
||||
id: download
|
||||
uses: dawidd6/action-download-artifact@v2.28.0
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
if_no_artifact_found: ignore
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
workflow: build-docs.yml
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
name: docs-site
|
||||
path: ./site/
|
||||
pattern: docs-site
|
||||
merge-multiple: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
- name: Deploy to Cloudflare Pages
|
||||
if: steps.download.outputs.found_artifact == 'true'
|
||||
# hashFiles returns an empty string if there are no files
|
||||
if: hashFiles('./site/*')
|
||||
id: deploy
|
||||
uses: cloudflare/pages-action@v1
|
||||
with:
|
||||
|
||||
2
.github/workflows/issue-manager.yml
vendored
2
.github/workflows/issue-manager.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
issue-manager:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: tiangolo/issue-manager@0.4.1
|
||||
- uses: tiangolo/issue-manager@0.5.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
config: >
|
||||
|
||||
3
.github/workflows/publish.yml
vendored
3
.github/workflows/publish.yml
vendored
@@ -18,6 +18,7 @@ jobs:
|
||||
matrix:
|
||||
package:
|
||||
- sqlmodel
|
||||
- sqlmodel-slim
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
@@ -33,4 +34,4 @@ jobs:
|
||||
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }}
|
||||
run: python -m build
|
||||
- name: Publish
|
||||
uses: pypa/gh-action-pypi-publish@v1.8.11
|
||||
uses: pypa/gh-action-pypi-publish@v1.9.0
|
||||
|
||||
10
.github/workflows/smokeshow.yml
vendored
10
.github/workflows/smokeshow.yml
vendored
@@ -20,12 +20,14 @@ jobs:
|
||||
|
||||
- run: pip install smokeshow
|
||||
|
||||
- uses: dawidd6/action-download-artifact@v2.28.0
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
workflow: test.yml
|
||||
commit: ${{ github.event.workflow_run.head_sha }}
|
||||
name: coverage-html
|
||||
path: htmlcov
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
- run: smokeshow upload coverage-html
|
||||
- run: smokeshow upload htmlcov
|
||||
env:
|
||||
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage}
|
||||
SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 95
|
||||
|
||||
3
.github/workflows/test-redistribute.yml
vendored
3
.github/workflows/test-redistribute.yml
vendored
@@ -16,6 +16,7 @@ jobs:
|
||||
matrix:
|
||||
package:
|
||||
- sqlmodel
|
||||
- sqlmodel-slim
|
||||
steps:
|
||||
- name: Dump GitHub context
|
||||
env:
|
||||
@@ -40,6 +41,8 @@ jobs:
|
||||
run: |
|
||||
cd dist/sqlmodel*/
|
||||
pip install -r requirements-tests.txt
|
||||
env:
|
||||
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }}
|
||||
- name: Run source distribution tests
|
||||
run: |
|
||||
cd dist/sqlmodel*/
|
||||
|
||||
17
.github/workflows/test.yml
vendored
17
.github/workflows/test.yml
vendored
@@ -47,11 +47,11 @@ jobs:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
|
||||
with:
|
||||
limit-access-to-actor: true
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: cache
|
||||
with:
|
||||
path: ${{ env.pythonLocation }}
|
||||
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}
|
||||
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
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
run: pip install --upgrade "pydantic>=1.10.0,<2.0.0"
|
||||
- name: Install Pydantic v2
|
||||
if: matrix.pydantic-version == 'pydantic-v2'
|
||||
run: pip install --upgrade "pydantic>=2.0.2,<3.0.0"
|
||||
run: 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'
|
||||
@@ -72,9 +72,9 @@ jobs:
|
||||
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}
|
||||
CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}
|
||||
- name: Store coverage files
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage
|
||||
name: coverage-${{ matrix.python-version }}-${{ matrix.pydantic-version }}
|
||||
path: coverage
|
||||
coverage-combine:
|
||||
needs:
|
||||
@@ -89,10 +89,11 @@ jobs:
|
||||
python-version: '3.8'
|
||||
|
||||
- name: Get coverage files
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: coverage
|
||||
pattern: coverage-*
|
||||
path: coverage
|
||||
merge-multiple: true
|
||||
|
||||
- run: pip install coverage[toml]
|
||||
|
||||
@@ -102,7 +103,7 @@ jobs:
|
||||
- run: coverage html --show-contexts --title "Coverage for ${{ github.sha }}"
|
||||
|
||||
- name: Store coverage HTML
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-html
|
||||
path: htmlcov
|
||||
|
||||
@@ -4,7 +4,7 @@ default_language_version:
|
||||
python: python3.10
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
rev: v4.6.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-toml
|
||||
@@ -14,7 +14,7 @@ repos:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.2.0
|
||||
rev: v0.5.2
|
||||
hooks:
|
||||
- id: ruff
|
||||
args:
|
||||
|
||||
342
docs/advanced/uuid.md
Normal file
342
docs/advanced/uuid.md
Normal file
@@ -0,0 +1,342 @@
|
||||
# UUID (Universally Unique Identifiers)
|
||||
|
||||
We have discussed some data types like `str`, `int`, etc.
|
||||
|
||||
There's another data type called `UUID` (Universally Unique Identifier).
|
||||
|
||||
You might have seen **UUIDs**, for example in URLs. They look something like this:
|
||||
|
||||
```
|
||||
4ff2dab7-bffe-414d-88a5-1826b9fea8df
|
||||
```
|
||||
|
||||
UUIDs can be particularly useful as an alternative to auto-incrementing integers for **primary keys**.
|
||||
|
||||
/// info
|
||||
|
||||
Official support for UUIDs was added in SQLModel version `0.0.20`.
|
||||
|
||||
///
|
||||
|
||||
## About UUIDs
|
||||
|
||||
UUIDs are numbers with 128 bits, that is, 16 bytes.
|
||||
|
||||
They are normally seen as 32 <abbr title="numbers in base 16 (instead of base 10), using letters from A to F to represent the numbers from 10 to 15">hexadecimal</abbr> characters separated by dashes.
|
||||
|
||||
There are several versions of UUID, some versions include the current time in the bytes, but **UUIDs version 4** are mainly random, the way they are generated makes them virtually **unique**.
|
||||
|
||||
### Distributed UUIDs
|
||||
|
||||
You could generate one UUID in one computer, and someone else could generate another UUID in another computer, and it would be almost **impossible** for both UUIDs to be the **same**.
|
||||
|
||||
This means that you don't have to wait for the DB to generate the ID for you, you can **generate it in code before sending it to the database**, because you can be quite certain it will be unique.
|
||||
|
||||
/// note | Technical Details
|
||||
|
||||
Because the number of possible UUIDs is so large (2^128), the probability of generating the same UUID version 4 (the random ones) twice is very low.
|
||||
|
||||
If you had 103 trillion version 4 UUIDs stored in the database, the probability of generating a duplicated new one is one in a billion. 🤓
|
||||
|
||||
///
|
||||
|
||||
For the same reason, if you decided to migrate your database, combine it with another database and mix records, etc. you would most probably be able to **just use the same UUIDs** you had originally.
|
||||
|
||||
/// warning
|
||||
|
||||
There's still a chance you could have a collision, but it's very low. In most cases you could assume you wouldn't have it, but it would be good to be prepared for it.
|
||||
|
||||
///
|
||||
|
||||
### UUIDs Prevent Information Leakage
|
||||
|
||||
Because UUIDs version 4 are **random**, you could give these IDs to the application users or to other systems, **without exposing information** about your application.
|
||||
|
||||
When using **auto-incremented integers** for primary keys, you could implicitly expose information about your system. For example, someone could create a new hero, and by getting the hero ID `20` **they would know that you have 20 heroes** in your system (or even less, if some heroes were already deleted).
|
||||
|
||||
### UUID Storage
|
||||
|
||||
Because UUIDs are 16 bytes, they would **consume more space** in the database than a smaller auto-incremented integer (commonly 4 bytes).
|
||||
|
||||
Depending on the database you use, UUIDs could have **better or worse performance**. If you are concerned about that, you should check the documentation for the specific database.
|
||||
|
||||
SQLite doesn't have a specific UUID type, so it will store the UUID as a string. Other databases like Postgres have a specific UUID type which would result in better performance and space usage than strings.
|
||||
|
||||
## Models with UUIDs
|
||||
|
||||
To use UUIDs as primary keys we need to import `uuid`, which is part of the Python standard library (we don't have to install anything) and use `uuid.UUID` as the **type** for the ID field.
|
||||
|
||||
We also want the Python code to **generate a new UUID** when creating a new instance, so we use `default_factory`.
|
||||
|
||||
The parameter `default_factory` takes a function (or in general, a "<abbr title="Something that can be called as a function.">callable</abbr>"). This function will be **called when creating a new instance** of the model and the value returned by the function will be used as the default value for the field.
|
||||
|
||||
For the function in `default_factory` we pass `uuid.uuid4`, which is a function that generates a **new UUID version 4**.
|
||||
|
||||
/// tip
|
||||
|
||||
We don't call `uuid.uuid4()` ourselves in the code (we don't put the parenthesis). Instead, we pass the function itself, just `uuid.uuid4`, so that SQLModel can call it every time we create a new instance.
|
||||
|
||||
///
|
||||
|
||||
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!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
Pydantic has support for <a href="https://docs.pydantic.dev/latest/api/standard_library_types/#uuid" class="external-link" target="_blank">`UUID` types</a>.
|
||||
|
||||
For the database, **SQLModel** internally uses <a href="https://docs.sqlalchemy.org/en/20/core/type_basics.html#sqlalchemy.types.Uuid" class="external-link" target="_blank">SQLAlchemy's `Uuid` type</a>.
|
||||
|
||||
### Create a Record with a UUID
|
||||
|
||||
When creating a `Hero` record, the `id` field will be **automatically populated** with a new UUID because we set `default_factory=uuid.uuid4`.
|
||||
|
||||
As `uuid.uuid4` will be called when creating the model instance, even before sending it to the database, we can **access and use the ID right away**.
|
||||
|
||||
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!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
### Select a Hero
|
||||
|
||||
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!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
/// tip
|
||||
|
||||
Even if a database like SQLite stores the UUID as a string, we can select and run comparisons using a Python UUID object and it will work.
|
||||
|
||||
SQLModel (actually SQLAlchemy) will take care of making it work. ✨
|
||||
|
||||
///
|
||||
|
||||
#### Select with `session.get()`
|
||||
|
||||
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!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
///
|
||||
|
||||
The same way as with other fields, we could update, delete, etc. 🚀
|
||||
|
||||
### Run the program
|
||||
|
||||
If you run the program, you will see the **UUID** generated in the Python code, and then the record **saved in the database with the same UUID**.
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python app.py
|
||||
|
||||
// Some boilerplate and previous output omitted 😉
|
||||
|
||||
// In SQLite, the UUID will be stored as a string
|
||||
// other DBs like Postgres have a specific UUID type
|
||||
CREATE TABLE hero (
|
||||
id CHAR(32) NOT NULL,
|
||||
name VARCHAR NOT NULL,
|
||||
secret_name VARCHAR NOT NULL,
|
||||
age INTEGER,
|
||||
PRIMARY KEY (id)
|
||||
)
|
||||
|
||||
// Before saving in the DB we already have the UUID
|
||||
The hero before saving in the DB
|
||||
name='Deadpond' secret_name='Dive Wilson' id=UUID('0e44c1a6-88d3-4a35-8b8a-307faa2def28') age=None
|
||||
The hero ID was already set
|
||||
0e44c1a6-88d3-4a35-8b8a-307faa2def28
|
||||
|
||||
// The SQL statement to insert the record uses our UUID
|
||||
INSERT INTO hero (id, name, secret_name, age) VALUES (?, ?, ?, ?)
|
||||
('0e44c1a688d34a358b8a307faa2def28', 'Deadpond', 'Dive Wilson', None)
|
||||
|
||||
// And indeed, the record was saved with the UUID we created 😎
|
||||
After saving in the DB
|
||||
age=None id=UUID('0e44c1a6-88d3-4a35-8b8a-307faa2def28') name='Deadpond' secret_name='Dive Wilson'
|
||||
|
||||
// Now we create a new hero (to select it in a bit)
|
||||
Created hero:
|
||||
age=None id=UUID('9d90d186-85db-4eaa-891a-def7b4ae2dab') name='Spider-Boy' secret_name='Pedro Parqueador'
|
||||
Created hero ID:
|
||||
9d90d186-85db-4eaa-891a-def7b4ae2dab
|
||||
|
||||
// And now we select it
|
||||
Selected hero:
|
||||
age=None id=UUID('9d90d186-85db-4eaa-891a-def7b4ae2dab') name='Spider-Boy' secret_name='Pedro Parqueador'
|
||||
Selected hero ID:
|
||||
9d90d186-85db-4eaa-891a-def7b4ae2dab
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more about **UUIDs** in:
|
||||
|
||||
* The official <a href="https://docs.python.org/3/library/uuid.html" class="external-link" target="_blank">Python docs for UUID</a>.
|
||||
* The <a href="https://en.wikipedia.org/wiki/Universally_unique_identifier" class="external-link" target="_blank">Wikipedia for UUID</a>.
|
||||
@@ -8,6 +8,10 @@
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.termy .linenos {
|
||||
display: none;
|
||||
}
|
||||
|
||||
a.external-link::after {
|
||||
/* \00A0 is a non-breaking space
|
||||
to make the mark be on the same line as the link
|
||||
|
||||
@@ -36,20 +36,10 @@ You will get completion for everything while writing the **minimum** amount of c
|
||||
|
||||
You won't need to keep guessing the types of different attributes in your models, if they could be `None`, etc. Your editor will be able to help you with everything because **SQLModel** is based on **standard Python type annotations**.
|
||||
|
||||
**SQLModel** even adopts currently <a href="https://github.com/microsoft/pyright/blob/main/specs/dataclass_transforms.md" class="external-link" target="_blank">in development standards</a> for Python type annotations to ensure the **best developer experience**, so you will get inline errors and autocompletion even while creating new model instances.
|
||||
**SQLModel** adopts <a href="https://peps.python.org/pep-0681/" class="external-link" target="_blank">PEP 681</a> for Python type annotations to ensure the **best developer experience**, so you will get inline errors and autocompletion even while creating new model instances.
|
||||
|
||||
<img class="shadow" src="/img/index/autocompletion01.png">
|
||||
|
||||
/// info
|
||||
|
||||
Don't worry, adopting this in-development standard only affects/improves editor support.
|
||||
|
||||
It doesn't affect performance or correctness. And if the in-progress standard was deprecated your code won't be affected.
|
||||
|
||||
Meanwhile, you will get inline errors (like type checks) and autocompletion on places you wouldn't get with any other library. 🎉
|
||||
|
||||
///
|
||||
|
||||
## Short
|
||||
|
||||
**SQLModel** has **sensible defaults** for everything, with **optional configurations** everywhere.
|
||||
|
||||
@@ -13,7 +13,7 @@ function setupTermynal() {
|
||||
|
||||
function createTermynals() {
|
||||
document
|
||||
.querySelectorAll(`.${termynalActivateClass} .highlight`)
|
||||
.querySelectorAll(`.${termynalActivateClass} .highlight code`)
|
||||
.forEach(node => {
|
||||
const text = node.textContent;
|
||||
const lines = text.split("\n");
|
||||
|
||||
@@ -2,6 +2,84 @@
|
||||
|
||||
## Latest Changes
|
||||
|
||||
## 0.0.21
|
||||
|
||||
### Features
|
||||
|
||||
* ✨ Add support for cascade delete relationships: `cascade_delete`, `ondelete`, and `passive_deletes`. PR [#983](https://github.com/tiangolo/sqlmodel/pull/983) by [@estebanx64](https://github.com/estebanx64).
|
||||
* New docs at: [Cascade Delete Relationships](https://sqlmodel.tiangolo.com/tutorial/relationship-attributes/cascade-delete-relationships/).
|
||||
|
||||
### Docs
|
||||
|
||||
* 📝 Update docs . PR [#1003](https://github.com/tiangolo/sqlmodel/pull/1003) by [@alejsdev](https://github.com/alejsdev).
|
||||
|
||||
### Internal
|
||||
|
||||
* ⬆ Bump actions/cache from 3 to 4. PR [#783](https://github.com/tiangolo/sqlmodel/pull/783) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump cairosvg from 2.7.0 to 2.7.1. PR [#919](https://github.com/tiangolo/sqlmodel/pull/919) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump jinja2 from 3.1.3 to 3.1.4. PR [#974](https://github.com/tiangolo/sqlmodel/pull/974) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump pypa/gh-action-pypi-publish from 1.8.11 to 1.9.0. PR [#987](https://github.com/tiangolo/sqlmodel/pull/987) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump mkdocstrings[python] from 0.23.0 to 0.25.1. PR [#927](https://github.com/tiangolo/sqlmodel/pull/927) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* ⬆ Bump dorny/paths-filter from 2 to 3. PR [#972](https://github.com/tiangolo/sqlmodel/pull/972) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
|
||||
## 0.0.20
|
||||
|
||||
### Features
|
||||
|
||||
* ✨ Add official UUID support, docs and tests, internally using new SQLAlchemy 2.0 types. Initial PR [#992](https://github.com/tiangolo/sqlmodel/pull/992) by [@estebanx64](https://github.com/estebanx64).
|
||||
* New docs in the [Advanced User Guide: UUID (Universally Unique Identifiers)](https://sqlmodel.tiangolo.com/advanced/uuid/).
|
||||
|
||||
### Docs
|
||||
|
||||
* ✏️ Fix internal link in `docs/tutorial/create-db-and-table.md`. PR [#911](https://github.com/tiangolo/sqlmodel/pull/911) by [@tfpgh](https://github.com/tfpgh).
|
||||
* ✏️ Add missing step in `create-db-and-table-with-db-browser.md`. PR [#976](https://github.com/tiangolo/sqlmodel/pull/976) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✏️ Fix typo in `docs/tutorial`. PR [#943](https://github.com/tiangolo/sqlmodel/pull/943) by [@luco17](https://github.com/luco17).
|
||||
* ✏️ Fix typo in `sqlmodel/_compat.py`. PR [#950](https://github.com/tiangolo/sqlmodel/pull/950) by [@Highfire1](https://github.com/Highfire1).
|
||||
* ✏️ Update pip installation command in tutorial. PR [#975](https://github.com/tiangolo/sqlmodel/pull/975) by [@alejsdev](https://github.com/alejsdev).
|
||||
* ✏️ Fix typo in `docs/tutorial/relationship-attributes/index.md`. PR [#880](https://github.com/tiangolo/sqlmodel/pull/880) by [@UncleGoogle](https://github.com/UncleGoogle).
|
||||
|
||||
### Internal
|
||||
|
||||
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#979](https://github.com/tiangolo/sqlmodel/pull/979) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
|
||||
* 🔨 Update docs Termynal scripts to not include line nums for local dev. PR [#1018](https://github.com/tiangolo/sqlmodel/pull/1018) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.0.19
|
||||
|
||||
### Fixes
|
||||
|
||||
* 🐛 Fix pydantic `EmailStr` support and `max_length` in several String subclasses. PR [#966](https://github.com/tiangolo/sqlmodel/pull/966) by [@estebanx64](https://github.com/estebanx64).
|
||||
* 🐛 Fix set varchar limit when `max_length` is set on Pydantic models using Pydantic v2. PR [#963](https://github.com/tiangolo/sqlmodel/pull/963) by [@estebanx64](https://github.com/estebanx64).
|
||||
|
||||
### Refactors
|
||||
|
||||
* ♻️ Refactor generate select template to isolate templated code to the minimum. PR [#967](https://github.com/tiangolo/sqlmodel/pull/967) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Upgrades
|
||||
|
||||
* ⬆️ Update minimum SQLAlchemy version to 2.0.14 as that one includes `TryCast` used internally. PR [#964](https://github.com/tiangolo/sqlmodel/pull/964) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Docs
|
||||
|
||||
* ✏️ Fix broken link to `@dataclass_transform` (now PEP 681) in `docs/features.md`. PR [#753](https://github.com/tiangolo/sqlmodel/pull/753) by [@soof-golan](https://github.com/soof-golan).
|
||||
|
||||
### Internal
|
||||
|
||||
* ⬆️ Upgrade Ruff and Black. PR [#968](https://github.com/tiangolo/sqlmodel/pull/968) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ⬆ Bump tiangolo/issue-manager from 0.4.1 to 0.5.0. PR [#922](https://github.com/tiangolo/sqlmodel/pull/922) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
* 📌 Pin typing-extensions in tests for compatiblity with Python 3.8, dirty-equals, Pydantic. PR [#965](https://github.com/tiangolo/sqlmodel/pull/965) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 Update GitHub Actions to download and upload artifacts. PR [#936](https://github.com/tiangolo/sqlmodel/pull/936) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 Tweak CI for test-redistribute, add needed env vars for slim. PR [#929](https://github.com/tiangolo/sqlmodel/pull/929) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.0.18
|
||||
|
||||
### Internal
|
||||
|
||||
* ✨ Add `sqlmodel-slim` setup. PR [#916](https://github.com/tiangolo/sqlmodel/pull/916) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
In the future SQLModel will include the standard default recommended packages, and `sqlmodel-slim` will come without those recommended standard packages and with a group of optional dependencies `sqlmodel-slim[standard]`, equivalent to `sqlmodel`, for those that want to opt out of those packages.
|
||||
|
||||
* 🔧 Re-enable MkDocs Material Social plugin. PR [#915](https://github.com/tiangolo/sqlmodel/pull/915) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.0.17
|
||||
|
||||
### Refactors
|
||||
|
||||
@@ -4,7 +4,7 @@ In the previous chapter, we saw how to add rows to the database using **SQLModel
|
||||
|
||||
Now let's talk a bit about why the `id` field **can't be `NULL`** on the database because it's a **primary key**, and we declare it using `Field(primary_key=True)`.
|
||||
|
||||
But the same `id` field actually **can be `None`** in the Python code, so we declare the type with `Optional[int]`, and set the default value to `Field(default=None)`:
|
||||
But the same `id` field actually **can be `None`** in the Python code, so we declare the type with `int | None (or Optional[int])`, and set the default value to `Field(default=None)`:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
|
||||
@@ -125,6 +125,8 @@ And delete that `./database.db` file in your project directory.
|
||||
|
||||
And click again on <kbd>New Database</kbd>.
|
||||
|
||||
Save the file with the name `database.db` again.
|
||||
|
||||
This time, if you see the dialog to create a new table, just close it by clicking the <kbd>Cancel</kbd> button.
|
||||
|
||||
And now, go to the tab <kbd>Execute SQL</kbd>.
|
||||
|
||||
@@ -145,7 +145,7 @@ Let's now see with more detail these field/column declarations.
|
||||
|
||||
### Optional Fields, Nullable Columns
|
||||
|
||||
Let's start with `age`, notice that it has a type of `Optional[int]`.
|
||||
Let's start with `age`, notice that it has a type of `int | None (or Optional[int])`.
|
||||
|
||||
And we import that `Optional` from the `typing` standard module.
|
||||
|
||||
@@ -354,7 +354,7 @@ But we will talk about it later.
|
||||
|
||||
### Engine Database URL
|
||||
|
||||
Each supported database has it's own URL type. For example, for **SQLite** it is `sqlite:///` followed by the file path. For example:
|
||||
Each supported database has its own URL type. For example, for **SQLite** it is `sqlite:///` followed by the file path. For example:
|
||||
|
||||
* `sqlite:///database.db`
|
||||
* `sqlite:///databases/local/application.db`
|
||||
@@ -470,7 +470,7 @@ If you didn't know about SQLAlchemy before and are just learning **SQLModel**, y
|
||||
|
||||
You can read a lot more about the engine in the <a href="https://docs.sqlalchemy.org/en/14/tutorial/engine.html" class="external-link" target="_blank">SQLAlchemy documentation</a>.
|
||||
|
||||
**SQLModel** defines it's own `create_engine()` function. It is the same as SQLAlchemy's `create_engine()`, but with the difference that it defaults to use `future=True` (which means that it uses the style of the latest SQLAlchemy, 1.4, and the future 2.0).
|
||||
**SQLModel** defines its own `create_engine()` function. It is the same as SQLAlchemy's `create_engine()`, but with the difference that it defaults to use `future=True` (which means that it uses the style of the latest SQLAlchemy, 1.4, and the future 2.0).
|
||||
|
||||
And SQLModel's version of `create_engine()` is type annotated internally, so your editor will be able to help you with autocompletion and inline errors.
|
||||
|
||||
@@ -688,7 +688,7 @@ In the example in the previous chapter we created the table using `TEXT` for som
|
||||
|
||||
But in this output SQLAlchemy is using `VARCHAR` instead. Let's see what's going on.
|
||||
|
||||
Remember that [each SQL Database has some different variations in what they support?](../databases/#sql-the-language){.internal-link target=_blank}
|
||||
Remember that [each SQL Database has some different variations in what they support?](../databases.md#sql-the-language){.internal-link target=_blank}
|
||||
|
||||
This is one of the differences. Each database supports some particular **data types**, like `INTEGER` and `TEXT`.
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ Now, after making sure we are inside of a virtual environment in some way, we ca
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
# (env) $$ python -m pip install sqlmodel
|
||||
# (env) $$ pip install sqlmodel
|
||||
---> 100%
|
||||
Successfully installed sqlmodel pydantic sqlalchemy
|
||||
```
|
||||
|
||||
@@ -90,7 +90,7 @@ Do you like **fancy words**? Cool! Programmers tend to like fancy words. 😅
|
||||
|
||||
That <abbr title="a recipe, a sequence of predefined steps that achieve a result">algorithm</abbr> I showed you above is called **Binary Search**.
|
||||
|
||||
It's called like that because you **search** something by splitting the dictionary (or any ordered list of things) in **two** ("binary" means "two") parts. And you do that process multiple times until you find what you want.
|
||||
It's called that because you **search** something by splitting the dictionary (or any ordered list of things) in **two** ("binary" means "two") parts. And you do that process multiple times until you find what you want.
|
||||
|
||||
///
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -134,7 +134,7 @@ Now let's do all that, but this time using the new, shiny `Relationship` attribu
|
||||
|
||||
Now we can create the `Team` instances and pass them directly to the new `team` argument when creating the `Hero` instances, as `team=team_preventers` instead of `team_id=team_preventers.id`.
|
||||
|
||||
And thanks to SQLAlchemy and how it works underneath, these teams don't even have to have an ID yet, but because we are assigning the whole object to each hero, those teams **will be automatically created** in the database, the automatic ID will be generated, and will be set in the `team_id` column for each of the corresponding hero rows.
|
||||
And thanks to SQLAlchemy and how it works underneath, these teams don't even need to have an ID yet, but because we are assigning the whole object to each hero, those teams **will be automatically created** in the database, the automatic ID will be generated, and will be set in the `team_id` column for each of the corresponding hero rows.
|
||||
|
||||
In fact, now we don't even have to put the teams explicitly in the session with `session.add(team)`, because these `Team` instances are **already associated** with heroes that **we do** `add` to the session.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ In the previous chapters we discussed how to manage databases with tables that h
|
||||
|
||||
And then we read the data together with `select()` and using `.where()` or `.join()` to connect it.
|
||||
|
||||
Now we will see how to use **Relationship Attributes**, an extra feature of **SQLModel** (and SQLAlchemy) to work with the data in the database in way much more familiar way, and closer to normal Python code.
|
||||
Now we will see how to use **Relationship Attributes**, an extra feature of **SQLModel** (and SQLAlchemy), to work with the data in the database in a much more familiar way, and closer to normal Python code.
|
||||
|
||||
/// info
|
||||
|
||||
|
||||
@@ -713,7 +713,7 @@ In this chapter we are touching some of them.
|
||||
|
||||
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.
|
||||
SQLAchemy also has its own `select`, and SQLModel's `select` uses SQLAlchemy's `select` internally.
|
||||
|
||||
But SQLModel's version does a lot of **tricks** with type annotations to make sure you get the best **editor support** possible, no matter if you use **VS Code**, **PyCharm**, or something else. ✨
|
||||
|
||||
|
||||
@@ -1250,7 +1250,7 @@ It would be an error telling you that
|
||||
|
||||
> `Hero.age` is potentially `None`, and you cannot compare `None` with `>`
|
||||
|
||||
This is because as we are using pure and plain Python annotations for the fields, `age` is indeed annotated as `Optional[int]`, which means `int` or `None`.
|
||||
This is because as we are using pure and plain Python annotations for the fields, `age` is indeed annotated as `int | None (or Optional[int])`.
|
||||
|
||||
By using this simple and standard Python type annotations we get the benefit of the extra simplicity and the inline error checks when creating or using instances. ✨
|
||||
|
||||
|
||||
0
docs_src/advanced/uuid/__init__.py
Normal file
0
docs_src/advanced/uuid/__init__.py
Normal file
65
docs_src/advanced/uuid/tutorial001.py
Normal file
65
docs_src/advanced/uuid/tutorial001.py
Normal file
@@ -0,0 +1,65 @@
|
||||
import uuid
|
||||
from typing import Union
|
||||
|
||||
from sqlmodel import Field, Session, SQLModel, create_engine, select
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: Union[int, None] = Field(default=None, index=True)
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
|
||||
def create_hero():
|
||||
with Session(engine) as session:
|
||||
hero = Hero(name="Deadpond", secret_name="Dive Wilson")
|
||||
print("The hero before saving in the DB")
|
||||
print(hero)
|
||||
print("The hero ID was already set")
|
||||
print(hero.id)
|
||||
session.add(hero)
|
||||
session.commit()
|
||||
session.refresh(hero)
|
||||
print("After saving in the DB")
|
||||
print(hero)
|
||||
|
||||
|
||||
def select_hero():
|
||||
with Session(engine) as session:
|
||||
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_2)
|
||||
session.commit()
|
||||
session.refresh(hero_2)
|
||||
hero_id = hero_2.id
|
||||
print("Created hero:")
|
||||
print(hero_2)
|
||||
print("Created hero ID:")
|
||||
print(hero_id)
|
||||
|
||||
statement = select(Hero).where(Hero.id == hero_id)
|
||||
selected_hero = session.exec(statement).one()
|
||||
print("Selected hero:")
|
||||
print(selected_hero)
|
||||
print("Selected hero ID:")
|
||||
print(selected_hero.id)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
create_db_and_tables()
|
||||
create_hero()
|
||||
select_hero()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
64
docs_src/advanced/uuid/tutorial001_py310.py
Normal file
64
docs_src/advanced/uuid/tutorial001_py310.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import uuid
|
||||
|
||||
from sqlmodel import Field, Session, SQLModel, create_engine, select
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: int | None = Field(default=None, index=True)
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
|
||||
def create_hero():
|
||||
with Session(engine) as session:
|
||||
hero = Hero(name="Deadpond", secret_name="Dive Wilson")
|
||||
print("The hero before saving in the DB")
|
||||
print(hero)
|
||||
print("The hero ID was already set")
|
||||
print(hero.id)
|
||||
session.add(hero)
|
||||
session.commit()
|
||||
session.refresh(hero)
|
||||
print("After saving in the DB")
|
||||
print(hero)
|
||||
|
||||
|
||||
def select_hero():
|
||||
with Session(engine) as session:
|
||||
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_2)
|
||||
session.commit()
|
||||
session.refresh(hero_2)
|
||||
hero_id = hero_2.id
|
||||
print("Created hero:")
|
||||
print(hero_2)
|
||||
print("Created hero ID:")
|
||||
print(hero_id)
|
||||
|
||||
statement = select(Hero).where(Hero.id == hero_id)
|
||||
selected_hero = session.exec(statement).one()
|
||||
print("Selected hero:")
|
||||
print(selected_hero)
|
||||
print("Selected hero ID:")
|
||||
print(selected_hero.id)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
create_db_and_tables()
|
||||
create_hero()
|
||||
select_hero()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
64
docs_src/advanced/uuid/tutorial002.py
Normal file
64
docs_src/advanced/uuid/tutorial002.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import uuid
|
||||
from typing import Union
|
||||
|
||||
from sqlmodel import Field, Session, SQLModel, create_engine
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: Union[int, None] = Field(default=None, index=True)
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
|
||||
def create_hero():
|
||||
with Session(engine) as session:
|
||||
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
|
||||
print("The hero before saving in the DB")
|
||||
print(hero_1)
|
||||
print("The hero ID was already set")
|
||||
print(hero_1.id)
|
||||
session.add(hero_1)
|
||||
session.commit()
|
||||
session.refresh(hero_1)
|
||||
print("After saving in the DB")
|
||||
print(hero_1)
|
||||
|
||||
|
||||
def select_hero():
|
||||
with Session(engine) as session:
|
||||
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_2)
|
||||
session.commit()
|
||||
session.refresh(hero_2)
|
||||
hero_id = hero_2.id
|
||||
print("Created hero:")
|
||||
print(hero_2)
|
||||
print("Created hero ID:")
|
||||
print(hero_id)
|
||||
|
||||
selected_hero = session.get(Hero, hero_id)
|
||||
print("Selected hero:")
|
||||
print(selected_hero)
|
||||
print("Selected hero ID:")
|
||||
print(selected_hero.id)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
create_db_and_tables()
|
||||
create_hero()
|
||||
select_hero()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
63
docs_src/advanced/uuid/tutorial002_py310.py
Normal file
63
docs_src/advanced/uuid/tutorial002_py310.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import uuid
|
||||
|
||||
from sqlmodel import Field, Session, SQLModel, create_engine
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: int | None = Field(default=None, index=True)
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
|
||||
def create_hero():
|
||||
with Session(engine) as session:
|
||||
hero = Hero(name="Deadpond", secret_name="Dive Wilson")
|
||||
print("The hero before saving in the DB")
|
||||
print(hero)
|
||||
print("The hero ID was already set")
|
||||
print(hero.id)
|
||||
session.add(hero)
|
||||
session.commit()
|
||||
session.refresh(hero)
|
||||
print("After saving in the DB")
|
||||
print(hero)
|
||||
|
||||
|
||||
def select_hero():
|
||||
with Session(engine) as session:
|
||||
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_2)
|
||||
session.commit()
|
||||
session.refresh(hero_2)
|
||||
hero_id = hero_2.id
|
||||
print("Created hero:")
|
||||
print(hero_2)
|
||||
print("Created hero ID:")
|
||||
print(hero_id)
|
||||
|
||||
selected_hero = session.get(Hero, hero_id)
|
||||
print("Selected hero:")
|
||||
print(selected_hero)
|
||||
print("Selected hero ID:")
|
||||
print(selected_hero.id)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
create_db_and_tables()
|
||||
create_hero()
|
||||
select_hero()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,110 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: List["Hero"] = Relationship(back_populates="team", cascade_delete=True)
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: Optional[int] = Field(default=None, index=True)
|
||||
|
||||
team_id: Optional[int] = Field(
|
||||
default=None, foreign_key="team.id", ondelete="CASCADE"
|
||||
)
|
||||
team: Optional[Team] = Relationship(back_populates="heroes")
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
hero_rusty_man = Hero(
|
||||
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
|
||||
)
|
||||
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_deadpond)
|
||||
session.add(hero_rusty_man)
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
session.refresh(hero_rusty_man)
|
||||
session.refresh(hero_spider_boy)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Created hero:", hero_rusty_man)
|
||||
print("Created hero:", hero_spider_boy)
|
||||
|
||||
hero_spider_boy.team = team_preventers
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
session.refresh(hero_spider_boy)
|
||||
print("Updated hero:", hero_spider_boy)
|
||||
|
||||
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
|
||||
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
|
||||
team_wakaland = Team(
|
||||
name="Wakaland",
|
||||
headquarters="Wakaland Capital City",
|
||||
heroes=[hero_black_lion, hero_sure_e],
|
||||
)
|
||||
session.add(team_wakaland)
|
||||
session.commit()
|
||||
session.refresh(team_wakaland)
|
||||
print("Team Wakaland:", team_wakaland)
|
||||
|
||||
|
||||
def delete_team():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
session.delete(team)
|
||||
session.commit()
|
||||
print("Deleted team:", team)
|
||||
|
||||
|
||||
def select_deleted_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Hero).where(Hero.name == "Black Lion")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Black Lion not found:", hero)
|
||||
|
||||
statement = select(Hero).where(Hero.name == "Princess Sure-E")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Princess Sure-E not found:", hero)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
delete_team()
|
||||
select_deleted_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,106 @@
|
||||
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: list["Hero"] = Relationship(back_populates="team", cascade_delete=True)
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: int | None = Field(default=None, index=True)
|
||||
|
||||
team_id: int | None = Field(default=None, foreign_key="team.id", ondelete="CASCADE")
|
||||
team: Team | None = Relationship(back_populates="heroes")
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
hero_rusty_man = Hero(
|
||||
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
|
||||
)
|
||||
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_deadpond)
|
||||
session.add(hero_rusty_man)
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
session.refresh(hero_rusty_man)
|
||||
session.refresh(hero_spider_boy)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Created hero:", hero_rusty_man)
|
||||
print("Created hero:", hero_spider_boy)
|
||||
|
||||
hero_spider_boy.team = team_preventers
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
session.refresh(hero_spider_boy)
|
||||
print("Updated hero:", hero_spider_boy)
|
||||
|
||||
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
|
||||
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
|
||||
team_wakaland = Team(
|
||||
name="Wakaland",
|
||||
headquarters="Wakaland Capital City",
|
||||
heroes=[hero_black_lion, hero_sure_e],
|
||||
)
|
||||
session.add(team_wakaland)
|
||||
session.commit()
|
||||
session.refresh(team_wakaland)
|
||||
print("Team Wakaland:", team_wakaland)
|
||||
|
||||
|
||||
def delete_team():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
session.delete(team)
|
||||
session.commit()
|
||||
print("Deleted team:", team)
|
||||
|
||||
|
||||
def select_deleted_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Hero).where(Hero.name == "Black Lion")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Black Lion not found:", hero)
|
||||
|
||||
statement = select(Hero).where(Hero.name == "Princess Sure-E")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Princess Sure-E not found:", hero)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
delete_team()
|
||||
select_deleted_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,110 @@
|
||||
from typing import Optional
|
||||
|
||||
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: list["Hero"] = Relationship(back_populates="team", cascade_delete=True)
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: Optional[int] = Field(default=None, index=True)
|
||||
|
||||
team_id: Optional[int] = Field(
|
||||
default=None, foreign_key="team.id", ondelete="CASCADE"
|
||||
)
|
||||
team: Optional[Team] = Relationship(back_populates="heroes")
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
hero_rusty_man = Hero(
|
||||
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
|
||||
)
|
||||
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_deadpond)
|
||||
session.add(hero_rusty_man)
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
session.refresh(hero_rusty_man)
|
||||
session.refresh(hero_spider_boy)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Created hero:", hero_rusty_man)
|
||||
print("Created hero:", hero_spider_boy)
|
||||
|
||||
hero_spider_boy.team = team_preventers
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
session.refresh(hero_spider_boy)
|
||||
print("Updated hero:", hero_spider_boy)
|
||||
|
||||
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
|
||||
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
|
||||
team_wakaland = Team(
|
||||
name="Wakaland",
|
||||
headquarters="Wakaland Capital City",
|
||||
heroes=[hero_black_lion, hero_sure_e],
|
||||
)
|
||||
session.add(team_wakaland)
|
||||
session.commit()
|
||||
session.refresh(team_wakaland)
|
||||
print("Team Wakaland:", team_wakaland)
|
||||
|
||||
|
||||
def delete_team():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
session.delete(team)
|
||||
session.commit()
|
||||
print("Deleted team:", team)
|
||||
|
||||
|
||||
def select_deleted_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Hero).where(Hero.name == "Black Lion")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Black Lion not found:", hero)
|
||||
|
||||
statement = select(Hero).where(Hero.name == "Princess Sure-E")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Princess Sure-E not found:", hero)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
delete_team()
|
||||
select_deleted_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,110 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: List["Hero"] = Relationship(back_populates="team")
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: Optional[int] = Field(default=None, index=True)
|
||||
|
||||
team_id: Optional[int] = Field(
|
||||
default=None, foreign_key="team.id", ondelete="SET NULL"
|
||||
)
|
||||
team: Optional[Team] = Relationship(back_populates="heroes")
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
hero_rusty_man = Hero(
|
||||
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
|
||||
)
|
||||
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_deadpond)
|
||||
session.add(hero_rusty_man)
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
session.refresh(hero_rusty_man)
|
||||
session.refresh(hero_spider_boy)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Created hero:", hero_rusty_man)
|
||||
print("Created hero:", hero_spider_boy)
|
||||
|
||||
hero_spider_boy.team = team_preventers
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
session.refresh(hero_spider_boy)
|
||||
print("Updated hero:", hero_spider_boy)
|
||||
|
||||
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
|
||||
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
|
||||
team_wakaland = Team(
|
||||
name="Wakaland",
|
||||
headquarters="Wakaland Capital City",
|
||||
heroes=[hero_black_lion, hero_sure_e],
|
||||
)
|
||||
session.add(team_wakaland)
|
||||
session.commit()
|
||||
session.refresh(team_wakaland)
|
||||
print("Team Wakaland:", team_wakaland)
|
||||
|
||||
|
||||
def delete_team():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
session.delete(team)
|
||||
session.commit()
|
||||
print("Deleted team:", team)
|
||||
|
||||
|
||||
def select_deleted_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Hero).where(Hero.name == "Black Lion")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Black Lion has no team:", hero)
|
||||
|
||||
statement = select(Hero).where(Hero.name == "Princess Sure-E")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Princess Sure-E has no team:", hero)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
delete_team()
|
||||
select_deleted_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,108 @@
|
||||
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: list["Hero"] = Relationship(back_populates="team")
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: int | None = Field(default=None, index=True)
|
||||
|
||||
team_id: int | None = Field(
|
||||
default=None, foreign_key="team.id", ondelete="SET NULL"
|
||||
)
|
||||
team: Team | None = Relationship(back_populates="heroes")
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
hero_rusty_man = Hero(
|
||||
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
|
||||
)
|
||||
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_deadpond)
|
||||
session.add(hero_rusty_man)
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
session.refresh(hero_rusty_man)
|
||||
session.refresh(hero_spider_boy)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Created hero:", hero_rusty_man)
|
||||
print("Created hero:", hero_spider_boy)
|
||||
|
||||
hero_spider_boy.team = team_preventers
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
session.refresh(hero_spider_boy)
|
||||
print("Updated hero:", hero_spider_boy)
|
||||
|
||||
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
|
||||
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
|
||||
team_wakaland = Team(
|
||||
name="Wakaland",
|
||||
headquarters="Wakaland Capital City",
|
||||
heroes=[hero_black_lion, hero_sure_e],
|
||||
)
|
||||
session.add(team_wakaland)
|
||||
session.commit()
|
||||
session.refresh(team_wakaland)
|
||||
print("Team Wakaland:", team_wakaland)
|
||||
|
||||
|
||||
def delete_team():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
session.delete(team)
|
||||
session.commit()
|
||||
print("Deleted team:", team)
|
||||
|
||||
|
||||
def select_deleted_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Hero).where(Hero.name == "Black Lion")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Black Lion has no team:", hero)
|
||||
|
||||
statement = select(Hero).where(Hero.name == "Princess Sure-E")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Princess Sure-E has no team:", hero)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
delete_team()
|
||||
select_deleted_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,110 @@
|
||||
from typing import Optional
|
||||
|
||||
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: list["Hero"] = Relationship(back_populates="team")
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: Optional[int] = Field(default=None, index=True)
|
||||
|
||||
team_id: Optional[int] = Field(
|
||||
default=None, foreign_key="team.id", ondelete="SET NULL"
|
||||
)
|
||||
team: Optional[Team] = Relationship(back_populates="heroes")
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
hero_rusty_man = Hero(
|
||||
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
|
||||
)
|
||||
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_deadpond)
|
||||
session.add(hero_rusty_man)
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
session.refresh(hero_rusty_man)
|
||||
session.refresh(hero_spider_boy)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Created hero:", hero_rusty_man)
|
||||
print("Created hero:", hero_spider_boy)
|
||||
|
||||
hero_spider_boy.team = team_preventers
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
session.refresh(hero_spider_boy)
|
||||
print("Updated hero:", hero_spider_boy)
|
||||
|
||||
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
|
||||
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
|
||||
team_wakaland = Team(
|
||||
name="Wakaland",
|
||||
headquarters="Wakaland Capital City",
|
||||
heroes=[hero_black_lion, hero_sure_e],
|
||||
)
|
||||
session.add(team_wakaland)
|
||||
session.commit()
|
||||
session.refresh(team_wakaland)
|
||||
print("Team Wakaland:", team_wakaland)
|
||||
|
||||
|
||||
def delete_team():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
session.delete(team)
|
||||
session.commit()
|
||||
print("Deleted team:", team)
|
||||
|
||||
|
||||
def select_deleted_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Hero).where(Hero.name == "Black Lion")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Black Lion has no team:", hero)
|
||||
|
||||
statement = select(Hero).where(Hero.name == "Princess Sure-E")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Princess Sure-E has no team:", hero)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
delete_team()
|
||||
select_deleted_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,112 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select, text
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: List["Hero"] = Relationship(back_populates="team", passive_deletes="all")
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: Optional[int] = Field(default=None, index=True)
|
||||
|
||||
team_id: Optional[int] = Field(
|
||||
default=None, foreign_key="team.id", ondelete="SET NULL"
|
||||
)
|
||||
team: Optional[Team] = Relationship(back_populates="heroes")
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
with engine.connect() as connection:
|
||||
connection.execute(text("PRAGMA foreign_keys=ON")) # for SQLite only
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
hero_rusty_man = Hero(
|
||||
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
|
||||
)
|
||||
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_deadpond)
|
||||
session.add(hero_rusty_man)
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
session.refresh(hero_rusty_man)
|
||||
session.refresh(hero_spider_boy)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Created hero:", hero_rusty_man)
|
||||
print("Created hero:", hero_spider_boy)
|
||||
|
||||
hero_spider_boy.team = team_preventers
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
session.refresh(hero_spider_boy)
|
||||
print("Updated hero:", hero_spider_boy)
|
||||
|
||||
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
|
||||
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
|
||||
team_wakaland = Team(
|
||||
name="Wakaland",
|
||||
headquarters="Wakaland Capital City",
|
||||
heroes=[hero_black_lion, hero_sure_e],
|
||||
)
|
||||
session.add(team_wakaland)
|
||||
session.commit()
|
||||
session.refresh(team_wakaland)
|
||||
print("Team Wakaland:", team_wakaland)
|
||||
|
||||
|
||||
def delete_team():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
session.delete(team)
|
||||
session.commit()
|
||||
print("Deleted team:", team)
|
||||
|
||||
|
||||
def select_deleted_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Hero).where(Hero.name == "Black Lion")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Black Lion has no team:", hero)
|
||||
|
||||
statement = select(Hero).where(Hero.name == "Princess Sure-E")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Princess Sure-E has no team:", hero)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
delete_team()
|
||||
select_deleted_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,110 @@
|
||||
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select, text
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: list["Hero"] = Relationship(back_populates="team", passive_deletes="all")
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: int | None = Field(default=None, index=True)
|
||||
|
||||
team_id: int | None = Field(
|
||||
default=None, foreign_key="team.id", ondelete="SET NULL"
|
||||
)
|
||||
team: Team | None = Relationship(back_populates="heroes")
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
with engine.connect() as connection:
|
||||
connection.execute(text("PRAGMA foreign_keys=ON")) # for SQLite only
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
hero_rusty_man = Hero(
|
||||
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
|
||||
)
|
||||
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_deadpond)
|
||||
session.add(hero_rusty_man)
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
session.refresh(hero_rusty_man)
|
||||
session.refresh(hero_spider_boy)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Created hero:", hero_rusty_man)
|
||||
print("Created hero:", hero_spider_boy)
|
||||
|
||||
hero_spider_boy.team = team_preventers
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
session.refresh(hero_spider_boy)
|
||||
print("Updated hero:", hero_spider_boy)
|
||||
|
||||
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
|
||||
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
|
||||
team_wakaland = Team(
|
||||
name="Wakaland",
|
||||
headquarters="Wakaland Capital City",
|
||||
heroes=[hero_black_lion, hero_sure_e],
|
||||
)
|
||||
session.add(team_wakaland)
|
||||
session.commit()
|
||||
session.refresh(team_wakaland)
|
||||
print("Team Wakaland:", team_wakaland)
|
||||
|
||||
|
||||
def delete_team():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
session.delete(team)
|
||||
session.commit()
|
||||
print("Deleted team:", team)
|
||||
|
||||
|
||||
def select_deleted_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Hero).where(Hero.name == "Black Lion")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Black Lion has no team:", hero)
|
||||
|
||||
statement = select(Hero).where(Hero.name == "Princess Sure-E")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Princess Sure-E has no team:", hero)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
delete_team()
|
||||
select_deleted_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,112 @@
|
||||
from typing import Optional
|
||||
|
||||
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select, text
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: list["Hero"] = Relationship(back_populates="team", passive_deletes="all")
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: Optional[int] = Field(default=None, index=True)
|
||||
|
||||
team_id: Optional[int] = Field(
|
||||
default=None, foreign_key="team.id", ondelete="SET NULL"
|
||||
)
|
||||
team: Optional[Team] = Relationship(back_populates="heroes")
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
with engine.connect() as connection:
|
||||
connection.execute(text("PRAGMA foreign_keys=ON")) # for SQLite only
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
hero_rusty_man = Hero(
|
||||
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
|
||||
)
|
||||
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_deadpond)
|
||||
session.add(hero_rusty_man)
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
session.refresh(hero_rusty_man)
|
||||
session.refresh(hero_spider_boy)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Created hero:", hero_rusty_man)
|
||||
print("Created hero:", hero_spider_boy)
|
||||
|
||||
hero_spider_boy.team = team_preventers
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
session.refresh(hero_spider_boy)
|
||||
print("Updated hero:", hero_spider_boy)
|
||||
|
||||
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
|
||||
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
|
||||
team_wakaland = Team(
|
||||
name="Wakaland",
|
||||
headquarters="Wakaland Capital City",
|
||||
heroes=[hero_black_lion, hero_sure_e],
|
||||
)
|
||||
session.add(team_wakaland)
|
||||
session.commit()
|
||||
session.refresh(team_wakaland)
|
||||
print("Team Wakaland:", team_wakaland)
|
||||
|
||||
|
||||
def delete_team():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
session.delete(team)
|
||||
session.commit()
|
||||
print("Deleted team:", team)
|
||||
|
||||
|
||||
def select_deleted_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Hero).where(Hero.name == "Black Lion")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Black Lion has no team:", hero)
|
||||
|
||||
statement = select(Hero).where(Hero.name == "Princess Sure-E")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Princess Sure-E has no team:", hero)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
delete_team()
|
||||
select_deleted_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,111 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select, text
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: List["Hero"] = Relationship(back_populates="team", passive_deletes="all")
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: Optional[int] = Field(default=None, index=True)
|
||||
|
||||
team_id: Optional[int] = Field(
|
||||
default=None, foreign_key="team.id", ondelete="RESTRICT"
|
||||
)
|
||||
team: Optional[Team] = Relationship(back_populates="heroes")
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
with engine.connect() as connection:
|
||||
connection.execute(text("PRAGMA foreign_keys=ON")) # for SQLite only
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
hero_rusty_man = Hero(
|
||||
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
|
||||
)
|
||||
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_deadpond)
|
||||
session.add(hero_rusty_man)
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
session.refresh(hero_rusty_man)
|
||||
session.refresh(hero_spider_boy)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Created hero:", hero_rusty_man)
|
||||
print("Created hero:", hero_spider_boy)
|
||||
|
||||
hero_spider_boy.team = team_preventers
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
session.refresh(hero_spider_boy)
|
||||
print("Updated hero:", hero_spider_boy)
|
||||
|
||||
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
|
||||
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
|
||||
team_wakaland = Team(
|
||||
name="Wakaland",
|
||||
headquarters="Wakaland Capital City",
|
||||
heroes=[hero_black_lion, hero_sure_e],
|
||||
)
|
||||
session.add(team_wakaland)
|
||||
session.commit()
|
||||
session.refresh(team_wakaland)
|
||||
print("Team Wakaland:", team_wakaland)
|
||||
|
||||
|
||||
def delete_team():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
session.delete(team)
|
||||
session.commit()
|
||||
print("Deleted team:", team)
|
||||
|
||||
|
||||
def select_deleted_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Hero).where(Hero.name == "Black Lion")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Black Lion has no team:", hero)
|
||||
|
||||
statement = select(Hero).where(Hero.name == "Princess Sure-E")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Princess Sure-E has no team:", hero)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
delete_team()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,109 @@
|
||||
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select, text
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: list["Hero"] = Relationship(back_populates="team", passive_deletes="all")
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: int | None = Field(default=None, index=True)
|
||||
|
||||
team_id: int | None = Field(
|
||||
default=None, foreign_key="team.id", ondelete="RESTRICT"
|
||||
)
|
||||
team: Team | None = Relationship(back_populates="heroes")
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
with engine.connect() as connection:
|
||||
connection.execute(text("PRAGMA foreign_keys=ON")) # for SQLite only
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
hero_rusty_man = Hero(
|
||||
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
|
||||
)
|
||||
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_deadpond)
|
||||
session.add(hero_rusty_man)
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
session.refresh(hero_rusty_man)
|
||||
session.refresh(hero_spider_boy)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Created hero:", hero_rusty_man)
|
||||
print("Created hero:", hero_spider_boy)
|
||||
|
||||
hero_spider_boy.team = team_preventers
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
session.refresh(hero_spider_boy)
|
||||
print("Updated hero:", hero_spider_boy)
|
||||
|
||||
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
|
||||
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
|
||||
team_wakaland = Team(
|
||||
name="Wakaland",
|
||||
headquarters="Wakaland Capital City",
|
||||
heroes=[hero_black_lion, hero_sure_e],
|
||||
)
|
||||
session.add(team_wakaland)
|
||||
session.commit()
|
||||
session.refresh(team_wakaland)
|
||||
print("Team Wakaland:", team_wakaland)
|
||||
|
||||
|
||||
def delete_team():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
session.delete(team)
|
||||
session.commit()
|
||||
print("Deleted team:", team)
|
||||
|
||||
|
||||
def select_deleted_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Hero).where(Hero.name == "Black Lion")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Black Lion has no team:", hero)
|
||||
|
||||
statement = select(Hero).where(Hero.name == "Princess Sure-E")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Princess Sure-E has no team:", hero)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
delete_team()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,111 @@
|
||||
from typing import Optional
|
||||
|
||||
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select, text
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: list["Hero"] = Relationship(back_populates="team", passive_deletes="all")
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: Optional[int] = Field(default=None, index=True)
|
||||
|
||||
team_id: Optional[int] = Field(
|
||||
default=None, foreign_key="team.id", ondelete="RESTRICT"
|
||||
)
|
||||
team: Optional[Team] = Relationship(back_populates="heroes")
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
with engine.connect() as connection:
|
||||
connection.execute(text("PRAGMA foreign_keys=ON")) # for SQLite only
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
hero_rusty_man = Hero(
|
||||
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
|
||||
)
|
||||
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_deadpond)
|
||||
session.add(hero_rusty_man)
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
session.refresh(hero_rusty_man)
|
||||
session.refresh(hero_spider_boy)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Created hero:", hero_rusty_man)
|
||||
print("Created hero:", hero_spider_boy)
|
||||
|
||||
hero_spider_boy.team = team_preventers
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
session.refresh(hero_spider_boy)
|
||||
print("Updated hero:", hero_spider_boy)
|
||||
|
||||
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
|
||||
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
|
||||
team_wakaland = Team(
|
||||
name="Wakaland",
|
||||
headquarters="Wakaland Capital City",
|
||||
heroes=[hero_black_lion, hero_sure_e],
|
||||
)
|
||||
session.add(team_wakaland)
|
||||
session.commit()
|
||||
session.refresh(team_wakaland)
|
||||
print("Team Wakaland:", team_wakaland)
|
||||
|
||||
|
||||
def delete_team():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
session.delete(team)
|
||||
session.commit()
|
||||
print("Deleted team:", team)
|
||||
|
||||
|
||||
def select_deleted_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Hero).where(Hero.name == "Black Lion")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Black Lion has no team:", hero)
|
||||
|
||||
statement = select(Hero).where(Hero.name == "Princess Sure-E")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Princess Sure-E has no team:", hero)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
delete_team()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,124 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select, text
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: List["Hero"] = Relationship(back_populates="team", passive_deletes="all")
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: Optional[int] = Field(default=None, index=True)
|
||||
|
||||
team_id: Optional[int] = Field(
|
||||
default=None, foreign_key="team.id", ondelete="RESTRICT"
|
||||
)
|
||||
team: Optional[Team] = Relationship(back_populates="heroes")
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
with engine.connect() as connection:
|
||||
connection.execute(text("PRAGMA foreign_keys=ON")) # for SQLite only
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
hero_rusty_man = Hero(
|
||||
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
|
||||
)
|
||||
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_deadpond)
|
||||
session.add(hero_rusty_man)
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
session.refresh(hero_rusty_man)
|
||||
session.refresh(hero_spider_boy)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Created hero:", hero_rusty_man)
|
||||
print("Created hero:", hero_spider_boy)
|
||||
|
||||
hero_spider_boy.team = team_preventers
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
session.refresh(hero_spider_boy)
|
||||
print("Updated hero:", hero_spider_boy)
|
||||
|
||||
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
|
||||
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
|
||||
team_wakaland = Team(
|
||||
name="Wakaland",
|
||||
headquarters="Wakaland Capital City",
|
||||
heroes=[hero_black_lion, hero_sure_e],
|
||||
)
|
||||
session.add(team_wakaland)
|
||||
session.commit()
|
||||
session.refresh(team_wakaland)
|
||||
print("Team Wakaland:", team_wakaland)
|
||||
|
||||
|
||||
def remove_team_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
team.heroes.clear()
|
||||
session.add(team)
|
||||
session.commit()
|
||||
session.refresh(team)
|
||||
print("Team with removed heroes:", team)
|
||||
|
||||
|
||||
def delete_team():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
session.delete(team)
|
||||
session.commit()
|
||||
print("Deleted team:", team)
|
||||
|
||||
|
||||
def select_deleted_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Hero).where(Hero.name == "Black Lion")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Black Lion has no team:", hero)
|
||||
|
||||
statement = select(Hero).where(Hero.name == "Princess Sure-E")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Princess Sure-E has no team:", hero)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
remove_team_heroes()
|
||||
delete_team()
|
||||
select_deleted_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,122 @@
|
||||
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select, text
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: list["Hero"] = Relationship(back_populates="team", passive_deletes="all")
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: int | None = Field(default=None, index=True)
|
||||
|
||||
team_id: int | None = Field(
|
||||
default=None, foreign_key="team.id", ondelete="RESTRICT"
|
||||
)
|
||||
team: Team | None = Relationship(back_populates="heroes")
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
with engine.connect() as connection:
|
||||
connection.execute(text("PRAGMA foreign_keys=ON")) # for SQLite only
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
hero_rusty_man = Hero(
|
||||
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
|
||||
)
|
||||
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_deadpond)
|
||||
session.add(hero_rusty_man)
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
session.refresh(hero_rusty_man)
|
||||
session.refresh(hero_spider_boy)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Created hero:", hero_rusty_man)
|
||||
print("Created hero:", hero_spider_boy)
|
||||
|
||||
hero_spider_boy.team = team_preventers
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
session.refresh(hero_spider_boy)
|
||||
print("Updated hero:", hero_spider_boy)
|
||||
|
||||
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
|
||||
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
|
||||
team_wakaland = Team(
|
||||
name="Wakaland",
|
||||
headquarters="Wakaland Capital City",
|
||||
heroes=[hero_black_lion, hero_sure_e],
|
||||
)
|
||||
session.add(team_wakaland)
|
||||
session.commit()
|
||||
session.refresh(team_wakaland)
|
||||
print("Team Wakaland:", team_wakaland)
|
||||
|
||||
|
||||
def remove_team_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
team.heroes.clear()
|
||||
session.add(team)
|
||||
session.commit()
|
||||
session.refresh(team)
|
||||
print("Team with removed heroes:", team)
|
||||
|
||||
|
||||
def delete_team():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
session.delete(team)
|
||||
session.commit()
|
||||
print("Deleted team:", team)
|
||||
|
||||
|
||||
def select_deleted_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Hero).where(Hero.name == "Black Lion")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Black Lion has no team:", hero)
|
||||
|
||||
statement = select(Hero).where(Hero.name == "Princess Sure-E")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Princess Sure-E has no team:", hero)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
remove_team_heroes()
|
||||
delete_team()
|
||||
select_deleted_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,124 @@
|
||||
from typing import Optional
|
||||
|
||||
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select, text
|
||||
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
headquarters: str
|
||||
|
||||
heroes: list["Hero"] = Relationship(back_populates="team", passive_deletes="all")
|
||||
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: Optional[int] = Field(default=None, index=True)
|
||||
|
||||
team_id: Optional[int] = Field(
|
||||
default=None, foreign_key="team.id", ondelete="RESTRICT"
|
||||
)
|
||||
team: Optional[Team] = Relationship(back_populates="heroes")
|
||||
|
||||
|
||||
sqlite_file_name = "database.db"
|
||||
sqlite_url = f"sqlite:///{sqlite_file_name}"
|
||||
|
||||
engine = create_engine(sqlite_url, echo=True)
|
||||
|
||||
|
||||
def create_db_and_tables():
|
||||
SQLModel.metadata.create_all(engine)
|
||||
with engine.connect() as connection:
|
||||
connection.execute(text("PRAGMA foreign_keys=ON")) # for SQLite only
|
||||
|
||||
|
||||
def create_heroes():
|
||||
with Session(engine) as session:
|
||||
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
|
||||
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
|
||||
|
||||
hero_deadpond = Hero(
|
||||
name="Deadpond", secret_name="Dive Wilson", team=team_z_force
|
||||
)
|
||||
hero_rusty_man = Hero(
|
||||
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
|
||||
)
|
||||
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
|
||||
session.add(hero_deadpond)
|
||||
session.add(hero_rusty_man)
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
|
||||
session.refresh(hero_deadpond)
|
||||
session.refresh(hero_rusty_man)
|
||||
session.refresh(hero_spider_boy)
|
||||
|
||||
print("Created hero:", hero_deadpond)
|
||||
print("Created hero:", hero_rusty_man)
|
||||
print("Created hero:", hero_spider_boy)
|
||||
|
||||
hero_spider_boy.team = team_preventers
|
||||
session.add(hero_spider_boy)
|
||||
session.commit()
|
||||
session.refresh(hero_spider_boy)
|
||||
print("Updated hero:", hero_spider_boy)
|
||||
|
||||
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
|
||||
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
|
||||
team_wakaland = Team(
|
||||
name="Wakaland",
|
||||
headquarters="Wakaland Capital City",
|
||||
heroes=[hero_black_lion, hero_sure_e],
|
||||
)
|
||||
session.add(team_wakaland)
|
||||
session.commit()
|
||||
session.refresh(team_wakaland)
|
||||
print("Team Wakaland:", team_wakaland)
|
||||
|
||||
|
||||
def remove_team_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
team.heroes.clear()
|
||||
session.add(team)
|
||||
session.commit()
|
||||
session.refresh(team)
|
||||
print("Team with removed heroes:", team)
|
||||
|
||||
|
||||
def delete_team():
|
||||
with Session(engine) as session:
|
||||
statement = select(Team).where(Team.name == "Wakaland")
|
||||
team = session.exec(statement).one()
|
||||
session.delete(team)
|
||||
session.commit()
|
||||
print("Deleted team:", team)
|
||||
|
||||
|
||||
def select_deleted_heroes():
|
||||
with Session(engine) as session:
|
||||
statement = select(Hero).where(Hero.name == "Black Lion")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Black Lion has no team:", hero)
|
||||
|
||||
statement = select(Hero).where(Hero.name == "Princess Sure-E")
|
||||
result = session.exec(statement)
|
||||
hero = result.first()
|
||||
print("Princess Sure-E has no team:", hero)
|
||||
|
||||
|
||||
def main():
|
||||
create_db_and_tables()
|
||||
create_heroes()
|
||||
remove_team_heroes()
|
||||
delete_team()
|
||||
select_deleted_heroes()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,4 +1,3 @@
|
||||
plugins:
|
||||
# TODO: Re-enable once this is fixed: https://github.com/squidfunk/mkdocs-material/issues/6983
|
||||
# social:
|
||||
social:
|
||||
typeset:
|
||||
|
||||
@@ -74,6 +74,7 @@ nav:
|
||||
- tutorial/relationship-attributes/read-relationships.md
|
||||
- tutorial/relationship-attributes/remove-relationships.md
|
||||
- tutorial/relationship-attributes/back-populates.md
|
||||
- tutorial/relationship-attributes/cascade-delete-relationships.md
|
||||
- tutorial/relationship-attributes/type-annotation-strings.md
|
||||
- Many to Many:
|
||||
- tutorial/many-to-many/index.md
|
||||
@@ -99,6 +100,7 @@ nav:
|
||||
- Advanced User Guide:
|
||||
- advanced/index.md
|
||||
- advanced/decimal.md
|
||||
- advanced/uuid.md
|
||||
- alternatives.md
|
||||
- help.md
|
||||
- contributing.md
|
||||
|
||||
39
pdm_build.py
Normal file
39
pdm_build.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import os
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from pdm.backend.hooks import Context
|
||||
|
||||
TIANGOLO_BUILD_PACKAGE = os.getenv("TIANGOLO_BUILD_PACKAGE", "sqlmodel")
|
||||
|
||||
|
||||
def pdm_build_initialize(context: Context) -> None:
|
||||
metadata = context.config.metadata
|
||||
# Get custom config for the current package, from the env var
|
||||
config: Dict[str, Any] = context.config.data["tool"]["tiangolo"][
|
||||
"_internal-slim-build"
|
||||
]["packages"][TIANGOLO_BUILD_PACKAGE]
|
||||
project_config: Dict[str, Any] = config["project"]
|
||||
# Get main optional dependencies, extras
|
||||
optional_dependencies: Dict[str, List[str]] = metadata.get(
|
||||
"optional-dependencies", {}
|
||||
)
|
||||
# Get custom optional dependencies name to always include in this (non-slim) package
|
||||
include_optional_dependencies: List[str] = config.get(
|
||||
"include-optional-dependencies", []
|
||||
)
|
||||
# Override main [project] configs with custom configs for this package
|
||||
for key, value in project_config.items():
|
||||
metadata[key] = value
|
||||
# Get custom build config for the current package
|
||||
build_config: Dict[str, Any] = (
|
||||
config.get("tool", {}).get("pdm", {}).get("build", {})
|
||||
)
|
||||
# Override PDM build config with custom build config for this package
|
||||
for key, value in build_config.items():
|
||||
context.config.build_config[key] = value
|
||||
# Get main dependencies
|
||||
dependencies: List[str] = metadata.get("dependencies", [])
|
||||
# Add optional dependencies to the default dependencies for this (non-slim) package
|
||||
for include_optional in include_optional_dependencies:
|
||||
optional_dependencies_group = optional_dependencies.get(include_optional, [])
|
||||
dependencies.extend(optional_dependencies_group)
|
||||
@@ -35,7 +35,7 @@ classifiers = [
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"SQLAlchemy >=2.0.0,<2.1.0",
|
||||
"SQLAlchemy >=2.0.14,<2.1.0",
|
||||
"pydantic >=1.10.13,<3.0.0",
|
||||
]
|
||||
|
||||
@@ -57,6 +57,18 @@ source-includes = [
|
||||
"sqlmodel/sql/expression.py.jinja2",
|
||||
]
|
||||
|
||||
[tool.tiangolo._internal-slim-build.packages.sqlmodel-slim.project]
|
||||
name = "sqlmodel-slim"
|
||||
|
||||
[tool.tiangolo._internal-slim-build.packages.sqlmodel]
|
||||
# include-optional-dependencies = ["standard"]
|
||||
|
||||
[tool.tiangolo._internal-slim-build.packages.sqlmodel.project]
|
||||
optional-dependencies = {}
|
||||
|
||||
# [tool.tiangolo._internal-slim-build.packages.sqlmodel.project.scripts]
|
||||
# sqlmodel = "sqlmodel.cli:main"
|
||||
|
||||
[tool.coverage.run]
|
||||
parallel = true
|
||||
source = [
|
||||
@@ -78,7 +90,7 @@ exclude_lines = [
|
||||
strict = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "sqlmodel.sql.expression"
|
||||
module = "sqlmodel.sql._expression_select_gen"
|
||||
warn_unused_ignores = false
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
# For mkdocstrings and code generator using templates
|
||||
black >=22.10,<24.0
|
||||
black >=22.10
|
||||
|
||||
@@ -10,9 +10,9 @@ jieba==0.42.1
|
||||
# For image processing by Material for MkDocs
|
||||
pillow==10.1.0
|
||||
# For image processing by Material for MkDocs
|
||||
cairosvg==2.7.0
|
||||
mkdocstrings[python]==0.23.0
|
||||
griffe-typingdoc==0.2.2
|
||||
cairosvg==2.7.1
|
||||
mkdocstrings[python]==0.25.1
|
||||
# Enable griffe-typingdoc once dropping Python 3.7 and upgrading typing-extensions
|
||||
# griffe-typingdoc==0.2.5
|
||||
# For griffe, it formats with black
|
||||
black==23.3.0
|
||||
typer == 0.12.3
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
pytest >=7.0.1,<8.0.0
|
||||
coverage[toml] >=6.2,<8.0
|
||||
mypy ==1.4.1
|
||||
ruff ==0.2.0
|
||||
ruff ==0.4.7
|
||||
# For FastAPI tests
|
||||
fastapi >=0.103.2
|
||||
httpx ==0.24.1
|
||||
# TODO: upgrade when deprecating Python 3.7
|
||||
dirty-equals ==0.6.0
|
||||
jinja2 ==3.1.3
|
||||
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
|
||||
typing-extensions ==4.6.1
|
||||
|
||||
@@ -7,8 +7,10 @@ import black
|
||||
from jinja2 import Template
|
||||
from pydantic import BaseModel
|
||||
|
||||
template_path = Path(__file__).parent.parent / "sqlmodel/sql/expression.py.jinja2"
|
||||
destiny_path = Path(__file__).parent.parent / "sqlmodel/sql/expression.py"
|
||||
template_path = (
|
||||
Path(__file__).parent.parent / "sqlmodel/sql/_expression_select_gen.py.jinja2"
|
||||
)
|
||||
destiny_path = Path(__file__).parent.parent / "sqlmodel/sql/_expression_select_gen.py"
|
||||
|
||||
|
||||
number_of_types = 4
|
||||
@@ -48,7 +50,7 @@ result = template.render(number_of_types=number_of_types, signatures=signatures)
|
||||
|
||||
result = (
|
||||
"# WARNING: do not modify this code, it is generated by "
|
||||
"expression.py.jinja2\n\n" + result
|
||||
"_expression_select_gen.py.jinja2\n\n" + result
|
||||
)
|
||||
|
||||
result = black.format_str(result, mode=black.Mode())
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
set -e
|
||||
set -x
|
||||
|
||||
CHECK_JINJA=1 python scripts/generate_select.py
|
||||
coverage run -m pytest tests
|
||||
coverage combine
|
||||
coverage report --show-missing
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
__version__ = "0.0.17"
|
||||
__version__ = "0.0.21"
|
||||
|
||||
# Re-export from SQLAlchemy
|
||||
from sqlalchemy.engine import create_engine as create_engine
|
||||
@@ -140,5 +140,4 @@ from .sql.expression import select as select
|
||||
from .sql.expression import tuple_ as tuple_
|
||||
from .sql.expression import type_coerce as type_coerce
|
||||
from .sql.expression import within_group as within_group
|
||||
from .sql.sqltypes import GUID as GUID
|
||||
from .sql.sqltypes import AutoString as AutoString
|
||||
|
||||
@@ -72,6 +72,7 @@ def partial_init() -> Generator[None, None, None]:
|
||||
|
||||
|
||||
if IS_PYDANTIC_V2:
|
||||
from annotated_types import MaxLen
|
||||
from pydantic import ConfigDict as BaseConfig
|
||||
from pydantic._internal._fields import PydanticMetadata
|
||||
from pydantic._internal._model_construction import ModelMetaclass
|
||||
@@ -193,7 +194,7 @@ if IS_PYDANTIC_V2:
|
||||
# Non optional unions are not allowed
|
||||
if bases[0] is not NoneType and bases[1] is not NoneType:
|
||||
raise ValueError(
|
||||
"Cannot have a (non-optional) union as a SQLlchemy field"
|
||||
"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]
|
||||
@@ -201,7 +202,7 @@ if IS_PYDANTIC_V2:
|
||||
|
||||
def get_field_metadata(field: Any) -> Any:
|
||||
for meta in field.metadata:
|
||||
if isinstance(meta, PydanticMetadata):
|
||||
if isinstance(meta, (PydanticMetadata, MaxLen)):
|
||||
return meta
|
||||
return FakeMetadata()
|
||||
|
||||
|
||||
@@ -43,8 +43,7 @@ class AsyncSession(_AsyncSession):
|
||||
bind_arguments: Optional[Dict[str, Any]] = None,
|
||||
_parent_execute_state: Optional[Any] = None,
|
||||
_add_event: Optional[Any] = None,
|
||||
) -> TupleResult[_TSelectParam]:
|
||||
...
|
||||
) -> TupleResult[_TSelectParam]: ...
|
||||
|
||||
@overload
|
||||
async def exec(
|
||||
@@ -56,8 +55,7 @@ class AsyncSession(_AsyncSession):
|
||||
bind_arguments: Optional[Dict[str, Any]] = None,
|
||||
_parent_execute_state: Optional[Any] = None,
|
||||
_add_event: Optional[Any] = None,
|
||||
) -> ScalarResult[_TSelectParam]:
|
||||
...
|
||||
) -> ScalarResult[_TSelectParam]: ...
|
||||
|
||||
async def exec(
|
||||
self,
|
||||
|
||||
138
sqlmodel/main.py
138
sqlmodel/main.py
@@ -25,7 +25,7 @@ from typing import (
|
||||
overload,
|
||||
)
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, EmailStr
|
||||
from pydantic.fields import FieldInfo as PydanticFieldInfo
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
@@ -51,7 +51,7 @@ from sqlalchemy.orm.attributes import set_attribute
|
||||
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
|
||||
from sqlalchemy.sql.sqltypes import LargeBinary, Time, Uuid
|
||||
from typing_extensions import Literal, deprecated, get_origin
|
||||
|
||||
from ._compat import ( # type: ignore[attr-defined]
|
||||
@@ -80,7 +80,7 @@ from ._compat import ( # type: ignore[attr-defined]
|
||||
sqlmodel_init,
|
||||
sqlmodel_validate,
|
||||
)
|
||||
from .sql.sqltypes import GUID, AutoString
|
||||
from .sql.sqltypes import AutoString
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pydantic._internal._model_construction import ModelMetaclass as ModelMetaclass
|
||||
@@ -91,6 +91,7 @@ if TYPE_CHECKING:
|
||||
_T = TypeVar("_T")
|
||||
NoArgAnyCallable = Callable[[], Any]
|
||||
IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any], None]
|
||||
OnDeleteType = Literal["CASCADE", "SET NULL", "RESTRICT"]
|
||||
|
||||
|
||||
def __dataclass_transform__(
|
||||
@@ -108,6 +109,7 @@ class FieldInfo(PydanticFieldInfo):
|
||||
primary_key = kwargs.pop("primary_key", False)
|
||||
nullable = kwargs.pop("nullable", Undefined)
|
||||
foreign_key = kwargs.pop("foreign_key", Undefined)
|
||||
ondelete = kwargs.pop("ondelete", Undefined)
|
||||
unique = kwargs.pop("unique", False)
|
||||
index = kwargs.pop("index", Undefined)
|
||||
sa_type = kwargs.pop("sa_type", Undefined)
|
||||
@@ -132,13 +134,17 @@ class FieldInfo(PydanticFieldInfo):
|
||||
)
|
||||
if nullable is not Undefined:
|
||||
raise RuntimeError(
|
||||
"Passing nullable is not supported when " "also passing a sa_column"
|
||||
"Passing nullable is not supported when also passing a sa_column"
|
||||
)
|
||||
if foreign_key is not Undefined:
|
||||
raise RuntimeError(
|
||||
"Passing foreign_key is not supported when "
|
||||
"also passing a sa_column"
|
||||
)
|
||||
if ondelete is not Undefined:
|
||||
raise RuntimeError(
|
||||
"Passing ondelete is not supported when also passing a sa_column"
|
||||
)
|
||||
if unique is not Undefined:
|
||||
raise RuntimeError(
|
||||
"Passing unique is not supported when also passing a sa_column"
|
||||
@@ -151,10 +157,14 @@ class FieldInfo(PydanticFieldInfo):
|
||||
raise RuntimeError(
|
||||
"Passing sa_type is not supported when also passing a sa_column"
|
||||
)
|
||||
if ondelete is not Undefined:
|
||||
if foreign_key is Undefined:
|
||||
raise RuntimeError("ondelete can only be used with foreign_key")
|
||||
super().__init__(default=default, **kwargs)
|
||||
self.primary_key = primary_key
|
||||
self.nullable = nullable
|
||||
self.foreign_key = foreign_key
|
||||
self.ondelete = ondelete
|
||||
self.unique = unique
|
||||
self.index = index
|
||||
self.sa_type = sa_type
|
||||
@@ -168,6 +178,8 @@ class RelationshipInfo(Representation):
|
||||
self,
|
||||
*,
|
||||
back_populates: Optional[str] = None,
|
||||
cascade_delete: Optional[bool] = False,
|
||||
passive_deletes: Optional[Union[bool, Literal["all"]]] = False,
|
||||
link_model: Optional[Any] = None,
|
||||
sa_relationship: Optional[RelationshipProperty] = None, # type: ignore
|
||||
sa_relationship_args: Optional[Sequence[Any]] = None,
|
||||
@@ -185,12 +197,15 @@ class RelationshipInfo(Representation):
|
||||
"also passing a sa_relationship"
|
||||
)
|
||||
self.back_populates = back_populates
|
||||
self.cascade_delete = cascade_delete
|
||||
self.passive_deletes = passive_deletes
|
||||
self.link_model = link_model
|
||||
self.sa_relationship = sa_relationship
|
||||
self.sa_relationship_args = sa_relationship_args
|
||||
self.sa_relationship_kwargs = sa_relationship_kwargs
|
||||
|
||||
|
||||
# include sa_type, sa_column_args, sa_column_kwargs
|
||||
@overload
|
||||
def Field(
|
||||
default: Any = Undefined,
|
||||
@@ -231,10 +246,65 @@ def Field(
|
||||
sa_column_args: Union[Sequence[Any], UndefinedType] = Undefined,
|
||||
sa_column_kwargs: Union[Mapping[str, Any], UndefinedType] = Undefined,
|
||||
schema_extra: Optional[Dict[str, Any]] = None,
|
||||
) -> Any:
|
||||
...
|
||||
) -> Any: ...
|
||||
|
||||
|
||||
# When foreign_key is str, include ondelete
|
||||
# include sa_type, sa_column_args, sa_column_kwargs
|
||||
@overload
|
||||
def Field(
|
||||
default: Any = Undefined,
|
||||
*,
|
||||
default_factory: Optional[NoArgAnyCallable] = None,
|
||||
alias: Optional[str] = None,
|
||||
title: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
exclude: Union[
|
||||
AbstractSet[Union[int, str]], Mapping[Union[int, str], Any], Any
|
||||
] = None,
|
||||
include: Union[
|
||||
AbstractSet[Union[int, str]], Mapping[Union[int, str], Any], Any
|
||||
] = None,
|
||||
const: Optional[bool] = None,
|
||||
gt: Optional[float] = None,
|
||||
ge: Optional[float] = None,
|
||||
lt: Optional[float] = None,
|
||||
le: Optional[float] = None,
|
||||
multiple_of: Optional[float] = None,
|
||||
max_digits: Optional[int] = None,
|
||||
decimal_places: Optional[int] = None,
|
||||
min_items: Optional[int] = None,
|
||||
max_items: Optional[int] = None,
|
||||
unique_items: Optional[bool] = None,
|
||||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
allow_mutation: bool = True,
|
||||
regex: Optional[str] = None,
|
||||
discriminator: Optional[str] = None,
|
||||
repr: bool = True,
|
||||
primary_key: Union[bool, UndefinedType] = Undefined,
|
||||
foreign_key: str,
|
||||
ondelete: Union[OnDeleteType, UndefinedType] = Undefined,
|
||||
unique: Union[bool, UndefinedType] = Undefined,
|
||||
nullable: Union[bool, UndefinedType] = Undefined,
|
||||
index: Union[bool, UndefinedType] = Undefined,
|
||||
sa_type: Union[Type[Any], UndefinedType] = Undefined,
|
||||
sa_column_args: Union[Sequence[Any], UndefinedType] = Undefined,
|
||||
sa_column_kwargs: Union[Mapping[str, Any], UndefinedType] = Undefined,
|
||||
schema_extra: Optional[Dict[str, Any]] = None,
|
||||
) -> Any: ...
|
||||
|
||||
|
||||
# Include sa_column, don't include
|
||||
# primary_key
|
||||
# foreign_key
|
||||
# ondelete
|
||||
# unique
|
||||
# nullable
|
||||
# index
|
||||
# sa_type
|
||||
# sa_column_args
|
||||
# sa_column_kwargs
|
||||
@overload
|
||||
def Field(
|
||||
default: Any = Undefined,
|
||||
@@ -268,8 +338,7 @@ def Field(
|
||||
repr: bool = True,
|
||||
sa_column: Union[Column, UndefinedType] = Undefined, # type: ignore
|
||||
schema_extra: Optional[Dict[str, Any]] = None,
|
||||
) -> Any:
|
||||
...
|
||||
) -> Any: ...
|
||||
|
||||
|
||||
def Field(
|
||||
@@ -304,6 +373,7 @@ def Field(
|
||||
repr: bool = True,
|
||||
primary_key: Union[bool, UndefinedType] = Undefined,
|
||||
foreign_key: Any = Undefined,
|
||||
ondelete: Union[OnDeleteType, UndefinedType] = Undefined,
|
||||
unique: Union[bool, UndefinedType] = Undefined,
|
||||
nullable: Union[bool, UndefinedType] = Undefined,
|
||||
index: Union[bool, UndefinedType] = Undefined,
|
||||
@@ -341,6 +411,7 @@ def Field(
|
||||
repr=repr,
|
||||
primary_key=primary_key,
|
||||
foreign_key=foreign_key,
|
||||
ondelete=ondelete,
|
||||
unique=unique,
|
||||
nullable=nullable,
|
||||
index=index,
|
||||
@@ -358,26 +429,30 @@ def Field(
|
||||
def Relationship(
|
||||
*,
|
||||
back_populates: Optional[str] = None,
|
||||
cascade_delete: Optional[bool] = False,
|
||||
passive_deletes: Optional[Union[bool, Literal["all"]]] = False,
|
||||
link_model: Optional[Any] = None,
|
||||
sa_relationship_args: Optional[Sequence[Any]] = None,
|
||||
sa_relationship_kwargs: Optional[Mapping[str, Any]] = None,
|
||||
) -> Any:
|
||||
...
|
||||
) -> Any: ...
|
||||
|
||||
|
||||
@overload
|
||||
def Relationship(
|
||||
*,
|
||||
back_populates: Optional[str] = None,
|
||||
cascade_delete: Optional[bool] = False,
|
||||
passive_deletes: Optional[Union[bool, Literal["all"]]] = False,
|
||||
link_model: Optional[Any] = None,
|
||||
sa_relationship: Optional[RelationshipProperty[Any]] = None,
|
||||
) -> Any:
|
||||
...
|
||||
) -> Any: ...
|
||||
|
||||
|
||||
def Relationship(
|
||||
*,
|
||||
back_populates: Optional[str] = None,
|
||||
cascade_delete: Optional[bool] = False,
|
||||
passive_deletes: Optional[Union[bool, Literal["all"]]] = False,
|
||||
link_model: Optional[Any] = None,
|
||||
sa_relationship: Optional[RelationshipProperty[Any]] = None,
|
||||
sa_relationship_args: Optional[Sequence[Any]] = None,
|
||||
@@ -385,6 +460,8 @@ def Relationship(
|
||||
) -> Any:
|
||||
relationship_info = RelationshipInfo(
|
||||
back_populates=back_populates,
|
||||
cascade_delete=cascade_delete,
|
||||
passive_deletes=passive_deletes,
|
||||
link_model=link_model,
|
||||
sa_relationship=sa_relationship,
|
||||
sa_relationship_args=sa_relationship_args,
|
||||
@@ -535,6 +612,10 @@ class SQLModelMetaclass(ModelMetaclass, DeclarativeMeta):
|
||||
rel_kwargs: Dict[str, Any] = {}
|
||||
if rel_info.back_populates:
|
||||
rel_kwargs["back_populates"] = rel_info.back_populates
|
||||
if rel_info.cascade_delete:
|
||||
rel_kwargs["cascade"] = "all, delete-orphan"
|
||||
if rel_info.passive_deletes:
|
||||
rel_kwargs["passive_deletes"] = rel_info.passive_deletes
|
||||
if rel_info.link_model:
|
||||
ins = inspect(rel_info.link_model)
|
||||
local_table = getattr(ins, "local_table") # noqa: B009
|
||||
@@ -574,7 +655,18 @@ def get_sqlalchemy_type(field: Any) -> Any:
|
||||
# Check enums first as an enum can also be a str, needed by Pydantic/FastAPI
|
||||
if issubclass(type_, Enum):
|
||||
return sa_Enum(type_)
|
||||
if issubclass(type_, str):
|
||||
if issubclass(
|
||||
type_,
|
||||
(
|
||||
str,
|
||||
ipaddress.IPv4Address,
|
||||
ipaddress.IPv4Network,
|
||||
ipaddress.IPv6Address,
|
||||
ipaddress.IPv6Network,
|
||||
Path,
|
||||
EmailStr,
|
||||
),
|
||||
):
|
||||
max_length = getattr(metadata, "max_length", None)
|
||||
if max_length:
|
||||
return AutoString(length=max_length)
|
||||
@@ -600,18 +692,8 @@ def get_sqlalchemy_type(field: Any) -> Any:
|
||||
precision=getattr(metadata, "max_digits", None),
|
||||
scale=getattr(metadata, "decimal_places", None),
|
||||
)
|
||||
if issubclass(type_, ipaddress.IPv4Address):
|
||||
return AutoString
|
||||
if issubclass(type_, ipaddress.IPv4Network):
|
||||
return AutoString
|
||||
if issubclass(type_, ipaddress.IPv6Address):
|
||||
return AutoString
|
||||
if issubclass(type_, ipaddress.IPv6Network):
|
||||
return AutoString
|
||||
if issubclass(type_, Path):
|
||||
return AutoString
|
||||
if issubclass(type_, uuid.UUID):
|
||||
return GUID
|
||||
return Uuid
|
||||
raise ValueError(f"{type_} has no matching SQLAlchemy type")
|
||||
|
||||
|
||||
@@ -645,8 +727,14 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
|
||||
if unique is Undefined:
|
||||
unique = False
|
||||
if foreign_key:
|
||||
if field_info.ondelete == "SET NULL" and not nullable:
|
||||
raise RuntimeError('ondelete="SET NULL" requires nullable=True')
|
||||
assert isinstance(foreign_key, str)
|
||||
args.append(ForeignKey(foreign_key))
|
||||
ondelete = getattr(field_info, "ondelete", Undefined)
|
||||
if ondelete is Undefined:
|
||||
ondelete = None
|
||||
assert isinstance(ondelete, (str, type(None))) # for typing
|
||||
args.append(ForeignKey(foreign_key, ondelete=ondelete))
|
||||
kwargs = {
|
||||
"primary_key": primary_key,
|
||||
"nullable": nullable,
|
||||
|
||||
@@ -35,8 +35,7 @@ class Session(_Session):
|
||||
bind_arguments: Optional[Dict[str, Any]] = None,
|
||||
_parent_execute_state: Optional[Any] = None,
|
||||
_add_event: Optional[Any] = None,
|
||||
) -> TupleResult[_TSelectParam]:
|
||||
...
|
||||
) -> TupleResult[_TSelectParam]: ...
|
||||
|
||||
@overload
|
||||
def exec(
|
||||
@@ -48,8 +47,7 @@ class Session(_Session):
|
||||
bind_arguments: Optional[Dict[str, Any]] = None,
|
||||
_parent_execute_state: Optional[Any] = None,
|
||||
_add_event: Optional[Any] = None,
|
||||
) -> ScalarResult[_TSelectParam]:
|
||||
...
|
||||
) -> ScalarResult[_TSelectParam]: ...
|
||||
|
||||
def exec(
|
||||
self,
|
||||
|
||||
43
sqlmodel/sql/_expression_select_cls.py
Normal file
43
sqlmodel/sql/_expression_select_cls.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from typing import (
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
from sqlalchemy.sql._typing import (
|
||||
_ColumnExpressionArgument,
|
||||
)
|
||||
from sqlalchemy.sql.expression import Select as _Select
|
||||
from typing_extensions import Self
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
# Separate this class in SelectBase, Select, and SelectOfScalar so that they can share
|
||||
# where and having without having type overlap incompatibility in session.exec().
|
||||
class SelectBase(_Select[Tuple[_T]]):
|
||||
inherit_cache = True
|
||||
|
||||
def where(self, *whereclause: Union[_ColumnExpressionArgument[bool], bool]) -> Self:
|
||||
"""Return a new `Select` construct with the given expression added to
|
||||
its `WHERE` clause, joined to the existing clause via `AND`, if any.
|
||||
"""
|
||||
return super().where(*whereclause) # type: ignore[arg-type]
|
||||
|
||||
def having(self, *having: Union[_ColumnExpressionArgument[bool], bool]) -> Self:
|
||||
"""Return a new `Select` construct with the given expression added to
|
||||
its `HAVING` clause, joined to the existing clause via `AND`, if any.
|
||||
"""
|
||||
return super().having(*having) # type: ignore[arg-type]
|
||||
|
||||
|
||||
class Select(SelectBase[_T]):
|
||||
inherit_cache = True
|
||||
|
||||
|
||||
# This is not comparable to sqlalchemy.sql.selectable.ScalarSelect, that has a different
|
||||
# purpose. This is the same as a normal SQLAlchemy Select class where there's only one
|
||||
# entity, so the result will be converted to a scalar by default. This way writing
|
||||
# for loops on the results will feel natural.
|
||||
class SelectOfScalar(SelectBase[_T]):
|
||||
inherit_cache = True
|
||||
367
sqlmodel/sql/_expression_select_gen.py
Normal file
367
sqlmodel/sql/_expression_select_gen.py
Normal file
@@ -0,0 +1,367 @@
|
||||
# WARNING: do not modify this code, it is generated by _expression_select_gen.py.jinja2
|
||||
|
||||
from datetime import datetime
|
||||
from typing import (
|
||||
Any,
|
||||
Mapping,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import (
|
||||
Column,
|
||||
)
|
||||
from sqlalchemy.sql.elements import (
|
||||
SQLCoreOperations,
|
||||
)
|
||||
from sqlalchemy.sql.roles import TypedColumnsClauseRole
|
||||
|
||||
from ._expression_select_cls import Select, SelectOfScalar
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
_TCCA = Union[
|
||||
TypedColumnsClauseRole[_T],
|
||||
SQLCoreOperations[_T],
|
||||
Type[_T],
|
||||
]
|
||||
|
||||
# Generated TypeVars start
|
||||
|
||||
|
||||
_TScalar_0 = TypeVar(
|
||||
"_TScalar_0",
|
||||
Column, # type: ignore
|
||||
Sequence, # type: ignore
|
||||
Mapping, # type: ignore
|
||||
UUID,
|
||||
datetime,
|
||||
float,
|
||||
int,
|
||||
bool,
|
||||
bytes,
|
||||
str,
|
||||
None,
|
||||
)
|
||||
|
||||
_T0 = TypeVar("_T0")
|
||||
|
||||
|
||||
_TScalar_1 = TypeVar(
|
||||
"_TScalar_1",
|
||||
Column, # type: ignore
|
||||
Sequence, # type: ignore
|
||||
Mapping, # type: ignore
|
||||
UUID,
|
||||
datetime,
|
||||
float,
|
||||
int,
|
||||
bool,
|
||||
bytes,
|
||||
str,
|
||||
None,
|
||||
)
|
||||
|
||||
_T1 = TypeVar("_T1")
|
||||
|
||||
|
||||
_TScalar_2 = TypeVar(
|
||||
"_TScalar_2",
|
||||
Column, # type: ignore
|
||||
Sequence, # type: ignore
|
||||
Mapping, # type: ignore
|
||||
UUID,
|
||||
datetime,
|
||||
float,
|
||||
int,
|
||||
bool,
|
||||
bytes,
|
||||
str,
|
||||
None,
|
||||
)
|
||||
|
||||
_T2 = TypeVar("_T2")
|
||||
|
||||
|
||||
_TScalar_3 = TypeVar(
|
||||
"_TScalar_3",
|
||||
Column, # type: ignore
|
||||
Sequence, # type: ignore
|
||||
Mapping, # type: ignore
|
||||
UUID,
|
||||
datetime,
|
||||
float,
|
||||
int,
|
||||
bool,
|
||||
bytes,
|
||||
str,
|
||||
None,
|
||||
)
|
||||
|
||||
_T3 = TypeVar("_T3")
|
||||
|
||||
|
||||
# Generated TypeVars end
|
||||
|
||||
|
||||
@overload
|
||||
def select(__ent0: _TCCA[_T0]) -> SelectOfScalar[_T0]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select(__ent0: _TScalar_0) -> SelectOfScalar[_TScalar_0]: # type: ignore
|
||||
...
|
||||
|
||||
|
||||
# Generated overloads start
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
__ent1: _TCCA[_T1],
|
||||
) -> Select[Tuple[_T0, _T1]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
entity_1: _TScalar_1,
|
||||
) -> Select[Tuple[_T0, _TScalar_1]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
__ent1: _TCCA[_T1],
|
||||
) -> Select[Tuple[_TScalar_0, _T1]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
entity_1: _TScalar_1,
|
||||
) -> Select[Tuple[_TScalar_0, _TScalar_1]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
__ent1: _TCCA[_T1],
|
||||
__ent2: _TCCA[_T2],
|
||||
) -> Select[Tuple[_T0, _T1, _T2]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
__ent1: _TCCA[_T1],
|
||||
entity_2: _TScalar_2,
|
||||
) -> Select[Tuple[_T0, _T1, _TScalar_2]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
entity_1: _TScalar_1,
|
||||
__ent2: _TCCA[_T2],
|
||||
) -> Select[Tuple[_T0, _TScalar_1, _T2]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
entity_1: _TScalar_1,
|
||||
entity_2: _TScalar_2,
|
||||
) -> Select[Tuple[_T0, _TScalar_1, _TScalar_2]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
__ent1: _TCCA[_T1],
|
||||
__ent2: _TCCA[_T2],
|
||||
) -> Select[Tuple[_TScalar_0, _T1, _T2]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
__ent1: _TCCA[_T1],
|
||||
entity_2: _TScalar_2,
|
||||
) -> Select[Tuple[_TScalar_0, _T1, _TScalar_2]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
entity_1: _TScalar_1,
|
||||
__ent2: _TCCA[_T2],
|
||||
) -> Select[Tuple[_TScalar_0, _TScalar_1, _T2]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
entity_1: _TScalar_1,
|
||||
entity_2: _TScalar_2,
|
||||
) -> Select[Tuple[_TScalar_0, _TScalar_1, _TScalar_2]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
__ent1: _TCCA[_T1],
|
||||
__ent2: _TCCA[_T2],
|
||||
__ent3: _TCCA[_T3],
|
||||
) -> Select[Tuple[_T0, _T1, _T2, _T3]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
__ent1: _TCCA[_T1],
|
||||
__ent2: _TCCA[_T2],
|
||||
entity_3: _TScalar_3,
|
||||
) -> Select[Tuple[_T0, _T1, _T2, _TScalar_3]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
__ent1: _TCCA[_T1],
|
||||
entity_2: _TScalar_2,
|
||||
__ent3: _TCCA[_T3],
|
||||
) -> Select[Tuple[_T0, _T1, _TScalar_2, _T3]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
__ent1: _TCCA[_T1],
|
||||
entity_2: _TScalar_2,
|
||||
entity_3: _TScalar_3,
|
||||
) -> Select[Tuple[_T0, _T1, _TScalar_2, _TScalar_3]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
entity_1: _TScalar_1,
|
||||
__ent2: _TCCA[_T2],
|
||||
__ent3: _TCCA[_T3],
|
||||
) -> Select[Tuple[_T0, _TScalar_1, _T2, _T3]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
entity_1: _TScalar_1,
|
||||
__ent2: _TCCA[_T2],
|
||||
entity_3: _TScalar_3,
|
||||
) -> Select[Tuple[_T0, _TScalar_1, _T2, _TScalar_3]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
entity_1: _TScalar_1,
|
||||
entity_2: _TScalar_2,
|
||||
__ent3: _TCCA[_T3],
|
||||
) -> Select[Tuple[_T0, _TScalar_1, _TScalar_2, _T3]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
entity_1: _TScalar_1,
|
||||
entity_2: _TScalar_2,
|
||||
entity_3: _TScalar_3,
|
||||
) -> Select[Tuple[_T0, _TScalar_1, _TScalar_2, _TScalar_3]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
__ent1: _TCCA[_T1],
|
||||
__ent2: _TCCA[_T2],
|
||||
__ent3: _TCCA[_T3],
|
||||
) -> Select[Tuple[_TScalar_0, _T1, _T2, _T3]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
__ent1: _TCCA[_T1],
|
||||
__ent2: _TCCA[_T2],
|
||||
entity_3: _TScalar_3,
|
||||
) -> Select[Tuple[_TScalar_0, _T1, _T2, _TScalar_3]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
__ent1: _TCCA[_T1],
|
||||
entity_2: _TScalar_2,
|
||||
__ent3: _TCCA[_T3],
|
||||
) -> Select[Tuple[_TScalar_0, _T1, _TScalar_2, _T3]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
__ent1: _TCCA[_T1],
|
||||
entity_2: _TScalar_2,
|
||||
entity_3: _TScalar_3,
|
||||
) -> Select[Tuple[_TScalar_0, _T1, _TScalar_2, _TScalar_3]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
entity_1: _TScalar_1,
|
||||
__ent2: _TCCA[_T2],
|
||||
__ent3: _TCCA[_T3],
|
||||
) -> Select[Tuple[_TScalar_0, _TScalar_1, _T2, _T3]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
entity_1: _TScalar_1,
|
||||
__ent2: _TCCA[_T2],
|
||||
entity_3: _TScalar_3,
|
||||
) -> Select[Tuple[_TScalar_0, _TScalar_1, _T2, _TScalar_3]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
entity_1: _TScalar_1,
|
||||
entity_2: _TScalar_2,
|
||||
__ent3: _TCCA[_T3],
|
||||
) -> Select[Tuple[_TScalar_0, _TScalar_1, _TScalar_2, _T3]]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
entity_1: _TScalar_1,
|
||||
entity_2: _TScalar_2,
|
||||
entity_3: _TScalar_3,
|
||||
) -> Select[Tuple[_TScalar_0, _TScalar_1, _TScalar_2, _TScalar_3]]: ...
|
||||
|
||||
|
||||
# Generated overloads end
|
||||
|
||||
|
||||
def select(*entities: Any) -> Union[Select, SelectOfScalar]: # type: ignore
|
||||
if len(entities) == 1:
|
||||
return SelectOfScalar(*entities)
|
||||
return Select(*entities)
|
||||
84
sqlmodel/sql/_expression_select_gen.py.jinja2
Normal file
84
sqlmodel/sql/_expression_select_gen.py.jinja2
Normal file
@@ -0,0 +1,84 @@
|
||||
from datetime import datetime
|
||||
from typing import (
|
||||
Any,
|
||||
Mapping,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import (
|
||||
Column,
|
||||
)
|
||||
from sqlalchemy.sql.elements import (
|
||||
SQLCoreOperations,
|
||||
)
|
||||
from sqlalchemy.sql.roles import TypedColumnsClauseRole
|
||||
|
||||
from ._expression_select_cls import Select, SelectOfScalar
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
_TCCA = Union[
|
||||
TypedColumnsClauseRole[_T],
|
||||
SQLCoreOperations[_T],
|
||||
Type[_T],
|
||||
]
|
||||
|
||||
# Generated TypeVars start
|
||||
|
||||
|
||||
{% for i in range(number_of_types) %}
|
||||
_TScalar_{{ i }} = TypeVar(
|
||||
"_TScalar_{{ i }}",
|
||||
Column, # type: ignore
|
||||
Sequence, # type: ignore
|
||||
Mapping, # type: ignore
|
||||
UUID,
|
||||
datetime,
|
||||
float,
|
||||
int,
|
||||
bool,
|
||||
bytes,
|
||||
str,
|
||||
None,
|
||||
)
|
||||
|
||||
_T{{ i }} = TypeVar("_T{{ i }}")
|
||||
|
||||
{% endfor %}
|
||||
|
||||
# Generated TypeVars end
|
||||
|
||||
@overload
|
||||
def select(__ent0: _TCCA[_T0]) -> SelectOfScalar[_T0]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def select(__ent0: _TScalar_0) -> SelectOfScalar[_TScalar_0]: # type: ignore
|
||||
...
|
||||
|
||||
|
||||
# Generated overloads start
|
||||
|
||||
{% for signature in signatures %}
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
{% for arg in signature[0] %}{{ arg.name }}: {{ arg.annotation }}, {% endfor %}
|
||||
) -> Select[Tuple[{%for ret in signature[1] %}{{ ret }} {% if not loop.last %}, {% endif %}{% endfor %}]]: ...
|
||||
|
||||
{% endfor %}
|
||||
|
||||
# Generated overloads end
|
||||
|
||||
|
||||
def select(*entities: Any) -> Union[Select, SelectOfScalar]: # type: ignore
|
||||
if len(entities) == 1:
|
||||
return SelectOfScalar(*entities)
|
||||
return Select(*entities)
|
||||
@@ -1,6 +1,3 @@
|
||||
# WARNING: do not modify this code, it is generated by expression.py.jinja2
|
||||
|
||||
from datetime import datetime
|
||||
from typing import (
|
||||
Any,
|
||||
Iterable,
|
||||
@@ -11,9 +8,7 @@ from typing import (
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
from uuid import UUID
|
||||
|
||||
import sqlalchemy
|
||||
from sqlalchemy import (
|
||||
@@ -39,14 +34,15 @@ from sqlalchemy.sql.elements import (
|
||||
Cast,
|
||||
CollectionAggregate,
|
||||
ColumnClause,
|
||||
SQLCoreOperations,
|
||||
TryCast,
|
||||
UnaryExpression,
|
||||
)
|
||||
from sqlalchemy.sql.expression import Select as _Select
|
||||
from sqlalchemy.sql.roles import TypedColumnsClauseRole
|
||||
from sqlalchemy.sql.type_api import TypeEngine
|
||||
from typing_extensions import Literal, Self
|
||||
from typing_extensions import Literal
|
||||
|
||||
from ._expression_select_cls import Select as Select
|
||||
from ._expression_select_cls import SelectOfScalar as SelectOfScalar
|
||||
from ._expression_select_gen import select as select
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
@@ -89,7 +85,7 @@ def between(
|
||||
upper_bound: Any,
|
||||
symmetric: bool = False,
|
||||
) -> BinaryExpression[bool]:
|
||||
return sqlalchemy.between(expr, lower_bound, upper_bound, symmetric=symmetric) # type: ignore[arg-type]
|
||||
return sqlalchemy.between(expr, lower_bound, upper_bound, symmetric=symmetric)
|
||||
|
||||
|
||||
def not_(clause: Union[_ColumnExpressionArgument[_T], _T]) -> ColumnElement[_T]:
|
||||
@@ -110,14 +106,14 @@ def cast(
|
||||
expression: Union[_ColumnExpressionOrLiteralArgument[Any], Any],
|
||||
type_: "_TypeEngineArgument[_T]",
|
||||
) -> Cast[_T]:
|
||||
return sqlalchemy.cast(expression, type_) # type: ignore[arg-type]
|
||||
return sqlalchemy.cast(expression, type_)
|
||||
|
||||
|
||||
def try_cast(
|
||||
expression: Union[_ColumnExpressionOrLiteralArgument[Any], Any],
|
||||
type_: "_TypeEngineArgument[_T]",
|
||||
) -> TryCast[_T]:
|
||||
return sqlalchemy.try_cast(expression, type_) # type: ignore[arg-type]
|
||||
return sqlalchemy.try_cast(expression, type_)
|
||||
|
||||
|
||||
def desc(
|
||||
@@ -135,7 +131,7 @@ def bitwise_not(expr: Union[_ColumnExpressionArgument[_T], _T]) -> UnaryExpressi
|
||||
|
||||
|
||||
def extract(field: str, expr: Union[_ColumnExpressionArgument[Any], Any]) -> Extract:
|
||||
return sqlalchemy.extract(field, expr) # type: ignore[arg-type]
|
||||
return sqlalchemy.extract(field, expr)
|
||||
|
||||
|
||||
def funcfilter(
|
||||
@@ -162,7 +158,7 @@ def nulls_last(column: Union[_ColumnExpressionArgument[_T], _T]) -> UnaryExpress
|
||||
return sqlalchemy.nulls_last(column) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def or_( # type: ignore[empty-body]
|
||||
def or_(
|
||||
initial_clause: Union[Literal[False], _ColumnExpressionArgument[bool], bool],
|
||||
*clauses: Union[_ColumnExpressionArgument[bool], bool],
|
||||
) -> ColumnElement[bool]:
|
||||
@@ -190,7 +186,7 @@ def over(
|
||||
) -> Over[_T]:
|
||||
return sqlalchemy.over(
|
||||
element, partition_by=partition_by, order_by=order_by, range_=range_, rows=rows
|
||||
) # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
||||
def tuple_(
|
||||
@@ -204,413 +200,13 @@ def type_coerce(
|
||||
expression: Union[_ColumnExpressionOrLiteralArgument[Any], Any],
|
||||
type_: "_TypeEngineArgument[_T]",
|
||||
) -> TypeCoerce[_T]:
|
||||
return sqlalchemy.type_coerce(expression, type_) # type: ignore[arg-type]
|
||||
return sqlalchemy.type_coerce(expression, type_)
|
||||
|
||||
|
||||
def within_group(
|
||||
element: FunctionElement[_T], *order_by: Union[_ColumnExpressionArgument[Any], Any]
|
||||
) -> WithinGroup[_T]:
|
||||
return sqlalchemy.within_group(element, *order_by) # type: ignore[arg-type]
|
||||
|
||||
|
||||
# Separate this class in SelectBase, Select, and SelectOfScalar so that they can share
|
||||
# where and having without having type overlap incompatibility in session.exec().
|
||||
class SelectBase(_Select[Tuple[_T]]):
|
||||
inherit_cache = True
|
||||
|
||||
def where(self, *whereclause: Union[_ColumnExpressionArgument[bool], bool]) -> Self:
|
||||
"""Return a new `Select` construct with the given expression added to
|
||||
its `WHERE` clause, joined to the existing clause via `AND`, if any.
|
||||
"""
|
||||
return super().where(*whereclause) # type: ignore[arg-type]
|
||||
|
||||
def having(self, *having: Union[_ColumnExpressionArgument[bool], bool]) -> Self:
|
||||
"""Return a new `Select` construct with the given expression added to
|
||||
its `HAVING` clause, joined to the existing clause via `AND`, if any.
|
||||
"""
|
||||
return super().having(*having) # type: ignore[arg-type]
|
||||
|
||||
|
||||
class Select(SelectBase[_T]):
|
||||
inherit_cache = True
|
||||
|
||||
|
||||
# This is not comparable to sqlalchemy.sql.selectable.ScalarSelect, that has a different
|
||||
# purpose. This is the same as a normal SQLAlchemy Select class where there's only one
|
||||
# entity, so the result will be converted to a scalar by default. This way writing
|
||||
# for loops on the results will feel natural.
|
||||
class SelectOfScalar(SelectBase[_T]):
|
||||
inherit_cache = True
|
||||
|
||||
|
||||
_TCCA = Union[
|
||||
TypedColumnsClauseRole[_T],
|
||||
SQLCoreOperations[_T],
|
||||
Type[_T],
|
||||
]
|
||||
|
||||
# Generated TypeVars start
|
||||
|
||||
|
||||
_TScalar_0 = TypeVar(
|
||||
"_TScalar_0",
|
||||
Column, # type: ignore
|
||||
Sequence, # type: ignore
|
||||
Mapping, # type: ignore
|
||||
UUID,
|
||||
datetime,
|
||||
float,
|
||||
int,
|
||||
bool,
|
||||
bytes,
|
||||
str,
|
||||
None,
|
||||
)
|
||||
|
||||
_T0 = TypeVar("_T0")
|
||||
|
||||
|
||||
_TScalar_1 = TypeVar(
|
||||
"_TScalar_1",
|
||||
Column, # type: ignore
|
||||
Sequence, # type: ignore
|
||||
Mapping, # type: ignore
|
||||
UUID,
|
||||
datetime,
|
||||
float,
|
||||
int,
|
||||
bool,
|
||||
bytes,
|
||||
str,
|
||||
None,
|
||||
)
|
||||
|
||||
_T1 = TypeVar("_T1")
|
||||
|
||||
|
||||
_TScalar_2 = TypeVar(
|
||||
"_TScalar_2",
|
||||
Column, # type: ignore
|
||||
Sequence, # type: ignore
|
||||
Mapping, # type: ignore
|
||||
UUID,
|
||||
datetime,
|
||||
float,
|
||||
int,
|
||||
bool,
|
||||
bytes,
|
||||
str,
|
||||
None,
|
||||
)
|
||||
|
||||
_T2 = TypeVar("_T2")
|
||||
|
||||
|
||||
_TScalar_3 = TypeVar(
|
||||
"_TScalar_3",
|
||||
Column, # type: ignore
|
||||
Sequence, # type: ignore
|
||||
Mapping, # type: ignore
|
||||
UUID,
|
||||
datetime,
|
||||
float,
|
||||
int,
|
||||
bool,
|
||||
bytes,
|
||||
str,
|
||||
None,
|
||||
)
|
||||
|
||||
_T3 = TypeVar("_T3")
|
||||
|
||||
|
||||
# Generated TypeVars end
|
||||
|
||||
|
||||
@overload
|
||||
def select(__ent0: _TCCA[_T0]) -> SelectOfScalar[_T0]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select(__ent0: _TScalar_0) -> SelectOfScalar[_TScalar_0]: # type: ignore
|
||||
...
|
||||
|
||||
|
||||
# Generated overloads start
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
__ent1: _TCCA[_T1],
|
||||
) -> Select[Tuple[_T0, _T1]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
entity_1: _TScalar_1,
|
||||
) -> Select[Tuple[_T0, _TScalar_1]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
__ent1: _TCCA[_T1],
|
||||
) -> Select[Tuple[_TScalar_0, _T1]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
entity_1: _TScalar_1,
|
||||
) -> Select[Tuple[_TScalar_0, _TScalar_1]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
__ent1: _TCCA[_T1],
|
||||
__ent2: _TCCA[_T2],
|
||||
) -> Select[Tuple[_T0, _T1, _T2]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
__ent1: _TCCA[_T1],
|
||||
entity_2: _TScalar_2,
|
||||
) -> Select[Tuple[_T0, _T1, _TScalar_2]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
entity_1: _TScalar_1,
|
||||
__ent2: _TCCA[_T2],
|
||||
) -> Select[Tuple[_T0, _TScalar_1, _T2]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
entity_1: _TScalar_1,
|
||||
entity_2: _TScalar_2,
|
||||
) -> Select[Tuple[_T0, _TScalar_1, _TScalar_2]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
__ent1: _TCCA[_T1],
|
||||
__ent2: _TCCA[_T2],
|
||||
) -> Select[Tuple[_TScalar_0, _T1, _T2]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
__ent1: _TCCA[_T1],
|
||||
entity_2: _TScalar_2,
|
||||
) -> Select[Tuple[_TScalar_0, _T1, _TScalar_2]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
entity_1: _TScalar_1,
|
||||
__ent2: _TCCA[_T2],
|
||||
) -> Select[Tuple[_TScalar_0, _TScalar_1, _T2]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
entity_1: _TScalar_1,
|
||||
entity_2: _TScalar_2,
|
||||
) -> Select[Tuple[_TScalar_0, _TScalar_1, _TScalar_2]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
__ent1: _TCCA[_T1],
|
||||
__ent2: _TCCA[_T2],
|
||||
__ent3: _TCCA[_T3],
|
||||
) -> Select[Tuple[_T0, _T1, _T2, _T3]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
__ent1: _TCCA[_T1],
|
||||
__ent2: _TCCA[_T2],
|
||||
entity_3: _TScalar_3,
|
||||
) -> Select[Tuple[_T0, _T1, _T2, _TScalar_3]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
__ent1: _TCCA[_T1],
|
||||
entity_2: _TScalar_2,
|
||||
__ent3: _TCCA[_T3],
|
||||
) -> Select[Tuple[_T0, _T1, _TScalar_2, _T3]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
__ent1: _TCCA[_T1],
|
||||
entity_2: _TScalar_2,
|
||||
entity_3: _TScalar_3,
|
||||
) -> Select[Tuple[_T0, _T1, _TScalar_2, _TScalar_3]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
entity_1: _TScalar_1,
|
||||
__ent2: _TCCA[_T2],
|
||||
__ent3: _TCCA[_T3],
|
||||
) -> Select[Tuple[_T0, _TScalar_1, _T2, _T3]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
entity_1: _TScalar_1,
|
||||
__ent2: _TCCA[_T2],
|
||||
entity_3: _TScalar_3,
|
||||
) -> Select[Tuple[_T0, _TScalar_1, _T2, _TScalar_3]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
entity_1: _TScalar_1,
|
||||
entity_2: _TScalar_2,
|
||||
__ent3: _TCCA[_T3],
|
||||
) -> Select[Tuple[_T0, _TScalar_1, _TScalar_2, _T3]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
__ent0: _TCCA[_T0],
|
||||
entity_1: _TScalar_1,
|
||||
entity_2: _TScalar_2,
|
||||
entity_3: _TScalar_3,
|
||||
) -> Select[Tuple[_T0, _TScalar_1, _TScalar_2, _TScalar_3]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
__ent1: _TCCA[_T1],
|
||||
__ent2: _TCCA[_T2],
|
||||
__ent3: _TCCA[_T3],
|
||||
) -> Select[Tuple[_TScalar_0, _T1, _T2, _T3]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
__ent1: _TCCA[_T1],
|
||||
__ent2: _TCCA[_T2],
|
||||
entity_3: _TScalar_3,
|
||||
) -> Select[Tuple[_TScalar_0, _T1, _T2, _TScalar_3]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
__ent1: _TCCA[_T1],
|
||||
entity_2: _TScalar_2,
|
||||
__ent3: _TCCA[_T3],
|
||||
) -> Select[Tuple[_TScalar_0, _T1, _TScalar_2, _T3]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
__ent1: _TCCA[_T1],
|
||||
entity_2: _TScalar_2,
|
||||
entity_3: _TScalar_3,
|
||||
) -> Select[Tuple[_TScalar_0, _T1, _TScalar_2, _TScalar_3]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
entity_1: _TScalar_1,
|
||||
__ent2: _TCCA[_T2],
|
||||
__ent3: _TCCA[_T3],
|
||||
) -> Select[Tuple[_TScalar_0, _TScalar_1, _T2, _T3]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
entity_1: _TScalar_1,
|
||||
__ent2: _TCCA[_T2],
|
||||
entity_3: _TScalar_3,
|
||||
) -> Select[Tuple[_TScalar_0, _TScalar_1, _T2, _TScalar_3]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
entity_1: _TScalar_1,
|
||||
entity_2: _TScalar_2,
|
||||
__ent3: _TCCA[_T3],
|
||||
) -> Select[Tuple[_TScalar_0, _TScalar_1, _TScalar_2, _T3]]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
entity_0: _TScalar_0,
|
||||
entity_1: _TScalar_1,
|
||||
entity_2: _TScalar_2,
|
||||
entity_3: _TScalar_3,
|
||||
) -> Select[Tuple[_TScalar_0, _TScalar_1, _TScalar_2, _TScalar_3]]:
|
||||
...
|
||||
|
||||
|
||||
# Generated overloads end
|
||||
|
||||
|
||||
def select(*entities: Any) -> Union[Select, SelectOfScalar]: # type: ignore
|
||||
if len(entities) == 1:
|
||||
return SelectOfScalar(*entities)
|
||||
return Select(*entities)
|
||||
return sqlalchemy.within_group(element, *order_by)
|
||||
|
||||
|
||||
def col(column_expression: _T) -> Mapped[_T]:
|
||||
|
||||
@@ -1,309 +0,0 @@
|
||||
from datetime import datetime
|
||||
from typing import (
|
||||
Any,
|
||||
Iterable,
|
||||
Mapping,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
from uuid import UUID
|
||||
|
||||
import sqlalchemy
|
||||
from sqlalchemy import (
|
||||
Column,
|
||||
ColumnElement,
|
||||
Extract,
|
||||
FunctionElement,
|
||||
FunctionFilter,
|
||||
Label,
|
||||
Over,
|
||||
TypeCoerce,
|
||||
WithinGroup,
|
||||
)
|
||||
from sqlalchemy.orm import InstrumentedAttribute, Mapped
|
||||
from sqlalchemy.sql._typing import (
|
||||
_ColumnExpressionArgument,
|
||||
_ColumnExpressionOrLiteralArgument,
|
||||
_ColumnExpressionOrStrLabelArgument,
|
||||
)
|
||||
from sqlalchemy.sql.elements import (
|
||||
BinaryExpression,
|
||||
Case,
|
||||
Cast,
|
||||
CollectionAggregate,
|
||||
ColumnClause,
|
||||
SQLCoreOperations,
|
||||
TryCast,
|
||||
UnaryExpression,
|
||||
)
|
||||
from sqlalchemy.sql.expression import Select as _Select
|
||||
from sqlalchemy.sql.roles import TypedColumnsClauseRole
|
||||
from sqlalchemy.sql.type_api import TypeEngine
|
||||
from typing_extensions import Literal, Self
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
_TypeEngineArgument = Union[Type[TypeEngine[_T]], TypeEngine[_T]]
|
||||
|
||||
# Redefine operatos that would only take a column expresion to also take the (virtual)
|
||||
# types of Pydantic models, e.g. str instead of only Mapped[str].
|
||||
|
||||
|
||||
def all_(expr: Union[_ColumnExpressionArgument[_T], _T]) -> CollectionAggregate[bool]:
|
||||
return sqlalchemy.all_(expr) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def and_(
|
||||
initial_clause: Union[Literal[True], _ColumnExpressionArgument[bool], bool],
|
||||
*clauses: Union[_ColumnExpressionArgument[bool], bool],
|
||||
) -> ColumnElement[bool]:
|
||||
return sqlalchemy.and_(initial_clause, *clauses) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def any_(expr: Union[_ColumnExpressionArgument[_T], _T]) -> CollectionAggregate[bool]:
|
||||
return sqlalchemy.any_(expr) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def asc(
|
||||
column: Union[_ColumnExpressionOrStrLabelArgument[_T], _T],
|
||||
) -> UnaryExpression[_T]:
|
||||
return sqlalchemy.asc(column) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def collate(
|
||||
expression: Union[_ColumnExpressionArgument[str], str], collation: str
|
||||
) -> BinaryExpression[str]:
|
||||
return sqlalchemy.collate(expression, collation) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def between(
|
||||
expr: Union[_ColumnExpressionOrLiteralArgument[_T], _T],
|
||||
lower_bound: Any,
|
||||
upper_bound: Any,
|
||||
symmetric: bool = False,
|
||||
) -> BinaryExpression[bool]:
|
||||
return sqlalchemy.between(expr, lower_bound, upper_bound, symmetric=symmetric) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def not_(clause: Union[_ColumnExpressionArgument[_T], _T]) -> ColumnElement[_T]:
|
||||
return sqlalchemy.not_(clause) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def case(
|
||||
*whens: Union[
|
||||
Tuple[Union[_ColumnExpressionArgument[bool], bool], Any], Mapping[Any, Any]
|
||||
],
|
||||
value: Optional[Any] = None,
|
||||
else_: Optional[Any] = None,
|
||||
) -> Case[Any]:
|
||||
return sqlalchemy.case(*whens, value=value, else_=else_) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def cast(
|
||||
expression: Union[_ColumnExpressionOrLiteralArgument[Any], Any],
|
||||
type_: "_TypeEngineArgument[_T]",
|
||||
) -> Cast[_T]:
|
||||
return sqlalchemy.cast(expression, type_) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def try_cast(
|
||||
expression: Union[_ColumnExpressionOrLiteralArgument[Any], Any],
|
||||
type_: "_TypeEngineArgument[_T]",
|
||||
) -> TryCast[_T]:
|
||||
return sqlalchemy.try_cast(expression, type_) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def desc(
|
||||
column: Union[_ColumnExpressionOrStrLabelArgument[_T], _T],
|
||||
) -> UnaryExpression[_T]:
|
||||
return sqlalchemy.desc(column) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def distinct(expr: Union[_ColumnExpressionArgument[_T], _T]) -> UnaryExpression[_T]:
|
||||
return sqlalchemy.distinct(expr) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def bitwise_not(expr: Union[_ColumnExpressionArgument[_T], _T]) -> UnaryExpression[_T]:
|
||||
return sqlalchemy.bitwise_not(expr) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def extract(field: str, expr: Union[_ColumnExpressionArgument[Any], Any]) -> Extract:
|
||||
return sqlalchemy.extract(field, expr) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def funcfilter(
|
||||
func: FunctionElement[_T], *criterion: Union[_ColumnExpressionArgument[bool], bool]
|
||||
) -> FunctionFilter[_T]:
|
||||
return sqlalchemy.funcfilter(func, *criterion) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def label(
|
||||
name: str,
|
||||
element: Union[_ColumnExpressionArgument[_T], _T],
|
||||
type_: Optional["_TypeEngineArgument[_T]"] = None,
|
||||
) -> Label[_T]:
|
||||
return sqlalchemy.label(name, element, type_=type_) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def nulls_first(
|
||||
column: Union[_ColumnExpressionArgument[_T], _T],
|
||||
) -> UnaryExpression[_T]:
|
||||
return sqlalchemy.nulls_first(column) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def nulls_last(column: Union[_ColumnExpressionArgument[_T], _T]) -> UnaryExpression[_T]:
|
||||
return sqlalchemy.nulls_last(column) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def or_( # type: ignore[empty-body]
|
||||
initial_clause: Union[Literal[False], _ColumnExpressionArgument[bool], bool],
|
||||
*clauses: Union[_ColumnExpressionArgument[bool], bool],
|
||||
) -> ColumnElement[bool]:
|
||||
return sqlalchemy.or_(initial_clause, *clauses) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def over(
|
||||
element: FunctionElement[_T],
|
||||
partition_by: Optional[
|
||||
Union[
|
||||
Iterable[Union[_ColumnExpressionArgument[Any], Any]],
|
||||
_ColumnExpressionArgument[Any],
|
||||
Any,
|
||||
]
|
||||
] = None,
|
||||
order_by: Optional[
|
||||
Union[
|
||||
Iterable[Union[_ColumnExpressionArgument[Any], Any]],
|
||||
_ColumnExpressionArgument[Any],
|
||||
Any,
|
||||
]
|
||||
] = None,
|
||||
range_: Optional[Tuple[Optional[int], Optional[int]]] = None,
|
||||
rows: Optional[Tuple[Optional[int], Optional[int]]] = None,
|
||||
) -> Over[_T]:
|
||||
return sqlalchemy.over(
|
||||
element, partition_by=partition_by, order_by=order_by, range_=range_, rows=rows
|
||||
) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def tuple_(
|
||||
*clauses: Union[_ColumnExpressionArgument[Any], Any],
|
||||
types: Optional[Sequence["_TypeEngineArgument[Any]"]] = None,
|
||||
) -> Tuple[Any, ...]:
|
||||
return sqlalchemy.tuple_(*clauses, types=types) # type: ignore[return-value]
|
||||
|
||||
|
||||
def type_coerce(
|
||||
expression: Union[_ColumnExpressionOrLiteralArgument[Any], Any],
|
||||
type_: "_TypeEngineArgument[_T]",
|
||||
) -> TypeCoerce[_T]:
|
||||
return sqlalchemy.type_coerce(expression, type_) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def within_group(
|
||||
element: FunctionElement[_T], *order_by: Union[_ColumnExpressionArgument[Any], Any]
|
||||
) -> WithinGroup[_T]:
|
||||
return sqlalchemy.within_group(element, *order_by) # type: ignore[arg-type]
|
||||
|
||||
|
||||
# Separate this class in SelectBase, Select, and SelectOfScalar so that they can share
|
||||
# where and having without having type overlap incompatibility in session.exec().
|
||||
class SelectBase(_Select[Tuple[_T]]):
|
||||
inherit_cache = True
|
||||
|
||||
def where(self, *whereclause: Union[_ColumnExpressionArgument[bool], bool]) -> Self:
|
||||
"""Return a new `Select` construct with the given expression added to
|
||||
its `WHERE` clause, joined to the existing clause via `AND`, if any.
|
||||
"""
|
||||
return super().where(*whereclause) # type: ignore[arg-type]
|
||||
|
||||
def having(self, *having: Union[_ColumnExpressionArgument[bool], bool]) -> Self:
|
||||
"""Return a new `Select` construct with the given expression added to
|
||||
its `HAVING` clause, joined to the existing clause via `AND`, if any.
|
||||
"""
|
||||
return super().having(*having) # type: ignore[arg-type]
|
||||
|
||||
|
||||
class Select(SelectBase[_T]):
|
||||
inherit_cache = True
|
||||
|
||||
|
||||
# This is not comparable to sqlalchemy.sql.selectable.ScalarSelect, that has a different
|
||||
# purpose. This is the same as a normal SQLAlchemy Select class where there's only one
|
||||
# entity, so the result will be converted to a scalar by default. This way writing
|
||||
# for loops on the results will feel natural.
|
||||
class SelectOfScalar(SelectBase[_T]):
|
||||
inherit_cache = True
|
||||
|
||||
|
||||
_TCCA = Union[
|
||||
TypedColumnsClauseRole[_T],
|
||||
SQLCoreOperations[_T],
|
||||
Type[_T],
|
||||
]
|
||||
|
||||
# Generated TypeVars start
|
||||
|
||||
|
||||
{% for i in range(number_of_types) %}
|
||||
_TScalar_{{ i }} = TypeVar(
|
||||
"_TScalar_{{ i }}",
|
||||
Column, # type: ignore
|
||||
Sequence, # type: ignore
|
||||
Mapping, # type: ignore
|
||||
UUID,
|
||||
datetime,
|
||||
float,
|
||||
int,
|
||||
bool,
|
||||
bytes,
|
||||
str,
|
||||
None,
|
||||
)
|
||||
|
||||
_T{{ i }} = TypeVar("_T{{ i }}")
|
||||
|
||||
{% endfor %}
|
||||
|
||||
# Generated TypeVars end
|
||||
|
||||
@overload
|
||||
def select(__ent0: _TCCA[_T0]) -> SelectOfScalar[_T0]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def select(__ent0: _TScalar_0) -> SelectOfScalar[_TScalar_0]: # type: ignore
|
||||
...
|
||||
|
||||
|
||||
# Generated overloads start
|
||||
|
||||
{% for signature in signatures %}
|
||||
|
||||
@overload
|
||||
def select( # type: ignore
|
||||
{% for arg in signature[0] %}{{ arg.name }}: {{ arg.annotation }}, {% endfor %}
|
||||
) -> Select[Tuple[{%for ret in signature[1] %}{{ ret }} {% if not loop.last %}, {% endif %}{% endfor %}]]:
|
||||
...
|
||||
|
||||
{% endfor %}
|
||||
|
||||
# Generated overloads end
|
||||
|
||||
|
||||
def select(*entities: Any) -> Union[Select, SelectOfScalar]: # type: ignore
|
||||
if len(entities) == 1:
|
||||
return SelectOfScalar(*entities)
|
||||
return Select(*entities)
|
||||
|
||||
|
||||
def col(column_expression: _T) -> Mapped[_T]:
|
||||
if not isinstance(column_expression, (ColumnClause, Column, InstrumentedAttribute)):
|
||||
raise RuntimeError(f"Not a SQLAlchemy column: {column_expression}")
|
||||
return column_expression # type: ignore
|
||||
@@ -1,10 +1,7 @@
|
||||
import uuid
|
||||
from typing import Any, Optional, cast
|
||||
from typing import Any, cast
|
||||
|
||||
from sqlalchemy import CHAR, types
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy import types
|
||||
from sqlalchemy.engine.interfaces import Dialect
|
||||
from sqlalchemy.sql.type_api import TypeEngine
|
||||
|
||||
|
||||
class AutoString(types.TypeDecorator): # type: ignore
|
||||
@@ -17,43 +14,3 @@ class AutoString(types.TypeDecorator): # type: ignore
|
||||
if impl.length is None and dialect.name == "mysql":
|
||||
return dialect.type_descriptor(types.String(self.mysql_default_length))
|
||||
return super().load_dialect_impl(dialect)
|
||||
|
||||
|
||||
# Reference form SQLAlchemy docs: https://docs.sqlalchemy.org/en/14/core/custom_types.html#backend-agnostic-guid-type
|
||||
# with small modifications
|
||||
class GUID(types.TypeDecorator): # type: ignore
|
||||
"""Platform-independent GUID type.
|
||||
|
||||
Uses PostgreSQL's UUID type, otherwise uses
|
||||
CHAR(32), storing as stringified hex values.
|
||||
|
||||
"""
|
||||
|
||||
impl = CHAR
|
||||
cache_ok = True
|
||||
|
||||
def load_dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]:
|
||||
if dialect.name == "postgresql":
|
||||
return dialect.type_descriptor(UUID())
|
||||
else:
|
||||
return dialect.type_descriptor(CHAR(32))
|
||||
|
||||
def process_bind_param(self, value: Any, dialect: Dialect) -> Optional[str]:
|
||||
if value is None:
|
||||
return value
|
||||
elif dialect.name == "postgresql":
|
||||
return str(value)
|
||||
else:
|
||||
if not isinstance(value, uuid.UUID):
|
||||
return uuid.UUID(value).hex
|
||||
else:
|
||||
# hexstring
|
||||
return value.hex
|
||||
|
||||
def process_result_value(self, value: Any, dialect: Dialect) -> Optional[uuid.UUID]:
|
||||
if value is None:
|
||||
return value
|
||||
else:
|
||||
if not isinstance(value, uuid.UUID):
|
||||
value = uuid.UUID(value)
|
||||
return cast(uuid.UUID, value)
|
||||
|
||||
0
tests/test_advanced/test_uuid/__init__.py
Normal file
0
tests/test_advanced/test_uuid/__init__.py
Normal file
71
tests/test_advanced/test_uuid/test_tutorial001.py
Normal file
71
tests/test_advanced/test_uuid/test_tutorial001.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from dirty_equals import IsUUID
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from ...conftest import get_testing_print_function
|
||||
|
||||
|
||||
def test_tutorial(clear_sqlmodel) -> None:
|
||||
from docs_src.advanced.uuid import tutorial001 as mod
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.main()
|
||||
first_uuid = calls[1][0]["id"]
|
||||
assert first_uuid == IsUUID(4)
|
||||
|
||||
second_uuid = calls[7][0]["id"]
|
||||
assert second_uuid == IsUUID(4)
|
||||
|
||||
assert first_uuid != second_uuid
|
||||
|
||||
assert calls == [
|
||||
["The hero before saving in the DB"],
|
||||
[
|
||||
{
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"id": first_uuid,
|
||||
"age": None,
|
||||
}
|
||||
],
|
||||
["The hero ID was already set"],
|
||||
[first_uuid],
|
||||
["After saving in the DB"],
|
||||
[
|
||||
{
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"age": None,
|
||||
"id": first_uuid,
|
||||
}
|
||||
],
|
||||
["Created hero:"],
|
||||
[
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"age": None,
|
||||
"id": second_uuid,
|
||||
}
|
||||
],
|
||||
["Created hero ID:"],
|
||||
[second_uuid],
|
||||
["Selected hero:"],
|
||||
[
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"age": None,
|
||||
"id": second_uuid,
|
||||
}
|
||||
],
|
||||
["Selected hero ID:"],
|
||||
[second_uuid],
|
||||
]
|
||||
72
tests/test_advanced/test_uuid/test_tutorial001_py310.py
Normal file
72
tests/test_advanced/test_uuid/test_tutorial001_py310.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from dirty_equals import IsUUID
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from ...conftest import get_testing_print_function, needs_py310
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_tutorial(clear_sqlmodel) -> None:
|
||||
from docs_src.advanced.uuid import tutorial001_py310 as mod
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.main()
|
||||
first_uuid = calls[1][0]["id"]
|
||||
assert first_uuid == IsUUID(4)
|
||||
|
||||
second_uuid = calls[7][0]["id"]
|
||||
assert second_uuid == IsUUID(4)
|
||||
|
||||
assert first_uuid != second_uuid
|
||||
|
||||
assert calls == [
|
||||
["The hero before saving in the DB"],
|
||||
[
|
||||
{
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"id": first_uuid,
|
||||
"age": None,
|
||||
}
|
||||
],
|
||||
["The hero ID was already set"],
|
||||
[first_uuid],
|
||||
["After saving in the DB"],
|
||||
[
|
||||
{
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"age": None,
|
||||
"id": first_uuid,
|
||||
}
|
||||
],
|
||||
["Created hero:"],
|
||||
[
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"age": None,
|
||||
"id": second_uuid,
|
||||
}
|
||||
],
|
||||
["Created hero ID:"],
|
||||
[second_uuid],
|
||||
["Selected hero:"],
|
||||
[
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"age": None,
|
||||
"id": second_uuid,
|
||||
}
|
||||
],
|
||||
["Selected hero ID:"],
|
||||
[second_uuid],
|
||||
]
|
||||
71
tests/test_advanced/test_uuid/test_tutorial002.py
Normal file
71
tests/test_advanced/test_uuid/test_tutorial002.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from dirty_equals import IsUUID
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from ...conftest import get_testing_print_function
|
||||
|
||||
|
||||
def test_tutorial(clear_sqlmodel) -> None:
|
||||
from docs_src.advanced.uuid import tutorial002 as mod
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.main()
|
||||
first_uuid = calls[1][0]["id"]
|
||||
assert first_uuid == IsUUID(4)
|
||||
|
||||
second_uuid = calls[7][0]["id"]
|
||||
assert second_uuid == IsUUID(4)
|
||||
|
||||
assert first_uuid != second_uuid
|
||||
|
||||
assert calls == [
|
||||
["The hero before saving in the DB"],
|
||||
[
|
||||
{
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"id": first_uuid,
|
||||
"age": None,
|
||||
}
|
||||
],
|
||||
["The hero ID was already set"],
|
||||
[first_uuid],
|
||||
["After saving in the DB"],
|
||||
[
|
||||
{
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"age": None,
|
||||
"id": first_uuid,
|
||||
}
|
||||
],
|
||||
["Created hero:"],
|
||||
[
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"age": None,
|
||||
"id": second_uuid,
|
||||
}
|
||||
],
|
||||
["Created hero ID:"],
|
||||
[second_uuid],
|
||||
["Selected hero:"],
|
||||
[
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"age": None,
|
||||
"id": second_uuid,
|
||||
}
|
||||
],
|
||||
["Selected hero ID:"],
|
||||
[second_uuid],
|
||||
]
|
||||
72
tests/test_advanced/test_uuid/test_tutorial002_py310.py
Normal file
72
tests/test_advanced/test_uuid/test_tutorial002_py310.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from dirty_equals import IsUUID
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from ...conftest import get_testing_print_function, needs_py310
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_tutorial(clear_sqlmodel) -> None:
|
||||
from docs_src.advanced.uuid import tutorial002_py310 as mod
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.main()
|
||||
first_uuid = calls[1][0]["id"]
|
||||
assert first_uuid == IsUUID(4)
|
||||
|
||||
second_uuid = calls[7][0]["id"]
|
||||
assert second_uuid == IsUUID(4)
|
||||
|
||||
assert first_uuid != second_uuid
|
||||
|
||||
assert calls == [
|
||||
["The hero before saving in the DB"],
|
||||
[
|
||||
{
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"id": first_uuid,
|
||||
"age": None,
|
||||
}
|
||||
],
|
||||
["The hero ID was already set"],
|
||||
[first_uuid],
|
||||
["After saving in the DB"],
|
||||
[
|
||||
{
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"age": None,
|
||||
"id": first_uuid,
|
||||
}
|
||||
],
|
||||
["Created hero:"],
|
||||
[
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"age": None,
|
||||
"id": second_uuid,
|
||||
}
|
||||
],
|
||||
["Created hero ID:"],
|
||||
[second_uuid],
|
||||
["Selected hero:"],
|
||||
[
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"age": None,
|
||||
"id": second_uuid,
|
||||
}
|
||||
],
|
||||
["Selected hero ID:"],
|
||||
[second_uuid],
|
||||
]
|
||||
@@ -108,3 +108,14 @@ def test_sa_column_no_index() -> None:
|
||||
index=True,
|
||||
sa_column=Column(Integer, primary_key=True),
|
||||
)
|
||||
|
||||
|
||||
def test_sa_column_no_ondelete() -> None:
|
||||
with pytest.raises(RuntimeError):
|
||||
|
||||
class Item(SQLModel, table=True):
|
||||
id: Optional[int] = Field(
|
||||
default=None,
|
||||
sa_column=Column(Integer, primary_key=True),
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
|
||||
37
tests/test_ondelete_raises.py
Normal file
37
tests/test_ondelete_raises.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from typing import Any, List, Union
|
||||
|
||||
import pytest
|
||||
from sqlmodel import Field, Relationship, SQLModel
|
||||
|
||||
|
||||
def test_ondelete_requires_nullable(clear_sqlmodel: Any) -> None:
|
||||
with pytest.raises(RuntimeError) as exc:
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: Union[int, None] = Field(default=None, primary_key=True)
|
||||
|
||||
heroes: List["Hero"] = Relationship(
|
||||
back_populates="team", passive_deletes="all"
|
||||
)
|
||||
|
||||
class Hero(SQLModel, table=True):
|
||||
id: Union[int, None] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
secret_name: str
|
||||
age: Union[int, None] = Field(default=None, index=True)
|
||||
|
||||
team_id: int = Field(foreign_key="team.id", ondelete="SET NULL")
|
||||
team: Team = Relationship(back_populates="heroes")
|
||||
|
||||
assert 'ondelete="SET NULL" requires nullable=True' in str(exc.value)
|
||||
|
||||
|
||||
def test_ondelete_requires_foreign_key(clear_sqlmodel: Any) -> None:
|
||||
with pytest.raises(RuntimeError) as exc:
|
||||
|
||||
class Team(SQLModel, table=True):
|
||||
id: Union[int, None] = Field(default=None, primary_key=True)
|
||||
|
||||
age: int = Field(ondelete="CASCADE")
|
||||
|
||||
assert "ondelete can only be used with foreign_key" in str(exc.value)
|
||||
19
tests/test_select_gen.py
Normal file
19
tests/test_select_gen.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from .conftest import needs_py39
|
||||
|
||||
root_path = Path(__file__).parent.parent
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_select_gen() -> None:
|
||||
result = subprocess.run(
|
||||
[sys.executable, "scripts/generate_select.py"],
|
||||
env={"CHECK_JINJA": "1"},
|
||||
check=True,
|
||||
cwd=root_path,
|
||||
capture_output=True,
|
||||
)
|
||||
print(result.stdout)
|
||||
@@ -0,0 +1,72 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from ....conftest import get_testing_print_function
|
||||
|
||||
|
||||
def test_tutorial(clear_sqlmodel):
|
||||
from docs_src.tutorial.relationship_attributes.cascade_delete_relationships import (
|
||||
tutorial001 as mod,
|
||||
)
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.main()
|
||||
assert calls == [
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"team_id": 1,
|
||||
"id": 1,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Rusty-Man",
|
||||
"secret_name": "Tommy Sharp",
|
||||
"team_id": 2,
|
||||
"id": 2,
|
||||
"age": 48,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": None,
|
||||
"id": 3,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Updated hero:",
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": 2,
|
||||
"id": 3,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Team Wakaland:",
|
||||
{"name": "Wakaland", "id": 3, "headquarters": "Wakaland Capital City"},
|
||||
],
|
||||
[
|
||||
"Deleted team:",
|
||||
{"name": "Wakaland", "id": 3, "headquarters": "Wakaland Capital City"},
|
||||
],
|
||||
["Black Lion not found:", None],
|
||||
["Princess Sure-E not found:", None],
|
||||
]
|
||||
@@ -0,0 +1,73 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from ....conftest import get_testing_print_function, needs_py310
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_tutorial(clear_sqlmodel):
|
||||
from docs_src.tutorial.relationship_attributes.cascade_delete_relationships import (
|
||||
tutorial001_py310 as mod,
|
||||
)
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.main()
|
||||
assert calls == [
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"team_id": 1,
|
||||
"id": 1,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Rusty-Man",
|
||||
"secret_name": "Tommy Sharp",
|
||||
"team_id": 2,
|
||||
"id": 2,
|
||||
"age": 48,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": None,
|
||||
"id": 3,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Updated hero:",
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": 2,
|
||||
"id": 3,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Team Wakaland:",
|
||||
{"name": "Wakaland", "id": 3, "headquarters": "Wakaland Capital City"},
|
||||
],
|
||||
[
|
||||
"Deleted team:",
|
||||
{"name": "Wakaland", "id": 3, "headquarters": "Wakaland Capital City"},
|
||||
],
|
||||
["Black Lion not found:", None],
|
||||
["Princess Sure-E not found:", None],
|
||||
]
|
||||
@@ -0,0 +1,73 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from ....conftest import get_testing_print_function, needs_py39
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_tutorial(clear_sqlmodel):
|
||||
from docs_src.tutorial.relationship_attributes.cascade_delete_relationships import (
|
||||
tutorial001_py39 as mod,
|
||||
)
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.main()
|
||||
assert calls == [
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"team_id": 1,
|
||||
"id": 1,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Rusty-Man",
|
||||
"secret_name": "Tommy Sharp",
|
||||
"team_id": 2,
|
||||
"id": 2,
|
||||
"age": 48,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": None,
|
||||
"id": 3,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Updated hero:",
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": 2,
|
||||
"id": 3,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Team Wakaland:",
|
||||
{"name": "Wakaland", "id": 3, "headquarters": "Wakaland Capital City"},
|
||||
],
|
||||
[
|
||||
"Deleted team:",
|
||||
{"name": "Wakaland", "id": 3, "headquarters": "Wakaland Capital City"},
|
||||
],
|
||||
["Black Lion not found:", None],
|
||||
["Princess Sure-E not found:", None],
|
||||
]
|
||||
@@ -0,0 +1,90 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from ....conftest import get_testing_print_function
|
||||
|
||||
|
||||
def test_tutorial(clear_sqlmodel):
|
||||
from docs_src.tutorial.relationship_attributes.cascade_delete_relationships import (
|
||||
tutorial002 as mod,
|
||||
)
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.main()
|
||||
assert calls == [
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 1,
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"team_id": 1,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": 48,
|
||||
"id": 2,
|
||||
"name": "Rusty-Man",
|
||||
"secret_name": "Tommy Sharp",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Updated hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Team Wakaland:",
|
||||
{"headquarters": "Wakaland Capital City", "id": 3, "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Deleted team:",
|
||||
{"headquarters": "Wakaland Capital City", "id": 3, "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Black Lion has no team:",
|
||||
{
|
||||
"age": 35,
|
||||
"id": 4,
|
||||
"name": "Black Lion",
|
||||
"secret_name": "Trevor Challa",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Princess Sure-E has no team:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 5,
|
||||
"name": "Princess Sure-E",
|
||||
"secret_name": "Sure-E",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
]
|
||||
@@ -0,0 +1,91 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from ....conftest import get_testing_print_function, needs_py310
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_tutorial(clear_sqlmodel):
|
||||
from docs_src.tutorial.relationship_attributes.cascade_delete_relationships import (
|
||||
tutorial002_py310 as mod,
|
||||
)
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.main()
|
||||
assert calls == [
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 1,
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"team_id": 1,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": 48,
|
||||
"id": 2,
|
||||
"name": "Rusty-Man",
|
||||
"secret_name": "Tommy Sharp",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Updated hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Team Wakaland:",
|
||||
{"headquarters": "Wakaland Capital City", "id": 3, "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Deleted team:",
|
||||
{"headquarters": "Wakaland Capital City", "id": 3, "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Black Lion has no team:",
|
||||
{
|
||||
"age": 35,
|
||||
"id": 4,
|
||||
"name": "Black Lion",
|
||||
"secret_name": "Trevor Challa",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Princess Sure-E has no team:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 5,
|
||||
"name": "Princess Sure-E",
|
||||
"secret_name": "Sure-E",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
]
|
||||
@@ -0,0 +1,91 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from ....conftest import get_testing_print_function, needs_py39
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_tutorial(clear_sqlmodel):
|
||||
from docs_src.tutorial.relationship_attributes.cascade_delete_relationships import (
|
||||
tutorial002_py39 as mod,
|
||||
)
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.main()
|
||||
assert calls == [
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 1,
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"team_id": 1,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": 48,
|
||||
"id": 2,
|
||||
"name": "Rusty-Man",
|
||||
"secret_name": "Tommy Sharp",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Updated hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Team Wakaland:",
|
||||
{"headquarters": "Wakaland Capital City", "id": 3, "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Deleted team:",
|
||||
{"headquarters": "Wakaland Capital City", "id": 3, "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Black Lion has no team:",
|
||||
{
|
||||
"age": 35,
|
||||
"id": 4,
|
||||
"name": "Black Lion",
|
||||
"secret_name": "Trevor Challa",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Princess Sure-E has no team:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 5,
|
||||
"name": "Princess Sure-E",
|
||||
"secret_name": "Sure-E",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
]
|
||||
@@ -0,0 +1,90 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from tests.conftest import get_testing_print_function
|
||||
|
||||
|
||||
def test_tutorial(clear_sqlmodel):
|
||||
from docs_src.tutorial.relationship_attributes.cascade_delete_relationships import (
|
||||
tutorial003 as mod,
|
||||
)
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.main()
|
||||
assert calls == [
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 1,
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"team_id": 1,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": 48,
|
||||
"id": 2,
|
||||
"name": "Rusty-Man",
|
||||
"secret_name": "Tommy Sharp",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Updated hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Team Wakaland:",
|
||||
{"id": 3, "headquarters": "Wakaland Capital City", "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Deleted team:",
|
||||
{"id": 3, "headquarters": "Wakaland Capital City", "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Black Lion has no team:",
|
||||
{
|
||||
"age": 35,
|
||||
"id": 4,
|
||||
"name": "Black Lion",
|
||||
"secret_name": "Trevor Challa",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Princess Sure-E has no team:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 5,
|
||||
"name": "Princess Sure-E",
|
||||
"secret_name": "Sure-E",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
]
|
||||
@@ -0,0 +1,91 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from ....conftest import get_testing_print_function, needs_py310
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_tutorial(clear_sqlmodel):
|
||||
from docs_src.tutorial.relationship_attributes.cascade_delete_relationships import (
|
||||
tutorial003_py310 as mod,
|
||||
)
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.main()
|
||||
assert calls == [
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 1,
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"team_id": 1,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": 48,
|
||||
"id": 2,
|
||||
"name": "Rusty-Man",
|
||||
"secret_name": "Tommy Sharp",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Updated hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Team Wakaland:",
|
||||
{"id": 3, "headquarters": "Wakaland Capital City", "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Deleted team:",
|
||||
{"id": 3, "headquarters": "Wakaland Capital City", "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Black Lion has no team:",
|
||||
{
|
||||
"age": 35,
|
||||
"id": 4,
|
||||
"name": "Black Lion",
|
||||
"secret_name": "Trevor Challa",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Princess Sure-E has no team:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 5,
|
||||
"name": "Princess Sure-E",
|
||||
"secret_name": "Sure-E",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
]
|
||||
@@ -0,0 +1,91 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from ....conftest import get_testing_print_function, needs_py39
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_tutorial(clear_sqlmodel):
|
||||
from docs_src.tutorial.relationship_attributes.cascade_delete_relationships import (
|
||||
tutorial003_py39 as mod,
|
||||
)
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.main()
|
||||
assert calls == [
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 1,
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"team_id": 1,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": 48,
|
||||
"id": 2,
|
||||
"name": "Rusty-Man",
|
||||
"secret_name": "Tommy Sharp",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Updated hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Team Wakaland:",
|
||||
{"id": 3, "headquarters": "Wakaland Capital City", "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Deleted team:",
|
||||
{"id": 3, "headquarters": "Wakaland Capital City", "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Black Lion has no team:",
|
||||
{
|
||||
"age": 35,
|
||||
"id": 4,
|
||||
"name": "Black Lion",
|
||||
"secret_name": "Trevor Challa",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Princess Sure-E has no team:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 5,
|
||||
"name": "Princess Sure-E",
|
||||
"secret_name": "Sure-E",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
]
|
||||
@@ -0,0 +1,106 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlmodel import Session, create_engine, select
|
||||
|
||||
from tests.conftest import get_testing_print_function
|
||||
|
||||
|
||||
def test_tutorial(clear_sqlmodel):
|
||||
from docs_src.tutorial.relationship_attributes.cascade_delete_relationships import (
|
||||
tutorial004 as mod,
|
||||
)
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.create_db_and_tables()
|
||||
mod.create_heroes()
|
||||
mod.select_deleted_heroes()
|
||||
with Session(mod.engine) as session:
|
||||
team = session.exec(
|
||||
select(mod.Team).where(mod.Team.name == "Wakaland")
|
||||
).one()
|
||||
team.heroes.clear()
|
||||
session.add(team)
|
||||
session.commit()
|
||||
mod.delete_team()
|
||||
assert calls == [
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 1,
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"team_id": 1,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": 48,
|
||||
"id": 2,
|
||||
"name": "Rusty-Man",
|
||||
"secret_name": "Tommy Sharp",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Updated hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Team Wakaland:",
|
||||
{"headquarters": "Wakaland Capital City", "id": 3, "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Black Lion has no team:",
|
||||
{
|
||||
"age": 35,
|
||||
"id": 4,
|
||||
"name": "Black Lion",
|
||||
"secret_name": "Trevor Challa",
|
||||
"team_id": 3,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Princess Sure-E has no team:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 5,
|
||||
"name": "Princess Sure-E",
|
||||
"secret_name": "Sure-E",
|
||||
"team_id": 3,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Deleted team:",
|
||||
{"headquarters": "Wakaland Capital City", "id": 3, "name": "Wakaland"},
|
||||
],
|
||||
]
|
||||
|
||||
with pytest.raises(IntegrityError) as exc:
|
||||
mod.main()
|
||||
assert "FOREIGN KEY constraint failed" in str(exc.value)
|
||||
@@ -0,0 +1,107 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlmodel import Session, create_engine, select
|
||||
|
||||
from tests.conftest import get_testing_print_function, needs_py310
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_tutorial(clear_sqlmodel):
|
||||
from docs_src.tutorial.relationship_attributes.cascade_delete_relationships import (
|
||||
tutorial004_py310 as mod,
|
||||
)
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.create_db_and_tables()
|
||||
mod.create_heroes()
|
||||
mod.select_deleted_heroes()
|
||||
with Session(mod.engine) as session:
|
||||
team = session.exec(
|
||||
select(mod.Team).where(mod.Team.name == "Wakaland")
|
||||
).one()
|
||||
team.heroes.clear()
|
||||
session.add(team)
|
||||
session.commit()
|
||||
mod.delete_team()
|
||||
assert calls == [
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 1,
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"team_id": 1,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": 48,
|
||||
"id": 2,
|
||||
"name": "Rusty-Man",
|
||||
"secret_name": "Tommy Sharp",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Updated hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Team Wakaland:",
|
||||
{"headquarters": "Wakaland Capital City", "id": 3, "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Black Lion has no team:",
|
||||
{
|
||||
"age": 35,
|
||||
"id": 4,
|
||||
"name": "Black Lion",
|
||||
"secret_name": "Trevor Challa",
|
||||
"team_id": 3,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Princess Sure-E has no team:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 5,
|
||||
"name": "Princess Sure-E",
|
||||
"secret_name": "Sure-E",
|
||||
"team_id": 3,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Deleted team:",
|
||||
{"headquarters": "Wakaland Capital City", "id": 3, "name": "Wakaland"},
|
||||
],
|
||||
]
|
||||
|
||||
with pytest.raises(IntegrityError) as exc:
|
||||
mod.main()
|
||||
assert "FOREIGN KEY constraint failed" in str(exc.value)
|
||||
@@ -0,0 +1,107 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlmodel import Session, create_engine, select
|
||||
|
||||
from tests.conftest import get_testing_print_function, needs_py39
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_tutorial(clear_sqlmodel):
|
||||
from docs_src.tutorial.relationship_attributes.cascade_delete_relationships import (
|
||||
tutorial004_py39 as mod,
|
||||
)
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.create_db_and_tables()
|
||||
mod.create_heroes()
|
||||
mod.select_deleted_heroes()
|
||||
with Session(mod.engine) as session:
|
||||
team = session.exec(
|
||||
select(mod.Team).where(mod.Team.name == "Wakaland")
|
||||
).one()
|
||||
team.heroes.clear()
|
||||
session.add(team)
|
||||
session.commit()
|
||||
mod.delete_team()
|
||||
assert calls == [
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 1,
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"team_id": 1,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": 48,
|
||||
"id": 2,
|
||||
"name": "Rusty-Man",
|
||||
"secret_name": "Tommy Sharp",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Updated hero:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 3,
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Team Wakaland:",
|
||||
{"headquarters": "Wakaland Capital City", "id": 3, "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Black Lion has no team:",
|
||||
{
|
||||
"age": 35,
|
||||
"id": 4,
|
||||
"name": "Black Lion",
|
||||
"secret_name": "Trevor Challa",
|
||||
"team_id": 3,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Princess Sure-E has no team:",
|
||||
{
|
||||
"age": None,
|
||||
"id": 5,
|
||||
"name": "Princess Sure-E",
|
||||
"secret_name": "Sure-E",
|
||||
"team_id": 3,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Deleted team:",
|
||||
{"headquarters": "Wakaland Capital City", "id": 3, "name": "Wakaland"},
|
||||
],
|
||||
]
|
||||
|
||||
with pytest.raises(IntegrityError) as exc:
|
||||
mod.main()
|
||||
assert "FOREIGN KEY constraint failed" in str(exc.value)
|
||||
@@ -0,0 +1,94 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from tests.conftest import get_testing_print_function
|
||||
|
||||
|
||||
def test_tutorial(clear_sqlmodel):
|
||||
from docs_src.tutorial.relationship_attributes.cascade_delete_relationships import (
|
||||
tutorial005 as mod,
|
||||
)
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.main()
|
||||
assert calls == [
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"team_id": 1,
|
||||
"id": 1,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Rusty-Man",
|
||||
"secret_name": "Tommy Sharp",
|
||||
"team_id": 2,
|
||||
"id": 2,
|
||||
"age": 48,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": None,
|
||||
"id": 3,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Updated hero:",
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": 2,
|
||||
"id": 3,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Team Wakaland:",
|
||||
{"id": 3, "headquarters": "Wakaland Capital City", "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Team with removed heroes:",
|
||||
{"id": 3, "headquarters": "Wakaland Capital City", "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Deleted team:",
|
||||
{"id": 3, "headquarters": "Wakaland Capital City", "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Black Lion has no team:",
|
||||
{
|
||||
"name": "Black Lion",
|
||||
"secret_name": "Trevor Challa",
|
||||
"team_id": None,
|
||||
"id": 4,
|
||||
"age": 35,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Princess Sure-E has no team:",
|
||||
{
|
||||
"name": "Princess Sure-E",
|
||||
"secret_name": "Sure-E",
|
||||
"team_id": None,
|
||||
"id": 5,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
]
|
||||
@@ -0,0 +1,95 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from tests.conftest import get_testing_print_function, needs_py310
|
||||
|
||||
|
||||
@needs_py310
|
||||
def test_tutorial(clear_sqlmodel):
|
||||
from docs_src.tutorial.relationship_attributes.cascade_delete_relationships import (
|
||||
tutorial005_py310 as mod,
|
||||
)
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.main()
|
||||
assert calls == [
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"team_id": 1,
|
||||
"id": 1,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Rusty-Man",
|
||||
"secret_name": "Tommy Sharp",
|
||||
"team_id": 2,
|
||||
"id": 2,
|
||||
"age": 48,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": None,
|
||||
"id": 3,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Updated hero:",
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": 2,
|
||||
"id": 3,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Team Wakaland:",
|
||||
{"id": 3, "headquarters": "Wakaland Capital City", "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Team with removed heroes:",
|
||||
{"id": 3, "headquarters": "Wakaland Capital City", "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Deleted team:",
|
||||
{"id": 3, "headquarters": "Wakaland Capital City", "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Black Lion has no team:",
|
||||
{
|
||||
"name": "Black Lion",
|
||||
"secret_name": "Trevor Challa",
|
||||
"team_id": None,
|
||||
"id": 4,
|
||||
"age": 35,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Princess Sure-E has no team:",
|
||||
{
|
||||
"name": "Princess Sure-E",
|
||||
"secret_name": "Sure-E",
|
||||
"team_id": None,
|
||||
"id": 5,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
]
|
||||
@@ -0,0 +1,95 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from tests.conftest import get_testing_print_function, needs_py39
|
||||
|
||||
|
||||
@needs_py39
|
||||
def test_tutorial(clear_sqlmodel):
|
||||
from docs_src.tutorial.relationship_attributes.cascade_delete_relationships import (
|
||||
tutorial005_py39 as mod,
|
||||
)
|
||||
|
||||
mod.sqlite_url = "sqlite://"
|
||||
mod.engine = create_engine(mod.sqlite_url)
|
||||
calls = []
|
||||
|
||||
new_print = get_testing_print_function(calls)
|
||||
|
||||
with patch("builtins.print", new=new_print):
|
||||
mod.main()
|
||||
assert calls == [
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Deadpond",
|
||||
"secret_name": "Dive Wilson",
|
||||
"team_id": 1,
|
||||
"id": 1,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Rusty-Man",
|
||||
"secret_name": "Tommy Sharp",
|
||||
"team_id": 2,
|
||||
"id": 2,
|
||||
"age": 48,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Created hero:",
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": None,
|
||||
"id": 3,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Updated hero:",
|
||||
{
|
||||
"name": "Spider-Boy",
|
||||
"secret_name": "Pedro Parqueador",
|
||||
"team_id": 2,
|
||||
"id": 3,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Team Wakaland:",
|
||||
{"id": 3, "headquarters": "Wakaland Capital City", "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Team with removed heroes:",
|
||||
{"id": 3, "headquarters": "Wakaland Capital City", "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Deleted team:",
|
||||
{"id": 3, "headquarters": "Wakaland Capital City", "name": "Wakaland"},
|
||||
],
|
||||
[
|
||||
"Black Lion has no team:",
|
||||
{
|
||||
"name": "Black Lion",
|
||||
"secret_name": "Trevor Challa",
|
||||
"team_id": None,
|
||||
"id": 4,
|
||||
"age": 35,
|
||||
},
|
||||
],
|
||||
[
|
||||
"Princess Sure-E has no team:",
|
||||
{
|
||||
"name": "Princess Sure-E",
|
||||
"secret_name": "Sure-E",
|
||||
"team_id": None,
|
||||
"id": 5,
|
||||
"age": None,
|
||||
},
|
||||
],
|
||||
]
|
||||
Reference in New Issue
Block a user