Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
cd1c1b3435
⬆ Update pytest requirement from <8.0.0,>=7.0.1 to >=7.0.1,<9.0.0
Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.0.1...8.2.2)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-17 22:26:03 +00:00
110 changed files with 1255 additions and 7105 deletions

View File

@ -4,10 +4,10 @@ contact_links:
about: Please report security vulnerabilities to security@tiangolo.com
- name: Question or Problem
about: Ask a question or ask about a problem in GitHub Discussions.
url: https://github.com/fastapi/sqlmodel/discussions/categories/questions
url: https://github.com/tiangolo/sqlmodel/discussions/categories/questions
- name: Feature Request
about: To suggest an idea or ask about a feature, please start with a question saying what you would like to achieve. There might be a way to do it already.
url: https://github.com/fastapi/sqlmodel/discussions/categories/questions
url: https://github.com/tiangolo/sqlmodel/discussions/categories/questions
- name: Show and tell
about: Show what you built with SQLModel or to be used with SQLModel.
url: https://github.com/fastapi/sqlmodel/discussions/categories/show-and-tell
url: https://github.com/tiangolo/sqlmodel/discussions/categories/show-and-tell

View File

@ -6,7 +6,7 @@ body:
value: |
Thanks for your interest in SQLModel! 🚀
If you are not @tiangolo or he didn't ask you directly to create an issue here, please start the conversation in a [Question in GitHub Discussions](https://github.com/fastapi/sqlmodel/discussions/categories/questions) instead.
If you are not @tiangolo or he didn't ask you directly to create an issue here, please start the conversation in a [Question in GitHub Discussions](https://github.com/tiangolo/sqlmodel/discussions/categories/questions) instead.
- type: checkboxes
id: privileged
attributes:

View File

@ -0,0 +1,7 @@
FROM python:3.7
RUN pip install httpx "pydantic==1.5.1" pygithub
COPY ./app /app
CMD ["python", "/app/main.py"]

View File

@ -0,0 +1,13 @@
name: Comment Docs Preview in PR
description: Comment with the docs URL preview in the PR
author: Sebastián Ramírez <tiangolo@gmail.com>
inputs:
token:
description: Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}
required: true
deploy_url:
description: The deployment URL to comment in the PR
required: true
runs:
using: docker
image: Dockerfile

View File

@ -0,0 +1,68 @@
import logging
import sys
from pathlib import Path
from typing import Optional
import httpx
from github import Github
from github.PullRequest import PullRequest
from pydantic import BaseModel, BaseSettings, SecretStr, ValidationError
github_api = "https://api.github.com"
class Settings(BaseSettings):
github_repository: str
github_event_path: Path
github_event_name: Optional[str] = None
input_token: SecretStr
input_deploy_url: str
class PartialGithubEventHeadCommit(BaseModel):
id: str
class PartialGithubEventWorkflowRun(BaseModel):
head_commit: PartialGithubEventHeadCommit
class PartialGithubEvent(BaseModel):
workflow_run: PartialGithubEventWorkflowRun
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
settings = Settings()
logging.info(f"Using config: {settings.json()}")
g = Github(settings.input_token.get_secret_value())
repo = g.get_repo(settings.github_repository)
try:
event = PartialGithubEvent.parse_file(settings.github_event_path)
except ValidationError as e:
logging.error(f"Error parsing event file: {e.errors()}")
sys.exit(0)
use_pr: Optional[PullRequest] = None
for pr in repo.get_pulls():
if pr.head.sha == event.workflow_run.head_commit.id:
use_pr = pr
break
if not use_pr:
logging.error(f"No PR found for hash: {event.workflow_run.head_commit.id}")
sys.exit(0)
github_headers = {
"Authorization": f"token {settings.input_token.get_secret_value()}"
}
url = f"{github_api}/repos/{settings.github_repository}/issues/{use_pr.number}/comments"
logging.info(f"Using comments URL: {url}")
response = httpx.post(
url,
headers=github_headers,
json={
"body": f"📝 Docs preview for commit {use_pr.head.sha} at: {settings.input_deploy_url}"
},
)
if not (200 <= response.status_code <= 300):
logging.error(f"Error posting comment: {response.text}")
sys.exit(1)
logging.info("Finished")

24
.github/labeler.yml vendored
View File

@ -1,24 +0,0 @@
docs:
- all:
- changed-files:
- any-glob-to-any-file:
- docs/**
- docs_src/**
- all-globs-to-all-files:
- '!sqlmodel/**'
- '!pyproject.toml'
internal:
- all:
- changed-files:
- any-glob-to-any-file:
- .github/**
- scripts/**
- .gitignore
- .pre-commit-config.yaml
- pdm_build.py
- requirements*.txt
- all-globs-to-all-files:
- '!docs/**'
- '!sqlmodel/**'
- '!pyproject.toml'

View File

@ -1,18 +0,0 @@
name: Add to Project
on:
pull_request_target:
issues:
types:
- opened
- reopened
jobs:
add-to-project:
name: Add to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@v1.0.2
with:
project-url: https://github.com/orgs/fastapi/projects/2
github-token: ${{ secrets.PROJECTS_TOKEN }}

View File

@ -7,11 +7,6 @@ on:
types:
- opened
- synchronize
env:
UV_SYSTEM_PYTHON: 1
jobs:
changes:
runs-on: ubuntu-latest
@ -33,12 +28,9 @@ jobs:
- docs/**
- docs_src/**
- requirements-docs.txt
- requirements-docs-insiders.txt
- pyproject.toml
- mkdocs.yml
- mkdocs.insiders.yml
- mkdocs.maybe-insiders.yml
- mkdocs.no-insiders.yml
- .github/workflows/build-docs.yml
- .github/workflows/deploy-docs.yml
@ -57,22 +49,21 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Setup uv
uses: astral-sh/setup-uv@v3
- uses: actions/cache@v3
id: cache
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
path: ${{ env.pythonLocation }}
key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt') }}-v01
- name: Install docs extras
run: uv pip install -r requirements-docs.txt
if: steps.cache.outputs.cache-hit != 'true'
run: pip install -r requirements-docs.txt
- name: Install Material for MkDocs Insiders
if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' )
run: uv pip install -r requirements-docs-insiders.txt
env:
TOKEN: ${{ secrets.SQLMODEL_MKDOCS_MATERIAL_INSIDERS }}
- uses: actions/cache@v4
if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' ) && steps.cache.outputs.cache-hit != 'true'
run: |
pip install git+https://${{ secrets.SQLMODEL_MKDOCS_MATERIAL_INSIDERS }}@github.com/squidfunk/mkdocs-material-insiders.git
pip install git+https://${{ secrets.SQLMODEL_MKDOCS_MATERIAL_INSIDERS }}@github.com/pawamoy-insiders/griffe-typing-deprecated.git
pip install git+https://${{ secrets.SQLMODEL_MKDOCS_MATERIAL_INSIDERS }}@github.com/pawamoy-insiders/mkdocstrings-python.git
- uses: actions/cache@v3
with:
key: mkdocs-cards-${{ github.ref }}
path: .cache
@ -84,7 +75,6 @@ jobs:
with:
name: docs-site
path: ./site/**
include-hidden-files: true
# https://github.com/marketplace/actions/alls-green#why
docs-all-green: # This job does nothing and is only used for the branch protection

View File

@ -6,15 +6,6 @@ on:
types:
- completed
permissions:
deployments: write
issues: write
pull-requests: write
statuses: write
env:
UV_SYSTEM_PYTHON: 1
jobs:
deploy-docs:
runs-on: ubuntu-latest
@ -24,27 +15,6 @@ jobs:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Setup uv
uses: astral-sh/setup-uv@v3
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
- name: Install GitHub Actions dependencies
run: uv pip install -r requirements-github-actions.txt
- name: Deploy Docs Status Pending
run: python ./scripts/deploy_docs_status.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
RUN_ID: ${{ github.run_id }}
- name: Clean site
run: |
rm -rf ./site
@ -60,19 +30,17 @@ jobs:
# hashFiles returns an empty string if there are no files
if: hashFiles('./site/*')
id: deploy
env:
PROJECT_NAME: sqlmodel
BRANCH: ${{ ( github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'main' && 'main' ) || ( github.event.workflow_run.head_sha ) }}
uses: cloudflare/wrangler-action@v3
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy ./site --project-name=${{ env.PROJECT_NAME }} --branch=${{ env.BRANCH }}
projectName: sqlmodel
directory: './site'
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ ( github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'main' && 'main' ) || ( github.event.workflow_run.head_sha ) }}
- name: Comment Deploy
run: python ./scripts/deploy_docs_status.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEPLOY_URL: ${{ steps.deploy.outputs.deployment-url }}
COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
RUN_ID: ${{ github.run_id }}
IS_DONE: "true"
if: steps.deploy.outputs.url != ''
uses: ./.github/actions/comment-docs-preview-in-pr
with:
token: ${{ secrets.GITHUB_TOKEN }}
deploy_url: "${{ steps.deploy.outputs.url }}"

View File

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

View File

@ -1,33 +0,0 @@
name: Labels
on:
pull_request_target:
types:
- opened
- synchronize
- reopened
# For label-checker
- labeled
- unlabeled
jobs:
labeler:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5
if: ${{ github.event.action != 'labeled' && github.event.action != 'unlabeled' }}
- run: echo "Done adding labels"
# Run this after labeler applied labels
check-labels:
needs:
- labeler
permissions:
pull-requests: read
runs-on: ubuntu-latest
steps:
- uses: docker://agilepathway/pull-request-label-checker:latest
with:
one_of: breaking,security,feature,bug,refactor,upgrade,docs,lang-all,internal
repo_token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -30,7 +30,8 @@ jobs:
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- uses: tiangolo/latest-changes@0.3.1
- uses: docker://tiangolo/latest-changes:0.2.0
# - uses: tiangolo/latest-changes@main
with:
token: ${{ secrets.GITHUB_TOKEN }}
latest_changes_file: docs/release-notes.md

View File

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

View File

@ -51,15 +51,3 @@ jobs:
run: |
cd dist
pip wheel --no-deps sqlmodel*.tar.gz
# https://github.com/marketplace/actions/alls-green#why
test-redistribute-alls-green: # This job does nothing and is only used for the branch protection
if: always()
needs:
- test-redistribute
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}

View File

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

2
.gitignore vendored
View File

@ -1,4 +1,5 @@
*.pyc
env*
.mypy_cache
.vscode
.idea
@ -11,4 +12,3 @@ coverage.xml
site
*.db
.cache
.venv*

View File

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

View File

@ -12,7 +12,7 @@ authors:
family-names: Ramírez
email: tiangolo@gmail.com
identifiers:
repository-code: 'https://github.com/fastapi/sqlmodel'
repository-code: 'https://github.com/tiangolo/sqlmodel'
url: 'https://sqlmodel.tiangolo.com'
abstract: >-
SQLModel, SQL databases in Python, designed for

View File

@ -1,19 +1,18 @@
<p align="center">
<a href="https://sqlmodel.tiangolo.com"><img src="https://sqlmodel.tiangolo.com/img/logo-margin/logo-margin-vector.svg#only-light" alt="SQLModel"></a>
<a href="https://sqlmodel.tiangolo.com"><img src="https://sqlmodel.tiangolo.com/img/logo-margin/logo-margin-vector.svg" alt="SQLModel"></a>
</p>
<p align="center">
<em>SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness.</em>
</p>
<p align="center">
<a href="https://github.com/fastapi/sqlmodel/actions?query=workflow%3ATest" target="_blank">
<img src="https://github.com/fastapi/sqlmodel/workflows/Test/badge.svg" alt="Test">
<a href="https://github.com/tiangolo/sqlmodel/actions?query=workflow%3ATest" target="_blank">
<img src="https://github.com/tiangolo/sqlmodel/workflows/Test/badge.svg" alt="Test">
</a>
<a href="https://github.com/fastapi/sqlmodel/actions?query=workflow%3APublish" target="_blank">
<img src="https://github.com/fastapi/sqlmodel/workflows/Publish/badge.svg" alt="Publish">
<a href="https://github.com/tiangolo/sqlmodel/actions?query=workflow%3APublish" target="_blank">
<img src="https://github.com/tiangolo/sqlmodel/workflows/Publish/badge.svg" alt="Publish">
</a>
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/sqlmodel" target="_blank">
<img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/sqlmodel.svg" alt="Coverage">
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/tiangolo/sqlmodel" target="_blank">
<img src="https://coverage-badge.samuelcolvin.workers.dev/tiangolo/sqlmodel.svg" alt="Coverage">
<a href="https://pypi.org/project/sqlmodel" target="_blank">
<img src="https://img.shields.io/pypi/v/sqlmodel?color=%2334D058&label=pypi%20package" alt="Package version">
</a>
@ -23,7 +22,7 @@
**Documentation**: <a href="https://sqlmodel.tiangolo.com" target="_blank">https://sqlmodel.tiangolo.com</a>
**Source Code**: <a href="https://github.com/fastapi/sqlmodel" target="_blank">https://github.com/fastapi/sqlmodel</a>
**Source Code**: <a href="https://github.com/tiangolo/sqlmodel" target="_blank">https://github.com/tiangolo/sqlmodel</a>
---
@ -65,8 +64,6 @@ As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. T
## Installation
Make sure you create a <a href="https://sqlmodel.tiangolo.com/virtual-environments/" class="external-link" target="_blank">virtual environment</a>, activate it, and then install SQLModel, for example with:
<div class="termy">
```console
@ -222,4 +219,4 @@ And at the same time, ✨ it is also a **Pydantic** model ✨. You can use inher
## License
This project is licensed under the terms of the [MIT license](https://github.com/fastapi/sqlmodel/blob/main/LICENSE).
This project is licensed under the terms of the [MIT license](https://github.com/tiangolo/sqlmodel/blob/main/LICENSE).

View File

@ -1,4 +0,0 @@
members:
- login: tiangolo
- login: estebanx64
- login: alejsdev

View File

@ -1,3 +0,0 @@
# About
About **SQLModel**, its design, inspiration, and more. 🤓

View File

@ -80,7 +80,45 @@ We don't call `uuid.uuid4()` ourselves in the code (we don't put the parenthesis
This means that the UUID will be generated in the Python code, **before sending the data to the database**.
{* ./docs_src/advanced/uuid/tutorial001_py310.py ln[1:10] hl[1,7] *}
//// 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>.
@ -94,7 +132,49 @@ As `uuid.uuid4` will be called when creating the model instance, even before sen
And that **same ID (a UUID)** will be saved in the database.
{* ./docs_src/advanced/uuid/tutorial001_py310.py ln[23:34] hl[25,27,29,34] *}
//// 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
@ -102,7 +182,49 @@ We can do the same operations we could do with other fields.
For example we can **select a hero by ID**:
{* ./docs_src/advanced/uuid/tutorial001_py310.py ln[37:54] hl[49] *}
//// 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
@ -116,7 +238,49 @@ SQLModel (actually SQLAlchemy) will take care of making it work. ✨
We could also select by ID with `session.get()`:
{* ./docs_src/advanced/uuid/tutorial002_py310.py ln[37:53] hl[49] *}
//// 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. 🚀

View File

@ -4,43 +4,49 @@ First, you might want to see the basic ways to [help SQLModel and get help](help
## Developing
If you already cloned the <a href="https://github.com/fastapi/sqlmodel" class="external-link" target="_blank">sqlmodel repository</a> and you want to deep dive in the code, here are some guidelines to set up your environment.
If you already cloned the repository and you know that you need to deep dive in the code, here are some guidelines to set up your environment.
### Virtual Environment
### Poetry
Follow the instructions to create and activate a [virtual environment](virtual-environments.md){.internal-link target=_blank} for the internal code of `sqlmodel`.
**SQLModel** uses <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a> to build, package, and publish the project.
### Install Requirements Using `pip`
You can learn how to install it in the <a href="https://python-poetry.org/docs/#installation" class="external-link" target="_blank">Poetry docs</a>.
After activating the environment, install the required packages:
After having Poetry available, you can install the development dependencies:
<div class="termy">
```console
$ pip install -r requirements.txt
$ poetry install
---> 100%
```
</div>
It will install all the dependencies and your local SQLModel in your local environment.
It will also create a virtual environment automatically and will install all the dependencies and your local SQLModel in it.
### Using your Local SQLModel
### Poetry Shell
If you create a Python file that imports and uses SQLModel, and run it with the Python from your local environment, it will use your cloned local SQLModel source code.
To use your current environment, and to have access to all the tools in it (for example `pytest` for the tests) enter into a Poetry Shell:
And if you update that local SQLModel source code when you run that Python file again, it will use the fresh version of SQLModel you just edited.
<div class="termy">
That way, you don't have to "install" your local version to be able to test every change.
```console
$ poetry shell
```
/// note | "Technical Details"
</div>
This only happens when you install using this included `requirements.txt` instead of running `pip install sqlmodel` directly.
That will set up the environment variables needed and start a new shell with them.
That is because inside the `requirements.txt` file, the local version of SQLModel is marked to be installed in "editable" mode, with the `-e` option.
#### Using your local SQLModel
///
If you create a Python file that imports and uses SQLModel, and run it with the Python from your local Poetry environment, it will use your local SQLModel source code.
And if you update that local SQLModel source code, when you run that Python file again, it will use the fresh version of SQLModel you just edited.
Poetry takes care of making that work. But of course, it will only work in the current Poetry environment, if you install standard SQLModel in another environment (not from the source in the GitHub repo), that will use the standard SQLModel, not your custom version.
### Format
@ -56,92 +62,9 @@ $ bash scripts/format.sh
It will also auto-sort all your imports.
## Tests
There is a script that you can run locally to test all the code and generate coverage reports in HTML:
<div class="termy">
```console
$ bash scripts/test-cov-html.sh
```
</div>
This command generates a directory `./htmlcov/`, if you open the file `./htmlcov/index.html` in your browser, you can explore interactively the regions of code that are covered by the tests, and notice if there is any region missing.
## Docs
First, make sure you set up your environment as described above, that will install all the requirements.
### Docs Live
During local development, there is a script that builds the site and checks for any changes, live-reloading:
<div class="termy">
```console
$ python ./scripts/docs.py live
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008
<span style="color: green;">[INFO]</span> Start watching changes
<span style="color: green;">[INFO]</span> Start detecting changes
```
</div>
It will serve the documentation on `http://127.0.0.1:8008`.
That way, you can edit the documentation/source files and see the changes live.
/// tip
Alternatively, you can perform the same steps that scripts does manually.
Go into the docs director at `docs/`:
```console
$ cd docs/
```
Then run `mkdocs` in that directory:
```console
$ mkdocs serve --dev-addr 8008
```
///
#### Typer CLI (Optional)
The instructions here show you how to use the script at `./scripts/docs.py` with the `python` program directly.
But you can also use <a href="https://typer.tiangolo.com/typer-cli/" class="external-link" target="_blank">Typer CLI</a>, and you will get autocompletion in your terminal for the commands after installing completion.
If you install Typer CLI, you can install completion with:
<div class="termy">
```console
$ typer --install-completion
zsh completion installed in /home/user/.bashrc.
Completion will take effect once you restart the terminal.
```
</div>
### Docs Structure
The documentation uses <a href="https://www.mkdocs.org/" class="external-link" target="_blank">MkDocs</a>.
And there are extra tools/scripts in place in `./scripts/docs.py`.
/// tip
You don't need to see the code in `./scripts/docs.py`, you just use it in the command line.
///
The documentation uses <a href="https://www.mkdocs.org/" class="external-link" target="_blank">MkDocs</a> with <a href="https://squidfunk.github.io/mkdocs-material/" class="external-link" target="_blank">Material for MkDocs</a>.
All the documentation is in Markdown format in the directory `./docs`.
@ -153,12 +76,49 @@ In fact, those blocks of code are not written inside the Markdown, they are Pyth
And those Python files are included/injected in the documentation when generating the site.
### Docs for Tests
### Docs for tests
Most of the tests actually run against the example source files in the documentation.
This helps to make sure that:
This helps making sure that:
* The documentation is up-to-date.
* The documentation is up to date.
* The documentation examples can be run as is.
* Most of the features are covered by the documentation, ensured by test coverage.
During local development, there is a script that builds the site and checks for any changes, live-reloading:
<div class="termy">
```console
$ bash scripts/docs-live.sh
<span style="color: green;">[INFO]</span> - Building documentation...
<span style="color: green;">[INFO]</span> - Cleaning site directory
<span style="color: green;">[INFO]</span> - Documentation built in 2.74 seconds
<span style="color: green;">[INFO]</span> - Serving on http://127.0.0.1:8008
```
</div>
It will serve the documentation on `http://127.0.0.1:8008`.
That way, you can edit the documentation/source files and see the changes live.
## Tests
There is a script that you can run locally to test all the code and generate coverage reports in HTML:
<div class="termy">
```console
$ bash scripts/test.sh
```
</div>
This command generates a directory `./htmlcov/`, if you open the file `./htmlcov/index.html` in your browser, you can explore interactively the regions of code that are covered by the tests, and notice if there is any region missing.
## Thanks
Thanks for contributing! ☕

View File

@ -29,43 +29,3 @@ a.internal-link::after {
.shadow {
box-shadow: 5px 5px 10px #999;
}
.user-list {
display: flex;
flex-wrap: wrap;
margin-bottom: 2rem;
}
.user-list-center {
justify-content: space-evenly;
}
.user {
margin: 1em;
min-width: 7em;
}
.user .avatar-wrapper {
width: 80px;
height: 80px;
margin: 10px auto;
overflow: hidden;
border-radius: 50%;
position: relative;
}
.user .avatar-wrapper img {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.user .title {
text-align: center;
}
.user .count {
font-size: 80%;
text-align: center;
}

View File

@ -26,8 +26,6 @@
position: relative;
-webkit-box-sizing: border-box;
box-sizing: border-box;
/* Custom line-height */
line-height: 1.2;
}
[data-termynal]:before {

View File

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

View File

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

View File

@ -1,300 +0,0 @@
# Environment Variables
Before we jump into code, let's cover a bit some of the **basics** that we'll need to understand how to work with Python (and programming) in general. Let's check a bit about **environment variables**.
/// tip
If you already know what "environment variables" are and how to use them, feel free to skip this.
///
An environment variable (also known as "**env var**") is a variable that lives **outside** of the Python code, in the **operating system**, and could be read by your Python code (or by other programs as well).
Environment variables could be useful for handling application **settings**, as part of the **installation** of Python, etc.
## Create and Use Env Vars
You can **create** and use environment variables in the **shell (terminal)**, without needing Python:
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
// You could create an env var MY_NAME with
$ export MY_NAME="Wade Wilson"
// Then you could use it with other programs, like
$ echo "Hello $MY_NAME"
Hello Wade Wilson
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
// Create an env var MY_NAME
$ $Env:MY_NAME = "Wade Wilson"
// Use it with other programs, like
$ echo "Hello $Env:MY_NAME"
Hello Wade Wilson
```
</div>
////
## Read env vars in Python
You could also create environment variables **outside** of Python, in the terminal (or with any other method), and then **read them in Python**.
For example you could have a file `main.py` with:
```Python hl_lines="3"
import os
name = os.getenv("MY_NAME", "World")
print(f"Hello {name} from Python")
```
/// tip
The second argument to <a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> is the default value to return.
If not provided, it's `None` by default, here we provide `"World"` as the default value to use.
///
Then you could call that Python program:
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
// Here we don't set the env var yet
$ python main.py
// As we didn't set the env var, we get the default value
Hello World from Python
// But if we create an environment variable first
$ export MY_NAME="Wade Wilson"
// And then call the program again
$ python main.py
// Now it can read the environment variable
Hello Wade Wilson from Python
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
// Here we don't set the env var yet
$ python main.py
// As we didn't set the env var, we get the default value
Hello World from Python
// But if we create an environment variable first
$ $Env:MY_NAME = "Wade Wilson"
// And then call the program again
$ python main.py
// Now it can read the environment variable
Hello Wade Wilson from Python
```
</div>
////
As environment variables can be set outside of the code, but can be read by the code, and don't have to be stored (committed to `git`) with the rest of the files, it's common to use them for configurations or **settings**.
You can also create an environment variable only for a **specific program invocation**, that is only available to that program, and only for its duration.
To do that, create it right before the program itself, on the same line:
<div class="termy">
```console
// Create an env var MY_NAME in line for this program call
$ MY_NAME="Wade Wilson" python main.py
// Now it can read the environment variable
Hello Wade Wilson from Python
// The env var no longer exists afterwards
$ python main.py
Hello World from Python
```
</div>
/// tip
You can read more about it at <a href="https://12factor.net/config" class="external-link" target="_blank">The Twelve-Factor App: Config</a>.
///
## Types and Validation
These environment variables can only handle **text strings**, as they are external to Python and have to be compatible with other programs and the rest of the system (and even with different operating systems, as Linux, Windows, macOS).
That means that **any value** read in Python from an environment variable **will be a `str`**, and any conversion to a different type or any validation has to be done in code.
## `PATH` Environment Variable
There is a **special** environment variable called **`PATH`** that is used by the operating systems (Linux, macOS, Windows) to find programs to run.
The value of the variable `PATH` is a long string that is made of directories separated by a colon `:` on Linux and macOS, and by a semicolon `;` on Windows.
For example, the `PATH` environment variable could look like this:
//// tab | Linux, macOS
```plaintext
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
```
This means that the system should look for programs in the directories:
* `/usr/local/bin`
* `/usr/bin`
* `/bin`
* `/usr/sbin`
* `/sbin`
////
//// tab | Windows
```plaintext
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32
```
This means that the system should look for programs in the directories:
* `C:\Program Files\Python312\Scripts`
* `C:\Program Files\Python312`
* `C:\Windows\System32`
////
When you type a **command** in the terminal, the operating system **looks for** the program in **each of those directories** listed in the `PATH` environment variable.
For example, when you type `python` in the terminal, the operating system looks for a program called `python` in the **first directory** in that list.
If it finds it, then it will **use it**. Otherwise it keeps looking in the **other directories**.
### Installing Python and Updating the `PATH`
When you install Python, you might be asked if you want to update the `PATH` environment variable.
//// tab | Linux, macOS
Let's say you install Python and it ends up in a directory `/opt/custompython/bin`.
If you say yes to update the `PATH` environment variable, then the installer will add `/opt/custompython/bin` to the `PATH` environment variable.
It could look like this:
```plaintext
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin
```
This way, when you type `python` in the terminal, the system will find the Python program in `/opt/custompython/bin` (the last directory) and use that one.
////
//// tab | Windows
Let's say you install Python and it ends up in a directory `C:\opt\custompython\bin`.
If you say yes to update the `PATH` environment variable, then the installer will add `C:\opt\custompython\bin` to the `PATH` environment variable.
```plaintext
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin
```
This way, when you type `python` in the terminal, the system will find the Python program in `C:\opt\custompython\bin` (the last directory) and use that one.
////
This way, when you type `python` in the terminal, the system will find the Python program in `/opt/custompython/bin` (the last directory) and use that one.
So, if you type:
<div class="termy">
```console
$ python
```
</div>
//// tab | Linux, macOS
The system will **find** the `python` program in `/opt/custompython/bin` and run it.
It would be roughly equivalent to typing:
<div class="termy">
```console
$ /opt/custompython/bin/python
```
</div>
////
//// tab | Windows
The system will **find** the `python` program in `C:\opt\custompython\bin\python` and run it.
It would be roughly equivalent to typing:
<div class="termy">
```console
$ C:\opt\custompython\bin\python
```
</div>
////
This information will be useful when learning about [Virtual Environments](virtual-environments.md){.internal-link target=_blank}.
## Conclusion
With this you should have a basic understanding of what **environment variables** are and how to use them in Python.
You can also read more about them in the <a href="https://en.wikipedia.org/wiki/Environment_variable" class="external-link" target="_blank">Wikipedia for Environment Variable</a>.
In many cases it's not very obvious how environment variables would be useful and applicable right away. But they keep showing up in many different scenarios when you are developing, so it's good to know about them.
For example, you will need this information in the next section, about [Virtual Environments](virtual-environments.md).

View File

@ -22,13 +22,13 @@ You can subscribe to the (infrequent) <a href="https://fastapi.tiangolo.com/news
## Star **SQLModel** in GitHub
You can "star" SQLModel in GitHub (clicking the star button at the top right): <a href="https://github.com/fastapi/sqlmodel" class="external-link" target="_blank">https://github.com/fastapi/sqlmodel</a>. ⭐️
You can "star" SQLModel in GitHub (clicking the star button at the top right): <a href="https://github.com/tiangolo/sqlmodel" class="external-link" target="_blank">https://github.com/tiangolo/sqlmodel</a>. ⭐️
By adding a star, other users will be able to find it more easily and see that it has been already useful for others.
## Watch the GitHub repository for releases
You can "watch" SQLModel in GitHub (clicking the "watch" button at the top right): <a href="https://github.com/fastapi/sqlmodel" class="external-link" target="_blank">https://github.com/fastapi/sqlmodel</a>. 👀
You can "watch" SQLModel in GitHub (clicking the "watch" button at the top right): <a href="https://github.com/tiangolo/sqlmodel" class="external-link" target="_blank">https://github.com/tiangolo/sqlmodel</a>. 👀
There you can select "Releases only".
@ -54,7 +54,7 @@ You can:
## Tweet about **SQLModel**
<a href="https://twitter.com/compose/tweet?text=I'm loving SQLModel because... https://github.com/fastapi/sqlmodel cc: @tiangolo" class="external-link" target="_blank">Tweet about **SQLModel**</a> and let me and others know why you like it. 🎉
<a href="https://twitter.com/compose/tweet?text=I'm loving SQLModel because... https://github.com/tiangolo/sqlmodel cc: @tiangolo" class="external-link" target="_blank">Tweet about **SQLModel**</a> and let me and others know why you like it. 🎉
I love to hear about how **SQLModel** is being used, what you have liked in it, in which project/company are you using it, etc.
@ -62,8 +62,8 @@ I love to hear about how **SQLModel** is being used, what you have liked in it,
You can try and help others with their questions in:
* <a href="https://github.com/fastapi/sqlmodel/discussions/categories/questions?discussions_q=category%3AQuestions+is%3Aunanswered" class="external-link" target="_blank">GitHub Discussions</a>
* <a href="https://github.com/fastapi/sqlmodel/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aquestion+-label%3Aanswered+" class="external-link" target="_blank">GitHub Issues</a>
* <a href="https://github.com/tiangolo/sqlmodel/discussions/categories/questions?discussions_q=category%3AQuestions+is%3Aunanswered" class="external-link" target="_blank">GitHub Discussions</a>
* <a href="https://github.com/tiangolo/sqlmodel/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aquestion+-label%3Aanswered+" class="external-link" target="_blank">GitHub Issues</a>
In many cases you might already know the answer for those questions. 🤓
@ -112,7 +112,7 @@ If they reply, there's a high chance you would have solved their problem, congra
## Watch the GitHub repository
You can "watch" SQLModel in GitHub (clicking the "watch" button at the top right): <a href="https://github.com/fastapi/sqlmodel" class="external-link" target="_blank">https://github.com/fastapi/sqlmodel</a>. 👀
You can "watch" SQLModel in GitHub (clicking the "watch" button at the top right): <a href="https://github.com/tiangolo/sqlmodel" class="external-link" target="_blank">https://github.com/tiangolo/sqlmodel</a>. 👀
If you select "Watching" instead of "Releases only" you will receive notifications when someone creates a new issue or question. You can also specify that you only want to be notified about new issues, or discussions, or PRs, etc.
@ -120,7 +120,7 @@ Then you can try and help them solve those questions.
## Ask Questions
You can <a href="https://github.com/fastapi/sqlmodel/discussions/new?category=questions" class="external-link" target="_blank">create a new question</a> in the GitHub repository, for example to:
You can <a href="https://github.com/tiangolo/sqlmodel/discussions/new?category=questions" class="external-link" target="_blank">create a new question</a> in the GitHub repository, for example to:
* Ask a **question** or ask about a **problem**.
* Suggest a new **feature**.
@ -214,7 +214,7 @@ Join the 👥 <a href="https://discord.gg/VQjSZaeJmf" class="external-link" targ
/// tip
For questions, ask them in <a href="https://github.com/fastapi/sqlmodel/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussions</a>, there's a much better chance you will receive help there.
For questions, ask them in <a href="https://github.com/tiangolo/sqlmodel/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussions</a>, there's a much better chance you will receive help there.
Use the chat only for other general conversations.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,105 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="svg8"
version="1.1"
viewBox="0 0 848.54461 237.29972"
height="237.29971mm"
width="848.54462mm"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<text
id="text861"
y="158.23088"
x="204.64526"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:113.462px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu Bold';letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.83655"
xml:space="preserve"
transform="scale(1.0209259,0.979503)"><tspan
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu Bold';fill:#ffffff;fill-opacity:1;stroke-width:2.83655"
y="158.23088"
x="204.64526"
id="tspan859">SQLModel</tspan></text>
<g
id="g1074"
transform="matrix(7.048594,0,0,6.7626049,42.411668,48.341116)">
<g
id="g1470">
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.27225;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
id="rect1457"
width="15.627598"
height="11.127809"
x="3.6129446"
y="3.9663849" />
<g
id="g1309-3"
transform="translate(2.3451874e-4,7.0648327)">
<path
id="path1225-7"
style="fill:#7e56c2;fill-opacity:1;stroke:none;stroke-width:1.82079;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
d="M 12.164062,0.94726562 C 6.0622484,0.95386183 1.1299602,2.2428239 1.1328125,3.8300781 v 5.7617188 c -0.00281,1.5920051 4.9579969,2.8829331 11.0781255,2.8828121 6.119365,-1.6e-4 11.078978,-1.291006 11.076171,-2.8828121 V 3.8300781 C 23.289909,2.2382718 18.330304,0.94742553 12.210938,0.94726562 Z"
transform="matrix(0.70540328,0,0,0.70540328,2.813504,1.2191569)" />
<ellipse
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.28439;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
id="path1225-2-5"
cx="11.426781"
cy="3.7396178"
rx="7.8139534"
ry="2.0326128" />
</g>
<g
id="g1309"
transform="translate(1.1725937e-4,1.6131871)">
<path
id="path1225"
style="fill:#7e56c2;fill-opacity:1;stroke:none;stroke-width:1.82079;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
d="M 12.164062,0.94726562 C 6.0622484,0.95386183 1.1299602,2.2428239 1.1328125,3.8300781 v 5.7617188 c -0.00281,1.5920051 4.9579969,2.8829331 11.0781255,2.8828121 6.119365,-1.6e-4 11.078978,-1.291006 11.076171,-2.8828121 V 3.8300781 C 23.289909,2.2382718 18.330304,0.94742553 12.210938,0.94726562 Z"
transform="matrix(0.70540328,0,0,0.70540328,2.813504,1.2191569)" />
<ellipse
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.28439;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
id="path1225-2"
cx="11.426781"
cy="3.7396178"
rx="7.8139534"
ry="2.0326128" />
</g>
<ellipse
style="fill:#7e56c2;fill-opacity:1;stroke:none;stroke-width:1.28439;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
id="path1225-2-9"
cx="11.426898"
cy="3.9663849"
rx="7.8139534"
ry="2.0326128" />
<g
id="g1391">
<circle
style="fill:#ffffff;stroke:none;stroke-width:0.638958;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
id="path1149-1-2"
cx="16.495272"
cy="8.9521942"
r="1.128226" />
<circle
style="fill:#ffffff;stroke:none;stroke-width:0.638958;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stop-color:#000000"
id="path1149-6-8-8"
cx="16.495272"
cy="14.40166"
r="1.128226" />
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -1,15 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg8"
version="1.1"
viewBox="0 0 848.54461 237.29972"
height="237.29971mm"
width="848.54462mm"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
sodipodi:docname="logo-margin.svg"
inkscape:version="1.0.2 (1.0.2+r75+1)">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2434"
inkscape:window-height="1412"
id="namedview22"
showgrid="false"
inkscape:zoom="0.44294931"
inkscape:cx="1603.5488"
inkscape:cy="448.44039"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg8" />
<defs
id="defs2" />
<metadata
@ -20,9 +44,17 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<rect
y="4.2351647e-22"
x="0"
height="237.29971"
width="848.54462"
id="rect824"
style="opacity:0.98;fill:#ffffff;fill-opacity:1;stroke-width:0.514755" />
<g
id="g939"
transform="translate(-23.453818,-6.0051303)">

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -3,23 +3,20 @@
</style>
<p align="center">
<a href="https://sqlmodel.tiangolo.com"><img src="https://sqlmodel.tiangolo.com/img/logo-margin/logo-margin-vector.svg#only-light" alt="SQLModel"></a>
<!-- only-mkdocs -->
<a href="https://sqlmodel.tiangolo.com"><img src="img/logo-margin/logo-margin-white-vector.svg#only-dark" alt="SQLModel"></a>
<!-- /only-mkdocs -->
<a href="https://sqlmodel.tiangolo.com"><img src="https://sqlmodel.tiangolo.com/img/logo-margin/logo-margin-vector.svg" alt="SQLModel"></a>
</p>
<p align="center">
<em>SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness.</em>
</p>
<p align="center">
<a href="https://github.com/fastapi/sqlmodel/actions?query=workflow%3ATest" target="_blank">
<img src="https://github.com/fastapi/sqlmodel/workflows/Test/badge.svg" alt="Test">
<a href="https://github.com/tiangolo/sqlmodel/actions?query=workflow%3ATest" target="_blank">
<img src="https://github.com/tiangolo/sqlmodel/workflows/Test/badge.svg" alt="Test">
</a>
<a href="https://github.com/fastapi/sqlmodel/actions?query=workflow%3APublish" target="_blank">
<img src="https://github.com/fastapi/sqlmodel/workflows/Publish/badge.svg" alt="Publish">
<a href="https://github.com/tiangolo/sqlmodel/actions?query=workflow%3APublish" target="_blank">
<img src="https://github.com/tiangolo/sqlmodel/workflows/Publish/badge.svg" alt="Publish">
</a>
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/sqlmodel" target="_blank">
<img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/sqlmodel.svg" alt="Coverage">
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/tiangolo/sqlmodel" target="_blank">
<img src="https://coverage-badge.samuelcolvin.workers.dev/tiangolo/sqlmodel.svg" alt="Coverage">
<a href="https://pypi.org/project/sqlmodel" target="_blank">
<img src="https://img.shields.io/pypi/v/sqlmodel?color=%2334D058&label=pypi%20package" alt="Package version">
</a>
@ -29,7 +26,7 @@
**Documentation**: <a href="https://sqlmodel.tiangolo.com" target="_blank">https://sqlmodel.tiangolo.com</a>
**Source Code**: <a href="https://github.com/fastapi/sqlmodel" target="_blank">https://github.com/fastapi/sqlmodel</a>
**Source Code**: <a href="https://github.com/tiangolo/sqlmodel" target="_blank">https://github.com/tiangolo/sqlmodel</a>
---
@ -78,8 +75,6 @@ As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. T
## Installation
Make sure you create a <a href="https://sqlmodel.tiangolo.com/virtual-environments/" class="external-link" target="_blank">virtual environment</a>, activate it, and then install SQLModel, for example with:
<div class="termy">
```console
@ -235,4 +230,4 @@ And at the same time, ✨ it is also a **Pydantic** model ✨. You can use inher
## License
This project is licensed under the terms of the [MIT license](https://github.com/fastapi/sqlmodel/blob/main/LICENSE).
This project is licensed under the terms of the [MIT license](https://github.com/tiangolo/sqlmodel/blob/main/LICENSE).

View File

@ -1,39 +0,0 @@
# Install **SQLModel**
Create a project directory, create a [virtual environment](virtual-environments.md){.internal-link target=_blank}, activate it, and then install **SQLModel**, for example with:
<div class="termy">
```console
$ pip install sqlmodel
---> 100%
Successfully installed sqlmodel pydantic sqlalchemy
```
</div>
As **SQLModel** is built on top of <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> and <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a>, when you install `sqlmodel` they will also be automatically installed.
## Install DB Browser for SQLite
Remember that [SQLite is a simple database in a single file](databases.md#a-single-file-database){.internal-link target=_blank}?
For most of the tutorial I'll use SQLite for the examples.
Python has integrated support for SQLite, it is a single file read and processed from Python. And it doesn't need an [External Database Server](databases.md#a-server-database){.internal-link target=_blank}, so it will be perfect for learning.
In fact, SQLite is perfectly capable of handling quite big applications. At some point you might want to migrate to a server-based database like <a href="https://www.postgresql.org/" class="external-link" target="_blank">PostgreSQL</a> (which is also free). But for now we'll stick to SQLite.
Through the tutorial I will show you SQL fragments, and Python examples. And I hope (and expect 🧐) you to actually run them, and verify that the database is working as expected and showing you the same data.
To be able to explore the SQLite file yourself, independent of Python code (and probably at the same time), I recommend you use <a href="https://sqlitebrowser.org/" class="external-link" target="_blank">DB Browser for SQLite</a>.
It's a great and simple program to interact with SQLite databases (SQLite files) in a nice user interface.
<img src="https://sqlitebrowser.org/images/screenshot.png">
Go ahead and <a href="https://sqlitebrowser.org/" class="external-link" target="_blank">Install DB Browser for SQLite</a>, it's free.
## Next Steps
Okay, let's get going! On the next section we'll start the [Tutorial - User Guide](tutorial/index.md). 🚀

View File

@ -110,6 +110,4 @@ async function main() {
setupTermynal()
}
document$.subscribe(() => {
main()
})
main()

View File

@ -1,7 +0,0 @@
# Learn
Learn how to use **SQLModel** here.
This includes an introduction to **databases**, **SQL**, how to interact with databases from **code** and more.
You could consider this a **book**, a **course**, the **official** and recommended way to learn **SQLModel**. 😎

View File

@ -1,115 +0,0 @@
# Repository Management Tasks
These are the tasks that can be performed to manage the SQLModel repository by [team members](./management.md#team){.internal-link target=_blank}.
/// tip
This section is useful only to a handful of people, team members with permissions to manage the repository. You can probably skip it. 😉
///
...so, you are a [team member of SQLModel](./management.md#team){.internal-link target=_blank}? Wow, you are so cool! 😎
You can help with everything on [Help SQLModel - Get Help](./help.md){.internal-link target=_blank} the same ways as external contributors. But additionally, there are some tasks that only you (as part of the team) can perform.
Here are the general instructions for the tasks you can perform.
Thanks a lot for your help. 🙇
## Be Nice
First of all, be nice. 😊
You probably are super nice if you were added to the team, but it's worth mentioning it. 🤓
### When Things are Difficult
When things are great, everything is easier, so that doesn't need much instructions. But when things are difficult, here are some guidelines.
Try to find the good side. In general, if people are not being unfriendly, try to thank their effort and interest, even if you disagree with the main subject (discussion, PR), just thank them for being interested in the project, or for having dedicated some time to try to do something.
It's difficult to convey emotion in text, use emojis to help. 😅
In discussions and PRs, in many cases, people bring their frustration and show it without filter, in many cases exaggerating, complaining, being entitled, etc. That's really not nice, and when it happens, it lowers our priority to solve their problems. But still, try to breath, and be gentle with your answers.
Try to avoid using bitter sarcasm or potentially passive-aggressive comments. If something is wrong, it's better to be direct (try to be gentle) than sarcastic.
Try to be as specific and objective as possible, avoid generalizations.
For conversations that are more difficult, for example to reject a PR, you can ask me (@tiangolo) to handle it directly.
## Edit PR Titles
* Edit the PR title to start with an emoji from <a href="https://gitmoji.dev/" class="external-link" target="_blank">gitmoji</a>.
* Use the emoji character, not the GitHub code. So, use `🐛` instead of `:bug:`. This is so that it shows up correctly outside of GitHub, for example in the release notes.
* Start the title with a verb. For example `Add`, `Refactor`, `Fix`, etc. This way the title will say the action that the PR does. Like `Add support for teleporting`, instead of `Teleporting wasn't working, so this PR fixes it`.
* Edit the text of the PR title to start in "imperative", like giving an order. So, instead of `Adding support for teleporting` use `Add support for teleporting`.
* Try to make the title descriptive about what it achieves. If it's a feature, try to describe it, for example `Add support for teleporting` instead of `Create TeleportAdapter class`.
* Do not finish the title with a period (`.`).
Once the PR is merged, a GitHub Action (<a href="https://github.com/tiangolo/latest-changes" class="external-link" target="_blank">latest-changes</a>) will use the PR title to update the latest changes automatically.
So, having a nice PR title will not only look nice in GitHub, but also in the release notes. 📝
## Add Labels to PRs
The same GitHub Action <a href="https://github.com/tiangolo/latest-changes" class="external-link" target="_blank">latest-changes</a> uses one label in the PR to decide the section in the release notes to put this PR in.
Make sure you use a supported label from the <a href="https://github.com/tiangolo/latest-changes#using-labels" class="external-link" target="_blank">latest-changes list of labels</a>:
* `breaking`: Breaking Changes
* Existing code will break if they update the version without changing their code. This rarely happens, so this label is not frequently used.
* `security`: Security Fixes
* This is for security fixes, like vulnerabilities. It would almost never be used.
* `feature`: Features
* New features, adding support for things that didn't exist before.
* `bug`: Fixes
* Something that was supported didn't work, and this fixes it. There are many PRs that claim to be bug fixes because the user is doing something in an unexpected way that is not supported, but they considered it what should be supported by default. Many of these are actually features or refactors. But in some cases there's an actual bug.
* `refactor`: Refactors
* This is normally for changes to the internal code that don't change the behavior. Normally it improves maintainability, or enables future features, etc.
* `upgrade`: Upgrades
* This is for upgrades to direct dependencies from the project, or extra optional dependencies, normally in `pyproject.toml`. So, things that would affect final users, they would end up receiving the upgrade in their code base once they update. But this is not for upgrades to internal dependencies used for development, testing, docs, etc. Those internal dependencies, normally in `requirements.txt` files or GitHub Action versions should be marked as `internal`, not `upgrade`.
* `docs`: Docs
* Changes in docs. This includes updating the docs, fixing typos. But it doesn't include changes to translations.
* You can normally quickly detect it by going to the "Files changed" tab in the PR and checking if the updated file(s) starts with `docs/en/docs`. The original version of the docs is always in English, so in `docs/en/docs`.
* `internal`: Internal
* Use this for changes that only affect how the repo is managed. For example upgrades to internal dependencies, changes in GitHub Actions or scripts, etc.
/// tip
Some tools like Dependabot, will add some labels, like `dependencies`, but have in mind that this label is not used by the `latest-changes` GitHub Action, so it won't be used in the release notes. Please make sure one of the labels above is added.
///
## Review PRs
If a PR doesn't explain what it does or why, ask for more information.
A PR should have a specific use case that it is solving.
* If the PR is for a feature, it should have docs.
* Unless it's a feature we want to discourage, like support for a corner case that we don't want users to use.
* The docs should include a source example file, not write Python directly in Markdown.
* If the source example(s) file can have different syntax for Python 3.8, 3.9, 3.10, there should be different versions of the file, and they should be shown in tabs in the docs.
* There should be tests testing the source example.
* Before the PR is applied, the new tests should fail.
* After applying the PR, the new tests should pass.
* Coverage should stay at 100%.
* If you see the PR makes sense, or we discussed it and considered it should be accepted, you can add commits on top of the PR to tweak it, to add docs, tests, format, refactor, remove extra files, etc.
* Feel free to comment in the PR to ask for more information, to suggest changes, etc.
* Once you think the PR is ready, move it in the internal GitHub project for me to review it.
## Dependabot PRs
Dependabot will create PRs to update dependencies for several things, and those PRs all look similar, but some are way more delicate than others.
* If the PR is for a direct dependency, so, Dependabot is modifying `pyproject.toml`, **don't merge it**. 😱 Let me check it first. There's a good chance that some additional tweaks or updates are needed.
* If the PR updates one of the internal dependencies, for example it's modifying `requirements.txt` files, or GitHub Action versions, if the tests are passing, the release notes (shown in a summary in the PR) don't show any obvious potential breaking change, you can merge it. 😎
## Mark GitHub Discussions Answers
When a question in GitHub Discussions has been answered, mark the answer by clicking "Mark as answer".
Many of the current Discussion Questions were migrated from old issues. Many have the label `answered`, that means they were answered when they were issues, but now in GitHub Discussions, it's not known what is the actual response from the messages.
You can filter discussions by <a href="https://github.com/fastapi/sqlmodel/discussions/categories/questions?discussions_q=category:Questions+is:open+is:unanswered" class="external-link" target="_blank">`Questions` that are `Unanswered`</a>.

View File

@ -1,45 +0,0 @@
# Repository Management
Here's a short description of how the SQLModel repository is managed and maintained.
## Owner
I, <a href="https://github.com/tiangolo" target="_blank">@tiangolo</a>, am the creator and owner of the SQLModel repository. 🤓
I normally give the final review to each PR before merging them. I make the final decisions on the project, I'm the <a href="https://en.wikipedia.org/wiki/Benevolent_dictator_for_life" class="external-link" target="_blank"><abbr title="Benevolent Dictator For Life">BDFL</abbr></a>. 😅
## Team
There's a team of people that help manage and maintain the project. 😎
They have different levels of permissions and [specific instructions](./management-tasks.md){.internal-link target=_blank}.
Some of the tasks they can perform include:
* Adding labels to PRs.
* Editing PR titles.
* Adding commits on top of PRs to tweak them.
* Mark answers in GitHub Discussions questions, etc.
* Merge some specific types of PRs.
Joining the team is by invitation only, and I could update or remove permissions, instructions, or membership.
### Team Members
This is the current list of team members. 😎
<div class="user-list user-list-center">
{% for user in members["members"] %}
<div class="user"><a href="https://github.com/{{ user.login }}" target="_blank"><div class="avatar-wrapper"><img src="https://github.com/{{ user.login }}.png"/></div><div class="title">@{{ user.login }}</div></a></div>
{% endfor %}
</div>
Additional to them, there's a large community of people helping each other and getting involved in the projects in different ways.
## External Contributions
External contributions are very welcome and appreciated, including answering questions, submitting PRs, etc. 🙇‍♂️
There are many ways to [help maintain SQLModel](./help.md){.internal-link target=_blank}.

View File

@ -2,97 +2,12 @@
## Latest Changes
### Refactors
* 🚨 Fix types for new Pydantic. PR [#1131](https://github.com/fastapi/sqlmodel/pull/1131) by [@tiangolo](https://github.com/tiangolo).
### Docs
* ✏️ Fix typo in the release notes of v0.0.22. PR [#1195](https://github.com/fastapi/sqlmodel/pull/1195) by [@PipeKnight](https://github.com/PipeKnight).
* 📝 Update includes for `docs/advanced/uuid.md`. PR [#1151](https://github.com/fastapi/sqlmodel/pull/1151) by [@tiangolo](https://github.com/tiangolo).
* 📝 Update includes for `docs/tutorial/create-db-and-table.md`. PR [#1149](https://github.com/fastapi/sqlmodel/pull/1149) by [@tiangolo](https://github.com/tiangolo).
* 📝 Fix internal links in docs. PR [#1148](https://github.com/fastapi/sqlmodel/pull/1148) by [@tiangolo](https://github.com/tiangolo).
* ✏️ Fix typo in documentation. PR [#1106](https://github.com/fastapi/sqlmodel/pull/1106) by [@Solipsistmonkey](https://github.com/Solipsistmonkey).
* 📝 Remove highlights in `indexes.md` . PR [#1100](https://github.com/fastapi/sqlmodel/pull/1100) by [@alejsdev](https://github.com/alejsdev).
### Internal
* ⬆️ Upgrade markdown-include-variants to version 0.0.3. PR [#1152](https://github.com/fastapi/sqlmodel/pull/1152) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update issue manager workflow. PR [#1137](https://github.com/fastapi/sqlmodel/pull/1137) by [@alejsdev](https://github.com/alejsdev).
* 👷 Fix smokeshow, checkout files on CI. PR [#1136](https://github.com/fastapi/sqlmodel/pull/1136) by [@tiangolo](https://github.com/tiangolo).
* 👷 Use uv in CI. PR [#1135](https://github.com/fastapi/sqlmodel/pull/1135) by [@tiangolo](https://github.com/tiangolo).
* Add docs dependency markdown-include-variants. PR [#1129](https://github.com/fastapi/sqlmodel/pull/1129) by [@tiangolo](https://github.com/tiangolo).
* 🔨 Update script to standardize format. PR [#1130](https://github.com/fastapi/sqlmodel/pull/1130) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update `labeler.yml`. PR [#1128](https://github.com/fastapi/sqlmodel/pull/1128) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update worfkow deploy-docs-notify URL. PR [#1126](https://github.com/fastapi/sqlmodel/pull/1126) by [@tiangolo](https://github.com/tiangolo).
* 👷 Upgrade Cloudflare GitHub Action. PR [#1124](https://github.com/fastapi/sqlmodel/pull/1124) by [@tiangolo](https://github.com/tiangolo).
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#1097](https://github.com/fastapi/sqlmodel/pull/1097) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
* ⬆ Bump tiangolo/issue-manager from 0.5.0 to 0.5.1. PR [#1107](https://github.com/fastapi/sqlmodel/pull/1107) by [@dependabot[bot]](https://github.com/apps/dependabot).
* 👷 Update `issue-manager.yml`. PR [#1103](https://github.com/fastapi/sqlmodel/pull/1103) by [@tiangolo](https://github.com/tiangolo).
* 👷 Fix coverage processing in CI, one name per matrix run. PR [#1104](https://github.com/fastapi/sqlmodel/pull/1104) by [@tiangolo](https://github.com/tiangolo).
* 💚 Set `include-hidden-files` to `True` when using the `upload-artifact` GH action. PR [#1098](https://github.com/fastapi/sqlmodel/pull/1098) by [@svlandeg](https://github.com/svlandeg).
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#1088](https://github.com/fastapi/sqlmodel/pull/1088) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
## 0.0.22
### Fixes
* 🐛 Fix support for types with `Optional[Annotated[x, f()]]`, e.g. `id: Optional[pydantic.UUID4]`. PR [#1093](https://github.com/fastapi/sqlmodel/pull/1093) by [@tiangolo](https://github.com/tiangolo).
### Docs
* ✏️ Fix a typo in `docs/virtual-environments.md`. PR [#1085](https://github.com/fastapi/sqlmodel/pull/1085) by [@tiangolo](https://github.com/tiangolo).
* 📝 Add docs for virtual environments and environment variables, update contributing. PR [#1082](https://github.com/fastapi/sqlmodel/pull/1082) by [@tiangolo](https://github.com/tiangolo).
* 📝 Add docs about repo management and team. PR [#1059](https://github.com/tiangolo/sqlmodel/pull/1059) by [@tiangolo](https://github.com/tiangolo).
* ✏️ Fix typo in `cascade_delete` docs. PR [#1030](https://github.com/tiangolo/sqlmodel/pull/1030) by [@tiangolo](https://github.com/tiangolo).
### Internal
* ✅ Refactor test_enums to make them independent of previous imports. PR [#1095](https://github.com/fastapi/sqlmodel/pull/1095) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update `latest-changes` GitHub Action. PR [#1087](https://github.com/fastapi/sqlmodel/pull/1087) by [@tiangolo](https://github.com/tiangolo).
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#1028](https://github.com/fastapi/sqlmodel/pull/1028) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
* ⬆ Bump ruff from 0.4.7 to 0.6.2. PR [#1081](https://github.com/fastapi/sqlmodel/pull/1081) by [@dependabot[bot]](https://github.com/apps/dependabot).
* 🔧 Update lint script. PR [#1084](https://github.com/fastapi/sqlmodel/pull/1084) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update Python version for coverage. PR [#1083](https://github.com/fastapi/sqlmodel/pull/1083) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Update coverage config files. PR [#1077](https://github.com/fastapi/sqlmodel/pull/1077) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Add URLs to `pyproject.toml`, show up in PyPI. PR [#1074](https://github.com/fastapi/sqlmodel/pull/1074) by [@tiangolo](https://github.com/tiangolo).
* 👷 Do not sync labels as it overrides manually added labels. PR [#1073](https://github.com/fastapi/sqlmodel/pull/1073) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update configs for GitHub Action labeler, to add only one label. PR [#1072](https://github.com/fastapi/sqlmodel/pull/1072) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update labeler GitHub Actions permissions and dependencies. PR [#1071](https://github.com/fastapi/sqlmodel/pull/1071) by [@tiangolo](https://github.com/tiangolo).
* 👷 Add GitHub Action label-checker. PR [#1069](https://github.com/fastapi/sqlmodel/pull/1069) by [@tiangolo](https://github.com/tiangolo).
* 👷 Add GitHub Action labeler. PR [#1068](https://github.com/fastapi/sqlmodel/pull/1068) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update GitHub Action add-to-project. PR [#1067](https://github.com/fastapi/sqlmodel/pull/1067) by [@tiangolo](https://github.com/tiangolo).
* 👷 Add GitHub Action add-to-project. PR [#1066](https://github.com/fastapi/sqlmodel/pull/1066) by [@tiangolo](https://github.com/tiangolo).
* 📝 Update admonitions in annotations. PR [#1065](https://github.com/fastapi/sqlmodel/pull/1065) by [@tiangolo](https://github.com/tiangolo).
* 📝 Update links from github.com/tiangolo/sqlmodel to github.com/fastapi/sqlmodel. PR [#1064](https://github.com/fastapi/sqlmodel/pull/1064) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Update members. PR [#1063](https://github.com/tiangolo/sqlmodel/pull/1063) by [@tiangolo](https://github.com/tiangolo).
* 💄 Add dark-mode logo. PR [#1061](https://github.com/tiangolo/sqlmodel/pull/1061) by [@tiangolo](https://github.com/tiangolo).
* 🔨 Update docs.py script to enable dirty reload conditionally. PR [#1060](https://github.com/tiangolo/sqlmodel/pull/1060) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Update MkDocs previews. PR [#1058](https://github.com/tiangolo/sqlmodel/pull/1058) by [@tiangolo](https://github.com/tiangolo).
* 💄 Update Termynal line-height. PR [#1057](https://github.com/tiangolo/sqlmodel/pull/1057) by [@tiangolo](https://github.com/tiangolo).
* 👷 Upgrade build docs configs. PR [#1047](https://github.com/tiangolo/sqlmodel/pull/1047) by [@tiangolo](https://github.com/tiangolo).
* 👷 Add alls-green for test-redistribute. PR [#1055](https://github.com/tiangolo/sqlmodel/pull/1055) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update docs-previews to handle no docs changes. PR [#1056](https://github.com/tiangolo/sqlmodel/pull/1056) by [@tiangolo](https://github.com/tiangolo).
* 👷🏻 Show docs deployment status and preview URLs in comment. PR [#1054](https://github.com/tiangolo/sqlmodel/pull/1054) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Enable auto dark mode. PR [#1046](https://github.com/tiangolo/sqlmodel/pull/1046) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update issue-manager. PR [#1045](https://github.com/tiangolo/sqlmodel/pull/1045) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update issue-manager.yml GitHub Action permissions. PR [#1040](https://github.com/tiangolo/sqlmodel/pull/1040) by [@tiangolo](https://github.com/tiangolo).
* ♻️ Refactor Deploy Docs GitHub Action to be a script and update token preparing for org. PR [#1039](https://github.com/tiangolo/sqlmodel/pull/1039) by [@tiangolo](https://github.com/tiangolo).
## 0.0.21
### Features
* ✨ Add support for cascade delete relationships: `cascade_delete`, `ondelete`, and `passive_deletes`. Initial 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).

View File

@ -1,3 +0,0 @@
# Resources
Additional resources, how to **help** and get help, how to **contribute**, and more. ✈️

View File

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

View File

@ -10,14 +10,14 @@ FastAPI is the framework to create the **web API**.
But we also need another type of program to run it, it is called a "**server**". We will use **Uvicorn** for that. And we will install Uvicorn with its *standard* dependencies.
Then install FastAPI.
Make sure you [have a virtual environment activated](../index.md#create-a-python-virtual-environment){.internal-link target=_blank}.
Make sure you create a [virtual environment](../../virtual-environments.md){.internal-link target=_blank}, activate it, and then install them, for example with:
Then install FastAPI and Uvicorn:
<div class="termy">
```console
$ pip install fastapi "uvicorn[standard]"
$ python -m pip install fastapi "uvicorn[standard]"
---> 100%
```

View File

@ -42,12 +42,12 @@ If you haven't done testing in FastAPI applications, first check the <a href="ht
Then, we can continue here, the first step is to install the dependencies, `requests` and `pytest`.
Make sure you create a [virtual environment](../../virtual-environments.md){.internal-link target=_blank}, activate it, and then install them, for example with:
Make sure you do it in the same [Python environment](../index.md#create-a-python-virtual-environment){.internal-link target=_blank}.
<div class="termy">
```console
$ pip install requests pytest
$ python -m pip install requests pytest
---> 100%
```

View File

@ -1,6 +1,4 @@
# Tutorial - User Guide
In this tutorial you will learn how to use **SQLModel**.
# Intro, Installation, and First Steps
## Type hints
@ -31,3 +29,198 @@ Using it in your editor is what really shows you the benefits of **SQLModel**, s
Running the examples is what will really help you understand what is going on.
You can learn a lot more by running some examples and playing around with them than by reading all the docs here.
## Create a Project
Please go ahead and create a directory for the project we will work on on this tutorial.
What I normally do is that I create a directory named `code` inside my home/user directory.
And inside of that I create one directory per project.
So, for example:
<div class="termy">
```console
// Go to the home directory
$ cd
// Create a directory for all your code projects
$ mkdir code
// Enter into that code directory
$ cd code
// Create a directory for this project
$ mkdir sqlmodel-tutorial
// Enter into that directory
$ cd sqlmodel-tutorial
```
</div>
/// tip
Make sure you don't name it also `sqlmodel`, so that you don't end up overriding the name of the package.
///
### Make sure you have Python
Make sure you have an officially supported version of Python.
You can check which version you have with:
<div class="termy">
```console
$ python3 --version
Python 3.11
```
</div>
There's a chance that you have multiple Python versions installed.
You might want to try with the specific versions, for example with:
* `python3.12`
* `python3.11`
* `python3.10`
* `python3.9`
The code would look like this:
<div class="termy">
```console
// Let's check with just python3
$ python3 --version
// This is too old! 😱
Python 3.5.6
// Let's see if python3.10 is available
$ python3.10 --version
// Oh, no, this one is not available 😔
command not found: python3.10
$ python3.9 --version
// Nice! This works 🎉
Python 3.9.0
// In this case, we would continue using python3.9 instead of python3
```
</div>
If you have different versions and `python3` is not the latest, make sure you use the latest version you have available. For example `python3.9`.
If you don't have a valid Python version installed, go and install that first.
### Create a Python virtual environment
When writing Python code, you should **always** use virtual environments in one way or another.
If you don't know what that is, you can read the <a href="https://docs.python.org/3/tutorial/venv.html" class="external-link" target="_blank">official tutorial for virtual environments</a>, it's quite simple.
In very short, a virtual environment is a small directory that contains a copy of Python and all the libraries you need to run your code.
And when you "activate" it, any package that you install, for example with `pip`, will be installed in that virtual environment.
/// tip
There are other tools to manage virtual environments, like <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a>.
And there are alternatives that are particularly useful for deployment like <a href="https://docs.docker.com/get-started/" class="external-link" target="_blank">Docker</a> and other types of containers. In this case, the "virtual environment" is not just the Python standard files and the installed packages, but the whole system.
///
Go ahead and create a Python virtual environment for this project. And make sure to also upgrade `pip`.
Here are the commands you could use:
/// tab | Linux, macOS, Linux in Windows
<div class="termy">
```console
// Remember that you might need to use python3.9 or similar 💡
// Create the virtual environment using the module "venv"
$ python3 -m venv env
// ...here it creates the virtual environment in the directory "env"
// Activate the virtual environment
$ source ./env/bin/activate
// Verify that the virtual environment is active
# (env) $$ which python
// The important part is that it is inside the project directory, at "code/sqlmodel-tutorial/env/bin/python"
/home/leela/code/sqlmodel-tutorial/env/bin/python
// Use the module "pip" to install and upgrade the package "pip" 🤯
# (env) $$ python -m pip install --upgrade pip
---> 100%
Successfully installed pip
```
</div>
///
/// tab | Windows PowerShell
<div class="termy">
```console
// Create the virtual environment using the module "venv"
# >$ python3 -m venv env
// ...here it creates the virtual environment in the directory "env"
// Activate the virtual environment
# >$ .\env\Scripts\Activate.ps1
// Verify that the virtual environment is active
# (env) >$ Get-Command python
// The important part is that it is inside the project directory, at "code\sqlmodel-tutorial\env\python.exe"
CommandType Name Version Source
----------- ---- ------- ------
Application python 0.0.0.0 C:\Users\leela\code\sqlmodel-tutorial\env\python.exe
// Use the module "pip" to install and upgrade the package "pip" 🤯
# (env) >$ python3 -m pip install --upgrade pip
---> 100%
Successfully installed pip
```
</div>
///
## Install **SQLModel**
Now, after making sure we are inside of a virtual environment in some way, we can install **SQLModel**:
<div class="termy">
```console
# (env) $$ pip install sqlmodel
---> 100%
Successfully installed sqlmodel pydantic sqlalchemy
```
</div>
As **SQLModel** is built on top of <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> and <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a>, when you install `sqlmodel` they will also be automatically installed.
## Install DB Browser for SQLite
Remember that [SQLite is a simple database in a single file](../databases.md#a-single-file-database){.internal-link target=_blank}?
For most of the tutorial I'll use SQLite for the examples.
Python has integrated support for SQLite, it is a single file read and processed from Python. And it doesn't need an [External Database Server](../databases.md#a-server-database){.internal-link target=_blank}, so it will be perfect for learning.
In fact, SQLite is perfectly capable of handling quite big applications. At some point you might want to migrate to a server-based database like <a href="https://www.postgresql.org/" class="external-link" target="_blank">PostgreSQL</a> (which is also free). But for now we'll stick to SQLite.
Through the tutorial I will show you SQL fragments, and Python examples. And I hope (and expect 🧐) you to actually run them, and verify that the database is working as expected and showing you the same data.
To be able to explore the SQLite file yourself, independent of Python code (and probably at the same time), I recommend you use <a href="https://sqlitebrowser.org/" class="external-link" target="_blank">DB Browser for SQLite</a>.
It's a great and simple program to interact with SQLite databases (SQLite files) in a nice user interface.
<img src="https://sqlitebrowser.org/images/screenshot.png">
Go ahead and <a href="https://sqlitebrowser.org/" class="external-link" target="_blank">Install DB Browser for SQLite</a>, it's free.
## Next Steps
Okay, let's get going! On the [next section](create-db-and-table-with-db-browser.md) we'll start creating a database. 🚀

View File

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

View File

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

View File

@ -1,844 +0,0 @@
# Virtual Environments
When you work in Python projects you probably should use a **virtual environment** (or a similar mechanism) to isolate the packages you install for each project.
/// info
If you already know about virtual environments, how to create them and use them, you might want to skip this section. 🤓
///
/// tip
A **virtual environment** is different than an **environment variable**.
An **environment variable** is a variable in the system that can be used by programs.
A **virtual environment** is a directory with some files in it.
///
/// info
This page will teach you how to use **virtual environments** and how they work.
If you are ready to adopt a **tool that manages everything** for you (including installing Python), try <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>.
///
## Create a Project
First, create a directory for your project.
What I normally do is that I create a directory named `code` inside my home/user directory.
And inside of that I create one directory per project.
<div class="termy">
```console
// Go to the home directory
$ cd
// Create a directory for all your code projects
$ mkdir code
// Enter into that code directory
$ cd code
// Create a directory for this project
$ mkdir awesome-project
// Enter into that project directory
$ cd awesome-project
```
</div>
## Create a Virtual Environment
When you start working on a Python project **for the first time**, create a virtual environment **<abbr title="there are other options, this is a simple guideline">inside your project</abbr>**.
/// tip
You only need to do this **once per project**, not every time you work.
///
//// tab | `venv`
To create a virtual environment, you can use the `venv` module that comes with Python.
<div class="termy">
```console
$ python -m venv .venv
```
</div>
/// details | What that command means
* `python`: use the program called `python`
* `-m`: call a module as a script, we'll tell it which module next
* `venv`: use the module called `venv` that normally comes installed with Python
* `.venv`: create the virtual environment in the new directory `.venv`
///
////
//// tab | `uv`
If you have <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a> installed, you can use it to create a virtual environment.
<div class="termy">
```console
$ uv venv
```
</div>
/// tip
By default, `uv` will create a virtual environment in a directory called `.venv`.
But you could customize it passing an additional argument with the directory name.
///
////
That command creates a new virtual environment in a directory called `.venv`.
/// details | `.venv` or other name
You could create the virtual environment in a different directory, but there's a convention of calling it `.venv`.
///
## Activate the Virtual Environment
Activate the new virtual environment so that any Python command you run or package you install uses it.
/// tip
Do this **every time** you start a **new terminal session** to work on the project.
///
//// tab | Linux, macOS
<div class="termy">
```console
$ source .venv/bin/activate
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
$ .venv\Scripts\Activate.ps1
```
</div>
////
//// tab | Windows Bash
Or if you use Bash for Windows (e.g. <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>):
<div class="termy">
```console
$ source .venv/Scripts/activate
```
</div>
////
/// tip
Every time you install a **new package** in that environment, **activate** the environment again.
This makes sure that if you use a **terminal (<abbr title="command line interface">CLI</abbr>) program** installed by that package, you use the one from your virtual environment and not any other that could be installed globally, probably with a different version than what you need.
///
## Check the Virtual Environment is Active
Check that the virtual environment is active (the previous command worked).
/// tip
This is **optional**, but it's a good way to **check** that everything is working as expected and you are using the virtual environment you intended.
///
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
$ which python
/home/user/code/awesome-project/.venv/bin/python
```
</div>
If it shows the `python` binary at `.venv/bin/python`, inside of your project (in this case `awesome-project`), then it worked. 🎉
////
//// tab | Windows PowerShell
<div class="termy">
```console
$ Get-Command python
C:\Users\user\code\awesome-project\.venv\Scripts\python
```
</div>
If it shows the `python` binary at `.venv\Scripts\python`, inside of your project (in this case `awesome-project`), then it worked. 🎉
////
## Upgrade `pip`
/// tip
If you use <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a> you would use it to install things instead of `pip`, so you don't need to upgrade `pip`. 😎
///
If you are using `pip` to install packages (it comes by default with Python), you should **upgrade** it to the latest version.
Many exotic errors while installing a package are solved by just upgrading `pip` first.
/// tip
You would normally do this **once**, right after you create the virtual environment.
///
Make sure the virtual environment is active (with the command above) and then run:
<div class="termy">
```console
$ python -m pip install --upgrade pip
---> 100%
```
</div>
## Add `.gitignore`
If you are using **Git** (you should), add a `.gitignore` file to exclude everything in your `.venv` from Git.
/// tip
If you used <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a> to create the virtual environment, it already did this for you, you can skip this step. 😎
///
/// tip
Do this **once**, right after you create the virtual environment.
///
<div class="termy">
```console
$ echo "*" > .venv/.gitignore
```
</div>
/// details | What that command means
* `echo "*"`: will "print" the text `*` in the terminal (the next part changes that a bit)
* `>`: anything printed to the terminal by the command to the left of `>` should not be printed but instead written to the file that goes to the right of `>`
* `.gitignore`: the name of the file where the text should be written
And `*` for Git means "everything". So, it will ignore everything in the `.venv` directory.
That command will create a file `.gitignore` with the content:
```gitignore
*
```
///
## Install Packages
After activating the environment, you can install packages in it.
/// tip
Do this **once** when installing or upgrading the packages your project needs.
If you need to upgrade a version or add a new package you would **do this again**.
///
### Install Packages Directly
If you're in a hurry and don't want to use a file to declare your project's package requirements, you can install them directly.
/// tip
It's a (very) good idea to put the packages and versions your program needs in a file (for example `requirements.txt` or `pyproject.toml`).
///
//// tab | `pip`
<div class="termy">
```console
$ pip install sqlmodel
---> 100%
```
</div>
////
//// tab | `uv`
If you have <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>:
<div class="termy">
```console
$ uv pip install sqlmodel
---> 100%
```
</div>
////
### Install from `requirements.txt`
If you have a `requirements.txt`, you can now use it to install its packages.
//// tab | `pip`
<div class="termy">
```console
$ pip install -r requirements.txt
---> 100%
```
</div>
////
//// tab | `uv`
If you have <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>:
<div class="termy">
```console
$ uv pip install -r requirements.txt
---> 100%
```
</div>
////
/// details | `requirements.txt`
A `requirements.txt` with some packages could look like:
```requirements.txt
sqlmodel==0.13.0
rich==13.7.1
```
///
## Run Your Program
After you activated the virtual environment, you can run your program, and it will use the Python inside of your virtual environment with the packages you installed there.
<div class="termy">
```console
$ python main.py
Hello World
```
</div>
## Configure Your Editor
You would probably use an editor, make sure you configure it to use the same virtual environment you created (it will probably autodetect it) so that you can get autocompletion and inline errors.
For example:
* <a href="https://code.visualstudio.com/docs/python/environments#_select-and-activate-an-environment" class="external-link" target="_blank">VS Code</a>
* <a href="https://www.jetbrains.com/help/pycharm/creating-virtual-environment.html" class="external-link" target="_blank">PyCharm</a>
/// tip
You normally have to do this only **once**, when you create the virtual environment.
///
## Deactivate the Virtual Environment
Once you are done working on your project you can **deactivate** the virtual environment.
<div class="termy">
```console
$ deactivate
```
</div>
This way, when you run `python` it won't try to run it from that virtual environment with the packages installed there.
## Ready to Work
Now you're ready to start working on your project.
/// tip
Do you want to understand what's all that above?
Continue reading. 👇🤓
///
## Why Virtual Environments
To work with SQLModel you need to install <a href="https://www.python.org/" class="external-link" target="_blank">Python</a>.
After that, you would need to **install** SQLModel and any other **packages** you want to use.
To install packages you would normally use the `pip` command that comes with Python (or similar alternatives).
Nevertheless, if you just use `pip` directly, the packages would be installed in your **global Python environment** (the global installation of Python).
### The Problem
So, what's the problem with installing packages in the global Python environment?
At some point, you will probably end up writing many different programs that depend on **different packages**. And some of these projects you work on will depend on **different versions** of the same package. 😱
For example, you could create a project called `philosophers-stone`, this program depends on another package called **`harry`, using the version `1`**. So, you need to install `harry`.
```mermaid
flowchart LR
stone(philosophers-stone) -->|requires| harry-1[harry v1]
```
Then, at some point later, you create another project called `prisoner-of-azkaban`, and this project also depends on `harry`, but this project needs **`harry` version `3`**.
```mermaid
flowchart LR
azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3]
```
But now the problem is, if you install the packages globally (in the global environment) instead of in a local **virtual environment**, you will have to choose which version of `harry` to install.
If you want to run `philosophers-stone` you will need to first install `harry` version `1`, for example with:
<div class="termy">
```console
$ pip install "harry==1"
```
</div>
And then you would end up with `harry` version `1` installed in your global Python environment.
```mermaid
flowchart LR
subgraph global[global env]
harry-1[harry v1]
end
subgraph stone-project[philosophers-stone project]
stone(philosophers-stone) -->|requires| harry-1
end
```
But then if you want to run `prisoner-of-azkaban`, you will need to uninstall `harry` version `1` and install `harry` version `3` (or just installing version `3` would automatically uninstall version `1`).
<div class="termy">
```console
$ pip install "harry==3"
```
</div>
And then you would end up with `harry` version `3` installed in your global Python environment.
And if you try to run `philosophers-stone` again, there's a chance it would **not work** because it needs `harry` version `1`.
```mermaid
flowchart LR
subgraph global[global env]
harry-1[<strike>harry v1</strike>]
style harry-1 fill:#ccc,stroke-dasharray: 5 5
harry-3[harry v3]
end
subgraph stone-project[philosophers-stone project]
stone(philosophers-stone) -.-x|⛔️| harry-1
end
subgraph azkaban-project[prisoner-of-azkaban project]
azkaban(prisoner-of-azkaban) --> |requires| harry-3
end
```
/// tip
It's very common in Python packages to try the best to **avoid breaking changes** in **new versions**, but it's better to be safe, and install newer versions intentionally and when you can run the tests to check everything is working correctly.
///
Now, imagine that with **many** other **packages** that all your **projects depend on**. That's very difficult to manage. And you would probably end up running some projects with some **incompatible versions** of the packages, and not knowing why something isn't working.
Also, depending on your operating system (e.g. Linux, Windows, macOS), it could have come with Python already installed. And in that case it probably had some packages pre-installed with some specific versions **needed by your system**. If you install packages in the global Python environment, you could end up **breaking** some of the programs that came with your operating system.
## Where are Packages Installed
When you install Python, it creates some directories with some files in your computer.
Some of these directories are the ones in charge of having all the packages you install.
When you run:
<div class="termy">
```console
// Don't run this now, it's just an example 🤓
$ pip install sqlmodel
---> 100%
```
</div>
That will download a compressed file with the SQLModel code, normally from <a href="https://pypi.org/project/sqlmodel/" class="external-link" target="_blank">PyPI</a>.
It will also **download** files for other packages that SQLModel depends on.
Then it will **extract** all those files and put them in a directory in your computer.
By default, it will put those files downloaded and extracted in the directory that comes with your Python installation, that's the **global environment**.
## What are Virtual Environments
The solution to the problems of having all the packages in the global environment is to use a **virtual environment for each project** you work on.
A virtual environment is a **directory**, very similar to the global one, where you can install the packages for a project.
This way, each project will have its own virtual environment (`.venv` directory) with its own packages.
```mermaid
flowchart TB
subgraph stone-project[philosophers-stone project]
stone(philosophers-stone) --->|requires| harry-1
subgraph venv1[.venv]
harry-1[harry v1]
end
end
subgraph azkaban-project[prisoner-of-azkaban project]
azkaban(prisoner-of-azkaban) --->|requires| harry-3
subgraph venv2[.venv]
harry-3[harry v3]
end
end
stone-project ~~~ azkaban-project
```
## What Does Activating a Virtual Environment Mean
When you activate a virtual environment, for example with:
//// tab | Linux, macOS
<div class="termy">
```console
$ source .venv/bin/activate
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
$ .venv\Scripts\Activate.ps1
```
</div>
////
//// tab | Windows Bash
Or if you use Bash for Windows (e.g. <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>):
<div class="termy">
```console
$ source .venv/Scripts/activate
```
</div>
////
That command will create or modify some [environment variables](environment-variables.md){.internal-link target=_blank} that will be available for the next commands.
One of those variables is the `PATH` variable.
/// tip
You can learn more about the `PATH` environment variable in the [Environment Variables](environment-variables.md#path-environment-variable){.internal-link target=_blank} section.
///
Activating a virtual environment adds its path `.venv/bin` (on Linux and macOS) or `.venv\Scripts` (on Windows) to the `PATH` environment variable.
Let's say that before activating the environment, the `PATH` variable looked like this:
//// tab | Linux, macOS
```plaintext
/usr/bin:/bin:/usr/sbin:/sbin
```
That means that the system would look for programs in:
* `/usr/bin`
* `/bin`
* `/usr/sbin`
* `/sbin`
////
//// tab | Windows
```plaintext
C:\Windows\System32
```
That means that the system would look for programs in:
* `C:\Windows\System32`
////
After activating the virtual environment, the `PATH` variable would look something like this:
//// tab | Linux, macOS
```plaintext
/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin
```
That means that the system will now start looking first look for programs in:
```plaintext
/home/user/code/awesome-project/.venv/bin
```
before looking in the other directories.
So, when you type `python` in the terminal, the system will find the Python program in
```plaintext
/home/user/code/awesome-project/.venv/bin/python
```
and use that one.
////
//// tab | Windows
```plaintext
C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32
```
That means that the system will now start looking first look for programs in:
```plaintext
C:\Users\user\code\awesome-project\.venv\Scripts
```
before looking in the other directories.
So, when you type `python` in the terminal, the system will find the Python program in
```plaintext
C:\Users\user\code\awesome-project\.venv\Scripts\python
```
and use that one.
////
An important detail is that it will put the virtual environment path at the **beginning** of the `PATH` variable. The system will find it **before** finding any other Python available. This way, when you run `python`, it will use the Python **from the virtual environment** instead of any other `python` (for example, a `python` from a global environment).
Activating a virtual environment also changes a couple of other things, but this is one of the most important things it does.
## Checking a Virtual Environment
When you check if a virtual environment is active, for example with:
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
$ which python
/home/user/code/awesome-project/.venv/bin/python
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
$ Get-Command python
C:\Users\user\code\awesome-project\.venv\Scripts\python
```
</div>
////
That means that the `python` program that will be used is the one **in the virtual environment**.
you use `which` in Linux and macOS and `Get-Command` in Windows PowerShell.
The way that command works is that it will go and check in the `PATH` environment variable, going through **each path in order**, looking for the program called `python`. Once it finds it, it will **show you the path** to that program.
The most important part is that when you call `python`, that is the exact "`python`" that will be executed.
So, you can confirm if you are in the correct virtual environment.
/// tip
It's easy to activate one virtual environment, get one Python, and then **go to another project**.
And the second project **wouldn't work** because you are using the **incorrect Python**, from a virtual environment for another project.
It's useful being able to check what `python` is being used. 🤓
///
## Why Deactivate a Virtual Environment
For example, you could be working on a project `philosophers-stone`, **activate that virtual environment**, install packages and work with that environment.
And then you want to work on **another project** `prisoner-of-azkaban`.
You go to that project:
<div class="termy">
```console
$ cd ~/code/prisoner-of-azkaban
```
</div>
If you don't deactivate the virtual environment for `philosophers-stone`, when you run `python` in the terminal, it will try to use the Python from `philosophers-stone`.
<div class="termy">
```console
$ cd ~/code/prisoner-of-azkaban
$ python main.py
// Error importing sirius, it's not installed 😱
Traceback (most recent call last):
File "main.py", line 1, in <module>
import sirius
```
</div>
But if you deactivate the virtual environment and activate the new one for `prisoner-of-askaban` then when you run `python` it will use the Python from the virtual environment in `prisoner-of-azkaban`.
<div class="termy">
```console
$ cd ~/code/prisoner-of-azkaban
// You don't need to be in the old directory to deactivate, you can do it wherever you are, even after going to the other project 😎
$ deactivate
// Activate the virtual environment in prisoner-of-azkaban/.venv 🚀
$ source .venv/bin/activate
// Now when you run python, it will find the package sirius installed in this virtual environment ✨
$ python main.py
I solemnly swear 🐺
```
</div>
## Alternatives
This is a simple guide to get you started and teach you how everything works **underneath**.
There are many **alternatives** to managing virtual environments, package dependencies (requirements), projects.
Once you are ready and want to use a tool to **manage the entire project**, packages dependencies, virtual environments, etc. I would suggest you try <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>.
`uv` can do a lot of things, it can:
* **Install Python** for you, including different versions
* Manage the **virtual environment** for your projects
* Install **packages**
* Manage package **dependencies and versions** for your project
* Make sure you have an **exact** set of packages and versions to install, including their dependencies, so that you can be sure that you can run your project in production exactly the same as in your computer while developing, this is called **locking**
* And many other things
## Conclusion
If you read and understood all this, now **you know much more** about virtual environments than many developers out there. 🤓
Knowing these details will most probably be useful in a future time when you are debugging something that seems complex, but you will know **how it all works underneath**. 😎

View File

@ -307,11 +307,8 @@
33. Print the `hero_1`.
/// info
Even if the `hero_1` wasn't fresh, this would **not** trigger a `refresh` making the **session** use the **engine** to fetch data from the database because it is not accessing an attribute.
///
!!! info
Even if the `hero_1` wasn't fresh, this would **not** trigger a `refresh` making the **session** use the **engine** to fetch data from the database because it is not accessing an attribute.
Because the `hero_1` is fresh it has all it's data available.
@ -323,11 +320,8 @@
34. Print the `hero_2`.
/// info
Even if the `hero_2` wasn't fresh, this would **not** trigger a `refresh` making the **session** use the **engine** to fetch data from the database because it is not accessing an attribute.
///
!!! info
Even if the `hero_2` wasn't fresh, this would **not** trigger a `refresh` making the **session** use the **engine** to fetch data from the database because it is not accessing an attribute.
Because the `hero_2` is fresh it has all it's data available.
@ -339,11 +333,8 @@
35. Print the `hero_3`.
/// info
Even if the `hero_3` wasn't fresh, this would **not** trigger a `refresh` making the **session** use the **engine** to fetch data from the database because it is not accessing an attribute.
///
!!! info
Even if the `hero_3` wasn't fresh, this would **not** trigger a `refresh` making the **session** use the **engine** to fetch data from the database because it is not accessing an attribute.
Because the `hero_3` is fresh it has all it's data available.

View File

@ -14,13 +14,10 @@
3. Get one hero object, expecting exactly one.
/// tip
!!! tip
This ensures there's no more than one, and that there's exactly one, not `None`.
This ensures there's no more than one, and that there's exactly one, not `None`.
This would never return `None`, instead it would raise an exception.
///
This would never return `None`, instead it would raise an exception.
4. Print the hero object.

View File

@ -22,8 +22,5 @@
We tell it that with the `poolclass=StaticPool` parameter.
/// info
You can read more details in the <a href="https://docs.sqlalchemy.org/en/14/dialects/sqlite.html#using-a-memory-database-in-multiple-threads" class="external-link" target="_blank">SQLAlchemy documentation about Using a Memory Database in Multiple Threads</a>
///
!!! info
You can read more details in the <a href="https://docs.sqlalchemy.org/en/14/dialects/sqlite.html#using-a-memory-database-in-multiple-threads" class="external-link" target="_blank">SQLAlchemy documentation about Using a Memory Database in Multiple Threads</a>

View File

@ -22,8 +22,5 @@
We tell it that with the `poolclass=StaticPool` parameter.
/// info
You can read more details in the <a href="https://docs.sqlalchemy.org/en/14/dialects/sqlite.html#using-a-memory-database-in-multiple-threads" class="external-link" target="_blank">SQLAlchemy documentation about Using a Memory Database in Multiple Threads</a>
///
!!! info
You can read more details in the <a href="https://docs.sqlalchemy.org/en/14/dialects/sqlite.html#using-a-memory-database-in-multiple-threads" class="external-link" target="_blank">SQLAlchemy documentation about Using a Memory Database in Multiple Threads</a>

View File

@ -22,8 +22,5 @@
We tell it that with the `poolclass=StaticPool` parameter.
/// info
You can read more details in the <a href="https://docs.sqlalchemy.org/en/14/dialects/sqlite.html#using-a-memory-database-in-multiple-threads" class="external-link" target="_blank">SQLAlchemy documentation about Using a Memory Database in Multiple Threads</a>
///
!!! info
You can read more details in the <a href="https://docs.sqlalchemy.org/en/14/dialects/sqlite.html#using-a-memory-database-in-multiple-threads" class="external-link" target="_blank">SQLAlchemy documentation about Using a Memory Database in Multiple Threads</a>

View File

@ -1,110 +0,0 @@
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()

View File

@ -1,106 +0,0 @@
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()

View File

@ -1,110 +0,0 @@
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()

View File

@ -1,110 +0,0 @@
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()

View File

@ -1,108 +0,0 @@
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()

View File

@ -1,110 +0,0 @@
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()

View File

@ -1,112 +0,0 @@
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()

View File

@ -1,110 +0,0 @@
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()

View File

@ -1,112 +0,0 @@
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()

View File

@ -1,111 +0,0 @@
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()

View File

@ -1,109 +0,0 @@
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()

View File

@ -1,111 +0,0 @@
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()

View File

@ -1,124 +0,0 @@
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()

View File

@ -1,122 +0,0 @@
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()

View File

@ -1,124 +0,0 @@
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()

View File

@ -16,13 +16,10 @@
7. Create a new **session** to query data.
/// tip
!!! tip
Notice that this is a new **session** independent from the one in the other function above.
Notice that this is a new **session** independent from the one in the other function above.
But it still uses the same **engine**. We still have one engine for the whole application.
///
But it still uses the same **engine**. We still have one engine for the whole application.
8. Use the `select()` function to create a statement selecting all the `Hero` objects.

View File

@ -13,13 +13,10 @@
3. Get one hero object, expecting exactly one.
/// tip
!!! tip
This ensures there's no more than one, and that there's exactly one, not `None`.
This ensures there's no more than one, and that there's exactly one, not `None`.
This would never return `None`, instead it would raise an exception.
///
This would never return `None`, instead it would raise an exception.
4. Print the hero object.

View File

@ -35,15 +35,12 @@
INFO Engine [no key 0.00020s] ('Captain North America',)
```
/// tip
!!! tip
See the `BEGIN` at the top?
See the `BEGIN` at the top?
This is SQLAlchemy automatically starting a transaction for us.
This is SQLAlchemy automatically starting a transaction for us.
This way, we could revert the last changes (if there were some) if we wanted to, even if the SQL to create them was already sent to the database.
///
This way, we could revert the last changes (if there were some) if we wanted to, even if the SQL to create them was already sent to the database.
7. Get one hero object for this new query.
@ -101,13 +98,10 @@
INFO Engine COMMIT
```
/// tip
!!! tip
See how SQLAlchemy (that powers SQLModel) optimizes the SQL to do as much work as possible in a single batch.
See how SQLAlchemy (that powers SQLModel) optimizes the SQL to do as much work as possible in a single batch.
Here it updates both heroes in a single SQL query.
///
Here it updates both heroes in a single SQL query.
16. Refresh the first hero.
@ -121,11 +115,8 @@
INFO Engine [generated in 0.00023s] (2,)
```
/// tip
Because we just committed a SQL transaction with `COMMIT`, SQLAlchemy will automatically start a new transaction with `BEGIN`.
///
!!! tip
Because we just committed a SQL transaction with `COMMIT`, SQLAlchemy will automatically start a new transaction with `BEGIN`.
17. Refresh the second hero.
@ -138,11 +129,8 @@
INFO Engine [cached since 0.001709s ago] (7,)
```
/// tip
SQLAlchemy is still using the previous transaction, so it doesn't have to create a new one.
///
!!! tip
SQLAlchemy is still using the previous transaction, so it doesn't have to create a new one.
18. Print the first hero, now updated.

View File

@ -1,7 +1,3 @@
plugins:
social:
typeset:
markdown_extensions:
material.extensions.preview:
targets:
include:
- "*"

View File

@ -6,206 +6,136 @@ theme:
name: material
custom_dir: docs/overrides
palette:
- media: "(prefers-color-scheme)"
toggle:
icon: material/lightbulb-auto
name: Switch to light mode
- media: '(prefers-color-scheme: light)'
scheme: default
- scheme: default
primary: deep purple
accent: amber
toggle:
icon: material/lightbulb
name: Switch to dark mode
- media: '(prefers-color-scheme: dark)'
scheme: slate
- scheme: slate
primary: deep purple
accent: amber
toggle:
icon: material/lightbulb-outline
name: Switch to system preference
name: Switch to light mode
features:
- search.suggest
- search.highlight
- content.tabs.link
- navigation.indexes
- content.tooltips
- navigation.path
- content.code.annotate
- content.code.copy
# - content.code.select
- content.footnote.tooltips
- content.tabs.link
- content.tooltips
- navigation.footer
- navigation.indexes
- navigation.instant
- navigation.instant.prefetch
# - navigation.instant.preview
- navigation.instant.progress
- navigation.path
- navigation.tabs
- navigation.tabs.sticky
- navigation.top
- navigation.tracking
- search.highlight
- search.share
- search.suggest
- toc.follow
- content.code.select
# - navigation.tabs
icon:
repo: fontawesome/brands/github-alt
logo: img/icon-white.svg
favicon: img/favicon.png
language: en
repo_name: fastapi/sqlmodel
repo_url: https://github.com/fastapi/sqlmodel
repo_name: tiangolo/sqlmodel
repo_url: https://github.com/tiangolo/sqlmodel
edit_uri: ''
plugins:
# Material for MkDocs
search:
social:
# Other plugins
macros:
include_yaml:
- sponsors: data/sponsors.yml
- members: data/members.yml
search: null
markdownextradata:
data: ./data
nav:
- SQLModel: index.md
- features.md
- Learn:
- learn/index.md
- databases.md
- db-to-code.md
- environment-variables.md
- virtual-environments.md
- install.md
- Tutorial - User Guide:
- tutorial/index.md
- tutorial/create-db-and-table-with-db-browser.md
- tutorial/create-db-and-table.md
- tutorial/insert.md
- tutorial/automatic-id-none-refresh.md
- tutorial/select.md
- tutorial/where.md
- tutorial/indexes.md
- tutorial/one.md
- tutorial/limit-and-offset.md
- tutorial/update.md
- tutorial/delete.md
- Connect Tables - JOIN:
- tutorial/connect/index.md
- tutorial/connect/create-connected-tables.md
- tutorial/connect/create-connected-rows.md
- tutorial/connect/read-connected-data.md
- tutorial/connect/update-data-connections.md
- tutorial/connect/remove-data-connections.md
- Relationship Attributes:
- tutorial/relationship-attributes/index.md
- tutorial/relationship-attributes/define-relationships-attributes.md
- tutorial/relationship-attributes/create-and-update-relationships.md
- 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
- tutorial/many-to-many/create-models-with-link.md
- tutorial/many-to-many/create-data.md
- tutorial/many-to-many/update-remove-relationships.md
- tutorial/many-to-many/link-with-extra-fields.md
- tutorial/code-structure.md
- FastAPI and Pydantic:
- tutorial/fastapi/index.md
- tutorial/fastapi/simple-hero-api.md
- tutorial/fastapi/response-model.md
- tutorial/fastapi/multiple-models.md
- tutorial/fastapi/read-one.md
- tutorial/fastapi/limit-and-offset.md
- tutorial/fastapi/update.md
- tutorial/fastapi/update-extra-data.md
- tutorial/fastapi/delete.md
- tutorial/fastapi/session-with-dependency.md
- tutorial/fastapi/teams.md
- tutorial/fastapi/relationships.md
- tutorial/fastapi/tests.md
- Advanced User Guide:
- advanced/index.md
- advanced/decimal.md
- advanced/uuid.md
- Resources:
- resources/index.md
- help.md
- contributing.md
- management-tasks.md
- About:
- about/index.md
- alternatives.md
- management.md
- databases.md
- db-to-code.md
- Tutorial - User Guide:
- tutorial/index.md
- tutorial/create-db-and-table-with-db-browser.md
- tutorial/create-db-and-table.md
- tutorial/insert.md
- tutorial/automatic-id-none-refresh.md
- tutorial/select.md
- tutorial/where.md
- tutorial/indexes.md
- tutorial/one.md
- tutorial/limit-and-offset.md
- tutorial/update.md
- tutorial/delete.md
- Connect Tables - JOIN:
- tutorial/connect/index.md
- tutorial/connect/create-connected-tables.md
- tutorial/connect/create-connected-rows.md
- tutorial/connect/read-connected-data.md
- tutorial/connect/update-data-connections.md
- tutorial/connect/remove-data-connections.md
- Relationship Attributes:
- tutorial/relationship-attributes/index.md
- tutorial/relationship-attributes/define-relationships-attributes.md
- tutorial/relationship-attributes/create-and-update-relationships.md
- tutorial/relationship-attributes/read-relationships.md
- tutorial/relationship-attributes/remove-relationships.md
- tutorial/relationship-attributes/back-populates.md
- tutorial/relationship-attributes/type-annotation-strings.md
- Many to Many:
- tutorial/many-to-many/index.md
- tutorial/many-to-many/create-models-with-link.md
- tutorial/many-to-many/create-data.md
- tutorial/many-to-many/update-remove-relationships.md
- tutorial/many-to-many/link-with-extra-fields.md
- tutorial/code-structure.md
- FastAPI and Pydantic:
- tutorial/fastapi/index.md
- tutorial/fastapi/simple-hero-api.md
- tutorial/fastapi/response-model.md
- tutorial/fastapi/multiple-models.md
- tutorial/fastapi/read-one.md
- tutorial/fastapi/limit-and-offset.md
- tutorial/fastapi/update.md
- tutorial/fastapi/update-extra-data.md
- tutorial/fastapi/delete.md
- tutorial/fastapi/session-with-dependency.md
- tutorial/fastapi/teams.md
- tutorial/fastapi/relationships.md
- tutorial/fastapi/tests.md
- Advanced User Guide:
- advanced/index.md
- advanced/decimal.md
- advanced/uuid.md
- alternatives.md
- help.md
- contributing.md
- release-notes.md
markdown_extensions:
# Python Markdown
abbr:
attr_list:
footnotes:
md_in_html:
tables:
markdown.extensions.attr_list:
markdown.extensions.tables:
markdown.extensions.md_in_html:
toc:
permalink: true
# Python Markdown Extensions
pymdownx.betterem:
smart_enable: all
pymdownx.caret:
pymdownx.highlight:
line_spans: __span
pymdownx.inlinehilite:
pymdownx.keys:
pymdownx.mark:
pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
pymdownx.tilde:
# pymdownx blocks
format: !!python/name:pymdownx.superfences.fence_code_format ''
pymdownx.betterem:
pymdownx.blocks.details:
pymdownx.blocks.admonition:
types:
- note
- attention
- caution
- danger
- error
- tip
- hint
- warning
# Custom types
- info
pymdownx.blocks.details:
- tip
- warning
- danger
pymdownx.blocks.tab:
alternate_style: True
# Other extensions
mdx_include:
markdown_include_variants:
extra:
analytics:
provider: google
property: G-J8HVTT936W
feedback:
title: Was this page helpful?
ratings:
- icon: material/emoticon-happy-outline
name: This page was helpful
data: 1
note: >-
Thanks for your feedback!
- icon: material/emoticon-sad-outline
name: This page could be improved
data: 0
note: >-
Thanks for your feedback!
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/fastapi/sqlmodel
link: https://github.com/tiangolo/sqlmodel
- icon: fontawesome/brands/twitter
link: https://twitter.com/tiangolo
- icon: fontawesome/brands/linkedin
@ -224,6 +154,3 @@ extra_css:
extra_javascript:
- js/termynal.js
- js/custom.js
hooks:
- scripts/mkdocs_hooks.py

View File

@ -40,11 +40,9 @@ dependencies = [
]
[project.urls]
Homepage = "https://github.com/fastapi/sqlmodel"
Homepage = "https://github.com/tiangolo/sqlmodel"
Documentation = "https://sqlmodel.tiangolo.com"
Repository = "https://github.com/fastapi/sqlmodel"
Issues = "https://github.com/fastapi/sqlmodel/issues"
Changelog = "https://sqlmodel.tiangolo.com/release-notes/"
Repository = "https://github.com/tiangolo/sqlmodel"
[tool.pdm]
version = { source = "file", path = "sqlmodel/__init__.py" }
@ -73,18 +71,14 @@ optional-dependencies = {}
[tool.coverage.run]
parallel = true
data_file = "coverage/.coverage"
source = [
"docs_src",
"tests",
"sqlmodel"
]
context = '${CONTEXT}'
dynamic_context = "test_function"
[tool.coverage.report]
show_missing = true
sort = "-Cover"
exclude_lines = [
"pragma: no cover",
"@overload",
@ -92,9 +86,6 @@ exclude_lines = [
"if TYPE_CHECKING:",
]
[tool.coverage.html]
show_contexts = true
[tool.mypy]
strict = true

View File

@ -1,3 +0,0 @@
git+https://${TOKEN}@github.com/squidfunk/mkdocs-material-insiders.git@9.5.30-insiders-4.53.11
git+https://${TOKEN}@github.com/pawamoy-insiders/griffe-typing-deprecated.git
git+https://${TOKEN}@github.com/pawamoy-insiders/mkdocstrings-python.git

View File

@ -1,19 +1,18 @@
-e .
-r requirements-docs-tests.txt
mkdocs-material==9.5.18
mkdocs-material==9.4.7
mdx-include >=1.4.1,<2.0.0
mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0
mkdocs-redirects>=1.2.1,<1.3.0
pyyaml >=5.3.1,<7.0.0
# For Material for MkDocs, Chinese search
# jieba==0.42.1
jieba==0.42.1
# For image processing by Material for MkDocs
pillow==10.3.0
pillow==10.1.0
# For image processing by Material for MkDocs
cairosvg==2.7.1
# mkdocstrings[python]==0.25.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
typer == 0.12.3
mkdocs-macros-plugin==1.0.5
markdown-include-variants==0.0.3

View File

@ -1,5 +0,0 @@
PyGithub>=2.3.0,<3.0.0
pydantic>=2.5.3,<3.0.0
pydantic-settings>=2.1.0,<3.0.0
httpx>=0.27.0,<0.28.0
smokeshow

View File

@ -1,9 +1,9 @@
-e .
-r requirements-docs-tests.txt
pytest >=7.0.1,<8.0.0
pytest >=7.0.1,<9.0.0
coverage[toml] >=6.2,<8.0
mypy ==1.4.1
ruff ==0.6.2
ruff ==0.4.7
# For FastAPI tests
fastapi >=0.103.2
httpx ==0.24.1

View File

@ -1,89 +0,0 @@
import logging
import re
from github import Github
from pydantic import SecretStr
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
github_repository: str
github_token: SecretStr
deploy_url: str | None = None
commit_sha: str
run_id: int
is_done: bool = False
def main():
logging.basicConfig(level=logging.INFO)
settings = Settings()
logging.info(f"Using config: {settings.model_dump_json()}")
g = Github(settings.github_token.get_secret_value())
repo = g.get_repo(settings.github_repository)
use_pr = next(
(pr for pr in repo.get_pulls() if pr.head.sha == settings.commit_sha), None
)
if not use_pr:
logging.error(f"No PR found for hash: {settings.commit_sha}")
return
commits = list(use_pr.get_commits())
current_commit = [c for c in commits if c.sha == settings.commit_sha][0]
run_url = f"https://github.com/{settings.github_repository}/actions/runs/{settings.run_id}"
if settings.is_done and not settings.deploy_url:
current_commit.create_status(
state="success",
description="No Docs Changes",
context="deploy-docs",
target_url=run_url,
)
logging.info("No docs changes found")
return
if not settings.deploy_url:
current_commit.create_status(
state="pending",
description="Deploying Docs",
context="deploy-docs",
target_url=run_url,
)
logging.info("No deploy URL available yet")
return
current_commit.create_status(
state="success",
description="Docs Deployed",
context="deploy-docs",
target_url=run_url,
)
files = list(use_pr.get_files())
docs_files = [f for f in files if f.filename.startswith("docs/")]
deploy_url = settings.deploy_url.rstrip("/")
links: list[str] = []
for f in docs_files:
match = re.match(r"docs/(.*)", f.filename)
assert match
path = match.group(1)
if path.endswith("index.md"):
path = path.replace("index.md", "")
else:
path = path.replace(".md", "/")
link = f"{deploy_url}/{path}"
links.append(link)
links.sort()
message = f"📝 Docs preview for commit {settings.commit_sha} at: {deploy_url}"
if links:
message += "\n\n### Modified Pages\n\n"
message += "\n".join([f"* {link}" for link in links])
print(message)
use_pr.as_issue().create_comment(message)
logging.info("Finished")
if __name__ == "__main__":
main()

View File

@ -7,6 +7,9 @@ from http.server import HTTPServer, SimpleHTTPRequestHandler
from importlib import metadata
from pathlib import Path
import mkdocs.commands.build
import mkdocs.commands.serve
import mkdocs.config
import mkdocs.utils
import typer
from jinja2 import Template
@ -65,13 +68,6 @@ def generate_readme_content() -> str:
pre_content = content[frontmatter_end:pre_end]
post_content = content[post_start:]
new_content = pre_content + message + post_content
# Remove content between <!-- only-mkdocs --> and <!-- /only-mkdocs -->
new_content = re.sub(
r"<!-- only-mkdocs -->.*?<!-- /only-mkdocs -->",
"",
new_content,
flags=re.DOTALL,
)
return new_content
@ -104,7 +100,7 @@ def verify_readme() -> None:
@app.command()
def live(dirty: bool = False) -> None:
def live() -> None:
"""
Serve with livereload a docs site for a specific language.
@ -115,10 +111,8 @@ def live(dirty: bool = False) -> None:
en.
"""
# Enable line numbers during local development to make it easier to highlight
args = ["mkdocs", "serve", "--dev-addr", "127.0.0.1:8008"]
if dirty:
args.append("--dirty")
subprocess.run(args, env={**os.environ, "LINENUMS": "true"}, check=True)
os.environ["LINENUMS"] = "true"
mkdocs.commands.serve.serve(dev_addr="127.0.0.1:8008")
@app.command()

View File

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

View File

@ -5,4 +5,4 @@ set -x
mypy sqlmodel
ruff check sqlmodel tests docs_src scripts
ruff format sqlmodel tests docs_src scripts --check
ruff format sqlmodel tests docs_src --check

View File

@ -1,38 +0,0 @@
from typing import Any, List, Union
from mkdocs.config.defaults import MkDocsConfig
from mkdocs.structure.files import Files
from mkdocs.structure.nav import Link, Navigation, Section
from mkdocs.structure.pages import Page
def generate_renamed_section_items(
items: List[Union[Page, Section, Link]], *, config: MkDocsConfig
) -> List[Union[Page, Section, Link]]:
new_items: List[Union[Page, Section, Link]] = []
for item in items:
if isinstance(item, Section):
new_title = item.title
new_children = generate_renamed_section_items(item.children, config=config)
first_child = new_children[0]
if isinstance(first_child, Page):
if first_child.file.src_path.endswith("index.md"):
# Read the source so that the title is parsed and available
first_child.read_source(config=config)
new_title = first_child.title or new_title
# Creating a new section makes it render it collapsed by default
# no idea why, so, let's just modify the existing one
# new_section = Section(title=new_title, children=new_children)
item.title = new_title
item.children = new_children
new_items.append(item)
else:
new_items.append(item)
return new_items
def on_nav(
nav: Navigation, *, config: MkDocsConfig, files: Files, **kwargs: Any
) -> Navigation:
new_items = generate_renamed_section_items(nav.items, config=config)
return Navigation(items=new_items, pages=nav.pages)

View File

@ -5,5 +5,5 @@ set -x
coverage run -m pytest tests
coverage combine
coverage report
coverage report --show-missing
coverage html

View File

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

View File

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

View File

@ -52,7 +52,7 @@ from sqlalchemy.orm.decl_api import DeclarativeMeta
from sqlalchemy.orm.instrumentation import is_instrumented
from sqlalchemy.sql.schema import MetaData
from sqlalchemy.sql.sqltypes import LargeBinary, Time, Uuid
from typing_extensions import Literal, TypeAlias, deprecated, get_origin
from typing_extensions import Literal, deprecated, get_origin
from ._compat import ( # type: ignore[attr-defined]
IS_PYDANTIC_V2,
@ -71,7 +71,7 @@ from ._compat import ( # type: ignore[attr-defined]
get_field_metadata,
get_model_fields,
get_relationship_to,
get_sa_type_from_field,
get_type_from_field,
init_pydantic_private_attrs,
is_field_noneable,
is_table_model_class,
@ -90,13 +90,7 @@ if TYPE_CHECKING:
_T = TypeVar("_T")
NoArgAnyCallable = Callable[[], Any]
IncEx: TypeAlias = Union[
Set[int],
Set[str],
Mapping[int, Union["IncEx", Literal[True]]],
Mapping[str, Union["IncEx", Literal[True]]],
]
OnDeleteType = Literal["CASCADE", "SET NULL", "RESTRICT"]
IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any], None]
def __dataclass_transform__(
@ -114,7 +108,6 @@ 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)
@ -139,17 +132,13 @@ 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"
@ -162,14 +151,10 @@ 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
@ -183,8 +168,6 @@ 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,
@ -202,15 +185,12 @@ 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,
@ -254,62 +234,6 @@ def Field(
) -> 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,
@ -378,7 +302,6 @@ 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,
@ -416,7 +339,6 @@ def Field(
repr=repr,
primary_key=primary_key,
foreign_key=foreign_key,
ondelete=ondelete,
unique=unique,
nullable=nullable,
index=index,
@ -434,8 +356,6 @@ 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,
@ -446,8 +366,6 @@ def Relationship(
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: ...
@ -456,8 +374,6 @@ def Relationship(
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,
@ -465,8 +381,6 @@ 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,
@ -617,10 +531,6 @@ 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
@ -654,7 +564,7 @@ def get_sqlalchemy_type(field: Any) -> Any:
if sa_type is not Undefined:
return sa_type
type_ = get_sa_type_from_field(field)
type_ = get_type_from_field(field)
metadata = get_field_metadata(field)
# Check enums first as an enum can also be a str, needed by Pydantic/FastAPI
@ -732,14 +642,8 @@ 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)
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))
args.append(ForeignKey(foreign_key))
kwargs = {
"primary_key": primary_key,
"nullable": nullable,
@ -863,8 +767,8 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry
self,
*,
mode: Union[Literal["json", "python"], str] = "python",
include: Union[IncEx, None] = None,
exclude: Union[IncEx, None] = None,
include: IncEx = None,
exclude: IncEx = None,
context: Union[Dict[str, Any], None] = None,
by_alias: bool = False,
exclude_unset: bool = False,
@ -913,8 +817,8 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry
def dict(
self,
*,
include: Union[IncEx, None] = None,
exclude: Union[IncEx, None] = None,
include: IncEx = None,
exclude: IncEx = None,
by_alias: bool = False,
exclude_unset: bool = False,
exclude_defaults: bool = False,

View File

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

View File

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

View File

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

View File

@ -108,14 +108,3 @@ 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",
)

View File

@ -1,37 +0,0 @@
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)

View File

@ -1,72 +0,0 @@
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],
]

View File

@ -1,73 +0,0 @@
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],
]

View File

@ -1,73 +0,0 @@
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],
]

View File

@ -1,90 +0,0 @@
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,
},
],
]

View File

@ -1,91 +0,0 @@
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,
},
],
]

Some files were not shown because too many files have changed in this diff Show More