Merge branch 'main' into main

This commit is contained in:
Sebastián Ramírez 2023-10-29 11:55:17 +04:00 committed by GitHub
commit a1833901f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
165 changed files with 3744 additions and 3153 deletions

View File

@ -1,5 +1,3 @@
name: Question or Problem
description: Ask a question or ask about a problem
labels: [question]
body:
- type: markdown
@ -8,29 +6,29 @@ body:
Thanks for your interest in SQLModel! 🚀
Please follow these instructions, fill every question, and do every step. 🙏
I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time.
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues.
I'm asking this because answering questions and solving problems in GitHub is what consumes most of the time.
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling questions.
All that, on top of all the incredible help provided by a bunch of community members that give a lot of their time to come here and help others.
If more SQLModel users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅).
By asking questions in a structured way (following this) it will be much easier to help you.
And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
As there are too many questions, I'll have to discard and close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
- type: checkboxes
id: checks
attributes:
label: First Check
description: Please confirm and check all the following options.
options:
- label: I added a very descriptive title to this issue.
- label: I added a very descriptive title here.
required: true
- label: I used the GitHub search to find a similar issue and didn't find it.
- label: I used the GitHub search to find a similar question and didn't find it.
required: true
- label: I searched the SQLModel documentation, with the integrated search.
required: true
@ -48,10 +46,10 @@ body:
label: Commit to Help
description: |
After submitting this, I commit to one of:
* Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
* I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
* Implement a Pull Request for a confirmed bug.
* Review one Pull Request by downloading the code and following all the review process](https://sqlmodel.tiangolo.com/help/#review-pull-requests).
options:
- label: I commit to help with one of those options 👆

View File

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

View File

@ -1,214 +0,0 @@
name: Feature Request
description: Suggest an idea or ask for a feature that you would like to have in SQLModel
labels: [enhancement]
body:
- type: markdown
attributes:
value: |
Thanks for your interest in SQLModel! 🚀
Please follow these instructions, fill every question, and do every step. 🙏
I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time.
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues.
All that, on top of all the incredible help provided by a bunch of community members that give a lot of their time to come here and help others.
If more SQLModel users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅).
By asking questions in a structured way (following this) it will be much easier to help you.
And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
- type: checkboxes
id: checks
attributes:
label: First Check
description: Please confirm and check all the following options.
options:
- label: I added a very descriptive title to this issue.
required: true
- label: I used the GitHub search to find a similar issue and didn't find it.
required: true
- label: I searched the SQLModel documentation, with the integrated search.
required: true
- label: I already searched in Google "How to X in SQLModel" and didn't find any information.
required: true
- label: I already read and followed all the tutorial in the docs and didn't find an answer.
required: true
- label: I already checked if it is not related to SQLModel but to [Pydantic](https://github.com/samuelcolvin/pydantic).
required: true
- label: I already checked if it is not related to SQLModel but to [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy).
required: true
- type: checkboxes
id: help
attributes:
label: Commit to Help
description: |
After submitting this, I commit to one of:
* Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
* I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
* Implement a Pull Request for a confirmed bug.
options:
- label: I commit to help with one of those options 👆
required: true
- type: textarea
id: example
attributes:
label: Example Code
description: |
Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case.
If I (or someone) can copy it, run it, and see it right away, there's a much higher chance I (or someone) will be able to help you.
placeholder: |
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str
age: Optional[int] = None
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
engine = create_engine("sqlite:///database.db")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
session.add(hero_1)
session.commit()
session.refresh(hero_1)
print(hero_1)
render: python
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: |
What is your feature request?
Write a short description telling me what you are trying to solve and what you are currently doing.
placeholder: |
* Create a Hero model.
* Create a Hero instance.
* Save it to a SQLite database.
* I would like it to also automatically send me an email with all the SQL code executed.
validations:
required: true
- type: textarea
id: wanted-solution
attributes:
label: Wanted Solution
description: |
Tell me what's the solution you would like.
placeholder: |
I would like it to have a `send_email` configuration that defaults to `False`, and can be set to `True` to send me an email.
validations:
required: true
- type: textarea
id: wanted-code
attributes:
label: Wanted Code
description: Show me an example of how you would want the code to look like.
placeholder: |
from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine
class Hero(SQLModel, table=True, send_email=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str
age: Optional[int] = None
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
engine = create_engine("sqlite:///database.db")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
session.add(hero_1)
session.commit()
session.refresh(hero_1)
print(hero_1)
render: python
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives
description: |
Tell me about alternatives you've considered.
placeholder: |
To hire someone to look at the logs, write the SQL in paper, and then send me a letter by post with it.
- type: dropdown
id: os
attributes:
label: Operating System
description: What operating system are you on?
multiple: true
options:
- Linux
- Windows
- macOS
- Other
validations:
required: true
- type: textarea
id: os-details
attributes:
label: Operating System Details
description: You can add more details about your operating system here, in particular if you chose "Other".
- type: input
id: sqlmodel-version
attributes:
label: SQLModel Version
description: |
What SQLModel version are you using?
You can find the SQLModel version with:
```bash
python -c "import sqlmodel; print(sqlmodel.__version__)"
```
validations:
required: true
- type: input
id: python-version
attributes:
label: Python Version
description: |
What Python version are you using?
You can find the Python version with:
```bash
python --version
```
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional Context
description: Add any additional context information or screenshots you think are useful.

22
.github/ISSUE_TEMPLATE/privileged.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Privileged
description: You are @tiangolo or he asked you directly to create an issue here. If not, check the other options. 👇
body:
- type: markdown
attributes:
value: |
Thanks for your interest in SQLModel! 🚀
If you are not @tiangolo or he didn't ask you directly to create an issue here, please start the conversation in a [Question in GitHub Discussions](https://github.com/tiangolo/sqlmodel/discussions/categories/questions) instead.
- type: checkboxes
id: privileged
attributes:
label: Privileged issue
description: Confirm that you are allowed to create an issue here.
options:
- label: I'm @tiangolo or he asked me directly to create an issue here.
required: true
- type: textarea
id: content
attributes:
label: Issue Content
description: Add the content of the issue here.

View File

@ -48,9 +48,7 @@ if __name__ == "__main__":
use_pr = pr
break
if not use_pr:
logging.error(
f"No PR found for hash: {event.workflow_run.head_commit.id}"
)
logging.error(f"No PR found for hash: {event.workflow_run.head_commit.id}")
sys.exit(0)
github_headers = {
"Authorization": f"token {settings.input_token.get_secret_value()}"

View File

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

View File

@ -1,10 +0,0 @@
name: Watch docs previews in PRs
description: Check PRs and trigger new docs deploys
author: "Sebastián Ramírez <tiangolo@gmail.com>"
inputs:
token:
description: 'Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}'
required: true
runs:
using: docker
image: Dockerfile

View File

@ -1,102 +0,0 @@
import logging
from datetime import datetime
from pathlib import Path
from typing import List, Optional
import httpx
from github import Github
from github.NamedUser import NamedUser
from pydantic import BaseModel, BaseSettings, SecretStr
github_api = "https://api.github.com"
netlify_api = "https://api.netlify.com"
main_branch = "main"
class Settings(BaseSettings):
input_token: SecretStr
github_repository: str
github_event_path: Path
github_event_name: Optional[str] = None
class Artifact(BaseModel):
id: int
node_id: str
name: str
size_in_bytes: int
url: str
archive_download_url: str
expired: bool
created_at: datetime
updated_at: datetime
class ArtifactResponse(BaseModel):
total_count: int
artifacts: List[Artifact]
def get_message(commit: str) -> str:
return f"Docs preview for commit {commit} at"
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
settings = Settings()
logging.info(f"Using config: {settings.json()}")
g = Github(settings.input_token.get_secret_value())
repo = g.get_repo(settings.github_repository)
owner: NamedUser = repo.owner
headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"}
prs = list(repo.get_pulls(state="open"))
response = httpx.get(
f"{github_api}/repos/{settings.github_repository}/actions/artifacts",
headers=headers,
)
data = response.json()
artifacts_response = ArtifactResponse.parse_obj(data)
for pr in prs:
logging.info("-----")
logging.info(f"Processing PR #{pr.number}: {pr.title}")
pr_comments = list(pr.get_issue_comments())
pr_commits = list(pr.get_commits())
last_commit = pr_commits[0]
for pr_commit in pr_commits:
if pr_commit.commit.author.date > last_commit.commit.author.date:
last_commit = pr_commit
commit = last_commit.commit.sha
logging.info(f"Last commit: {commit}")
message = get_message(commit)
notified = False
for pr_comment in pr_comments:
if message in pr_comment.body:
notified = True
logging.info(f"Docs preview was notified: {notified}")
if not notified:
artifact_name = f"docs-zip-{commit}"
use_artifact: Optional[Artifact] = None
for artifact in artifacts_response.artifacts:
if artifact.name == artifact_name:
use_artifact = artifact
break
if not use_artifact:
logging.info("Artifact not available")
else:
logging.info(f"Existing artifact: {use_artifact.name}")
response = httpx.post(
f"{github_api}/repos/{settings.github_repository}/actions/workflows/preview-docs.yml/dispatches",
headers=headers,
json={
"ref": main_branch,
"inputs": {
"pr": f"{pr.number}",
"name": artifact_name,
"commit": commit,
},
},
)
logging.info(
f"Trigger sent, response status: {response.status_code} - content: {response.content}"
)
logging.info("Finished")

View File

@ -4,78 +4,91 @@ on:
branches:
- main
pull_request:
types: [opened, synchronize]
workflow_dispatch:
inputs:
debug_enabled:
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
required: false
default: false
types:
- opened
- synchronize
jobs:
changes:
runs-on: ubuntu-latest
# Required permissions
permissions:
pull-requests: read
# Set job outputs to values from filter step
outputs:
docs: ${{ steps.filter.outputs.docs }}
steps:
- uses: actions/checkout@v4
# For pull requests it's not necessary to checkout the code but for the main branch it is
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
docs:
- README.md
- docs/**
- docs_src/**
- pyproject.toml
- mkdocs.yml
- mkdocs.insiders.yml
build-docs:
runs-on: ubuntu-20.04
needs:
- changes
if: ${{ needs.changes.outputs.docs == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.7"
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
with:
limit-access-to-actor: true
python-version: "3.11"
- uses: actions/cache@v3
id: cache
with:
path: ${{ env.pythonLocation }}
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root-docs
- name: Install poetry
key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-v01
- name: Install Poetry
if: steps.cache.outputs.cache-hit != 'true'
# TODO: remove python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
# once there's a release of Poetry 1.2.x including poetry-core > 1.1.0a6
# Ref: https://github.com/python-poetry/poetry-core/pull/188
run: |
python -m pip install --upgrade pip
python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
python -m pip install "poetry==1.2.0a2"
python -m poetry plugin add poetry-version-plugin
python -m pip install "poetry"
python -m poetry self add poetry-version-plugin
- name: Configure poetry
run: python -m poetry config virtualenvs.create false
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: python -m poetry install
- name: Install Material for MkDocs Insiders
if: github.event.pull_request.head.repo.fork == false && steps.cache.outputs.cache-hit != 'true'
run: python -m poetry run pip install git+https://${{ secrets.ACTIONS_TOKEN }}@github.com/squidfunk/mkdocs-material-insiders.git
if: ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false ) && steps.cache.outputs.cache-hit != 'true'
run: python -m poetry run pip install git+https://${{ secrets.SQLMODEL_MKDOCS_MATERIAL_INSIDERS }}@github.com/squidfunk/mkdocs-material-insiders.git
- uses: actions/cache@v3
with:
key: mkdocs-cards-${{ github.ref }}
path: .cache
- name: Build Docs
if: github.event.pull_request.head.repo.fork == true
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
run: python -m poetry run mkdocs build
- name: Build Docs with Insiders
if: github.event.pull_request.head.repo.fork == false
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
run: python -m poetry run mkdocs build --config-file mkdocs.insiders.yml
- name: Zip docs
run: python -m poetry run bash ./scripts/zip-docs.sh
- uses: actions/upload-artifact@v3
with:
name: docs-zip
path: ./site/docs.zip
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v1.1.5
name: docs-site
path: ./site/**
# https://github.com/marketplace/actions/alls-green#why
docs-all-green: # This job does nothing and is only used for the branch protection
if: always()
needs:
- build-docs
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
with:
publish-dir: './site'
production-branch: main
github-token: ${{ secrets.GITHUB_TOKEN }}
enable-commit-comment: false
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
jobs: ${{ toJSON(needs) }}
allowed-skips: build-docs

48
.github/workflows/deploy-docs.yml vendored Normal file
View File

@ -0,0 +1,48 @@
name: Deploy Docs
on:
workflow_run:
workflows:
- Build Docs
types:
- completed
jobs:
deploy-docs:
runs-on: ubuntu-latest
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v4
- name: Clean site
run: |
rm -rf ./site
mkdir ./site
- name: Download Artifact Docs
id: download
uses: dawidd6/action-download-artifact@v2.28.0
with:
if_no_artifact_found: ignore
github_token: ${{ secrets.GITHUB_TOKEN }}
workflow: build-docs.yml
run_id: ${{ github.event.workflow_run.id }}
name: docs-site
path: ./site/
- name: Deploy to Cloudflare Pages
if: steps.download.outputs.found_artifact == 'true'
id: deploy
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: sqlmodel
directory: './site'
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ ( github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'main' && 'main' ) || ( github.event.workflow_run.head_sha ) }}
- name: Comment Deploy
if: steps.deploy.outputs.url != ''
uses: ./.github/actions/comment-docs-preview-in-pr
with:
token: ${{ secrets.GITHUB_TOKEN }}
deploy_url: "${{ steps.deploy.outputs.url }}"

View File

@ -12,22 +12,22 @@ on:
description: PR number
required: true
debug_enabled:
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
required: false
default: false
default: 'false'
jobs:
latest-changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4
with:
# To allow latest-changes to commit to the main branch
token: ${{ secrets.ACTIONS_TOKEN }}
token: ${{ secrets.SQLMODEL_LATEST_CHANGES }}
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- uses: docker://tiangolo/latest-changes:0.0.3

View File

@ -1,46 +0,0 @@
name: Preview Docs
on:
workflow_run:
workflows:
- Build Docs
types:
- completed
jobs:
preview-docs:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3.1.0
- name: Clean site
run: |
rm -rf ./site
mkdir ./site
- name: Download Artifact Docs
uses: dawidd6/action-download-artifact@v2.24.2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
workflow: build-docs.yml
run_id: ${{ github.event.workflow_run.id }}
name: docs-zip
path: ./site/
- name: Unzip docs
run: |
cd ./site
unzip docs.zip
rm -f docs.zip
- name: Deploy to Netlify
id: netlify
uses: nwtgck/actions-netlify@v1.1.5
with:
publish-dir: './site'
production-deploy: false
github-token: ${{ secrets.GITHUB_TOKEN }}
enable-commit-comment: false
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
- name: Comment Deploy
uses: ./.github/actions/comment-docs-preview-in-pr
with:
token: ${{ secrets.GITHUB_TOKEN }}
deploy_url: "${{ steps.netlify.outputs.deploy-url }}"

View File

@ -7,15 +7,15 @@ on:
workflow_dispatch:
inputs:
debug_enabled:
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
required: false
default: false
default: 'false'
jobs:
publish:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
@ -23,24 +23,20 @@ jobs:
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- uses: actions/cache@v3
id: cache
with:
path: ${{ env.pythonLocation }}
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root-v2
- name: Install poetry
if: steps.cache.outputs.cache-hit != 'true'
# TODO: remove python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
# once there's a release of Poetry 1.2.x including poetry-core > 1.1.0a6
# Ref: https://github.com/python-poetry/poetry-core/pull/188
run: |
python -m pip install --upgrade pip
python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
python -m pip install "poetry==1.2.0a2"
python -m poetry plugin add poetry-version-plugin
python -m pip install "poetry"
python -m poetry self add poetry-version-plugin
- name: Configure poetry
run: python -m poetry config virtualenvs.create false
- name: Install Dependencies

View File

@ -20,7 +20,7 @@ jobs:
- run: pip install smokeshow
- uses: dawidd6/action-download-artifact@v2.24.2
- uses: dawidd6/action-download-artifact@v2.28.0
with:
workflow: test.yml
commit: ${{ github.event.workflow_run.head_sha }}

View File

@ -5,24 +5,30 @@ on:
branches:
- main
pull_request:
types: [opened, synchronize]
types:
- opened
- synchronize
workflow_dispatch:
inputs:
debug_enabled:
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
required: false
default: false
default: 'false'
jobs:
test:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.6.15", "3.7", "3.8", "3.9", "3.10"]
python-version:
- "3.7"
- "3.8"
- "3.9"
- "3.10"
fail-fast: false
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
@ -30,31 +36,26 @@ jobs:
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- uses: actions/cache@v3
id: cache
with:
path: ${{ env.pythonLocation }}
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-root-v2
- name: Install poetry
if: steps.cache.outputs.cache-hit != 'true'
# TODO: remove python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
# once there's a release of Poetry 1.2.x including poetry-core > 1.1.0a6
# Ref: https://github.com/python-poetry/poetry-core/pull/188
run: |
python -m pip install --upgrade pip
python -m pip install --force git+https://github.com/python-poetry/poetry-core.git@ad33bc2
python -m pip install "poetry==1.2.0a2"
python -m poetry plugin add poetry-version-plugin
python -m pip install "poetry"
python -m poetry self add poetry-version-plugin
- name: Configure poetry
run: python -m poetry config virtualenvs.create false
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: python -m poetry install
- name: Lint
if: ${{ matrix.python-version != '3.6.15' }}
run: python -m poetry run bash scripts/lint.sh
- run: mkdir coverage
- name: Test
@ -68,11 +69,12 @@ jobs:
name: coverage
path: coverage
coverage-combine:
needs: [test]
needs:
- test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
@ -96,3 +98,15 @@ jobs:
with:
name: coverage-html
path: htmlcov
# https://github.com/marketplace/actions/alls-green#why
alls-green: # This job does nothing and is only used for the branch protection
if: always()
needs:
- coverage-combine
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}

25
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,25 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
default_language_version:
python: python3.10
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-added-large-files
- id: check-toml
- id: check-yaml
args:
- --unsafe
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.1.2
hooks:
- id: ruff
args:
- --fix
- id: ruff-format
ci:
autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate

24
CITATION.cff Normal file
View File

@ -0,0 +1,24 @@
# This CITATION.cff file was generated with cffinit.
# Visit https://bit.ly/cffinit to generate yours today!
cff-version: 1.2.0
title: SQLModel
message: >-
If you use this software, please cite it using the
metadata from this file.
type: software
authors:
- given-names: Sebastián
family-names: Ramírez
email: tiangolo@gmail.com
identifiers:
repository-code: 'https://github.com/tiangolo/sqlmodel'
url: 'https://sqlmodel.tiangolo.com'
abstract: >-
SQLModel, SQL databases in Python, designed for
simplicity, compatibility, and robustness.
keywords:
- fastapi
- pydantic
- sqlalchemy
license: MIT

View File

@ -50,7 +50,7 @@ It combines SQLAlchemy and Pydantic and tries to simplify the code you write as
## Requirements
A recent and currently supported version of Python (right now, <a href="https://www.python.org/downloads/" class="external-link" target="_blank">Python supports versions 3.6 and above</a>).
A recent and currently supported <a href="https://www.python.org/downloads/" class="external-link" target="_blank">version of Python</a>.
As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. They will be automatically installed when you install SQLModel.

View File

@ -6,10 +6,6 @@ First, you might want to see the basic ways to [help SQLModel and get help](help
If you already cloned the repository and you know that you need to deep dive in the code, here are some guidelines to set up your environment.
### Python
SQLModel supports Python 3.6 and above, but for development you should have at least **Python 3.7**.
### Poetry
**SQLModel** uses <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a> to build, package, and publish the project.
@ -116,7 +112,7 @@ There is a script that you can run locally to test all the code and generate cov
<div class="termy">
```console
$ bash scripts/test-cov-html.sh
$ bash scripts/test.sh
```
</div>

View File

@ -111,7 +111,7 @@ DROP TABLE hero;
That is how you tell the database in SQL to delete the entire table `hero`.
<a href="http://www.nooooooooooooooo.com/" class="external-link" target="_blank">Nooooo!</a> We lost all the data in the `hero` table! 💥😱
<a href="https://theuselessweb.site/nooooooooooooooo/" class="external-link" target="_blank">Nooooo!</a> We lost all the data in the `hero` table! 💥😱
### SQL Sanitization
@ -305,4 +305,4 @@ You will see **your own code** a lot more than the internal table names, so it's
So, to keep things consistent, I'll keep using the same table names that **SQLModel** would have generated.
!!! tip
You can also override the table name. You can read about it in the Advanced User Guide.
You can also override the table name. You can read about it in the Advanced User Guide.

View File

@ -12,7 +12,7 @@ Nevertheless, SQLModel is completely **independent** of FastAPI and can be used
## Just Modern Python
It's all based on standard <abbr title="Python currently supported versions, 3.6 and above.">modern **Python**</abbr> type annotations. No new syntax to learn. Just standard modern Python.
It's all based on standard <abbr title="Currently supported versions of Python">modern **Python**</abbr> type annotations. No new syntax to learn. Just standard modern Python.
If you need a 2 minute refresher of how to use Python types (even if you don't use SQLModel or FastAPI), check the FastAPI tutorial section: <a href="https://fastapi.tiangolo.com/python-types/" class="external-link" target="_blank">Python types intro</a>.

View File

@ -58,26 +58,125 @@ You can:
I love to hear about how **SQLModel** is being used, what you have liked in it, in which project/company are you using it, etc.
## Help others with issues in GitHub
## Help others with questions in GitHub
You can see <a href="https://github.com/tiangolo/sqlmodel/issues" class="external-link" target="_blank">existing issues</a> and try and help others, most of the times they are questions that you might already know the answer for. 🤓
You can try and help others with their questions in:
* <a href="https://github.com/tiangolo/sqlmodel/discussions/categories/questions?discussions_q=category%3AQuestions+is%3Aunanswered" class="external-link" target="_blank">GitHub Discussions</a>
* <a href="https://github.com/tiangolo/sqlmodel/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aquestion+-label%3Aanswered+" class="external-link" target="_blank">GitHub Issues</a>
In many cases you might already know the answer for those questions. 🤓
Just remember, the most important point is: try to be kind. People come with their frustrations and in many cases don't ask in the best way, but try as best as you can to be kind. 🤗
The idea is for the **SQLModel** community to be kind and welcoming. At the same time, don't accept bullying or disrespectful behavior towards others. We have to take care of each other.
---
Here's how to help others with questions (in discussions or issues):
### Understand the question
* Check if you can understand what is the **purpose** and use case of the person asking.
* Then check if the question (the vast majority are questions) is **clear**.
* In many cases the question asked is about an imaginary solution from the user, but there might be a **better** one. If you can understand the problem and use case better, you might be able to suggest a better **alternative solution**.
* If you can't understand the question, ask for more **details**.
### Reproduce the problem
For most of the cases and most of the questions there's something related to the person's **original code**.
In many cases they will only copy a fragment of the code, but that's not enough to **reproduce the problem**.
* You can ask them to provide a <a href="https://stackoverflow.com/help/minimal-reproducible-example" class="external-link" target="_blank">minimal, reproducible, example</a>, that you can **copy-paste** and run locally to see the same error or behavior they are seeing, or to understand their use case better.
* If you are feeling too generous, you can try to **create an example** like that yourself, just based on the description of the problem. Just have in mind that this might take a lot of time and it might be better to ask them to clarify the problem first.
### Suggest solutions
* After being able to understand the question, you can give them a possible **answer**.
* In many cases, it's better to understand their **underlying problem or use case**, because there might be a better way to solve it than what they are trying to do.
### Ask to close
If they reply, there's a high chance you would have solved their problem, congrats, **you're a hero**! 🦸
* Now, if that solved their problem, you can ask them to:
* In GitHub Discussions: mark the comment as the **answer**.
* In GitHub Issues: **close** the issue**.
## Watch the GitHub repository
You can "watch" SQLModel in GitHub (clicking the "watch" button at the top right): <a href="https://github.com/tiangolo/sqlmodel" class="external-link" target="_blank">https://github.com/tiangolo/sqlmodel</a>. 👀
If you select "Watching" instead of "Releases only" you will receive notifications when someone creates a new issue.
If you select "Watching" instead of "Releases only" you will receive notifications when someone creates a new issue or question. You can also specify that you only want to be notified about new issues, or discussions, or PRs, etc.
Then you can try and help them solve those issues.
Then you can try and help them solve those questions.
## Create issues
## Ask Questions
You can <a href="https://github.com/tiangolo/sqlmodel/issues/new/choose" class="external-link" target="_blank">create a new issue</a> in the GitHub repository, for example to:
You can <a href="https://github.com/tiangolo/sqlmodel/discussions/new?category=questions" class="external-link" target="_blank">create a new question</a> in the GitHub repository, for example to:
* Ask a **question** or ask about a **problem**.
* Suggest a new **feature**.
**Note**: if you create an issue, then I'm going to ask you to also help others. 😉
**Note**: if you do it, then I'm going to ask you to also help others. 😉
## Review Pull Requests
You can help me review pull requests from others.
Again, please try your best to be kind. 🤗
---
Here's what to have in mind and how to review a pull request:
### Understand the problem
* First, make sure you **understand the problem** that the pull request is trying to solve. It might have a longer discussion in a GitHub Discussion or issue.
* There's also a good chance that the pull request is not actually needed because the problem can be solved in a **different way**. Then you can suggest or ask about that.
### Don't worry about style
* Don't worry too much about things like commit message styles, I will squash and merge customizing the commit manually.
* Also don't worry about style rules, there are already automatized tools checking that.
And if there's any other style or consistency need, I'll ask directly for that, or I'll add commits on top with the needed changes.
### Check the code
* Check and read the code, see if it makes sense, **run it locally** and see if it actually solves the problem.
* Then **comment** saying that you did that, that's how I will know you really checked it.
!!! info
Unfortunately, I can't simply trust PRs that just have several approvals.
Several times it has happened that there are PRs with 3, 5 or more approvals, probably because the description is appealing, but when I check the PRs, they are actually broken, have a bug, or don't solve the problem they claim to solve. 😅
So, it's really important that you actually read and run the code, and let me know in the comments that you did. 🤓
* If the PR can be simplified in a way, you can ask for that, but there's no need to be too picky, there might be a lot of subjective points of view (and I will have my own as well 🙈), so it's better if you can focus on the fundamental things.
### Tests
* Help me check that the PR has **tests**.
* Check that the tests **fail** before the PR. 🚨
* Then check that the tests **pass** after the PR. ✅
* Many PRs don't have tests, you can **remind** them to add tests, or you can even **suggest** some tests yourself. That's one of the things that consume most time and you can help a lot with that.
* Then also comment what you tried, that way I'll know that you checked it. 🤓
## Create a Pull Request
@ -86,7 +185,44 @@ You can [contribute](contributing.md){.internal-link target=_blank} to the sourc
* To fix a typo you found on the documentation.
* To propose new documentation sections.
* To fix an existing issue/bug.
* Make sure to add tests.
* To add a new feature.
* Make sure to add tests.
* Make sure to add documentation if it's relevant.
## Help Maintain SQLModel
Help me maintain **SQLModel**! 🤓
There's a lot of work to do, and for most of it, **YOU** can do it.
The main tasks that you can do right now are:
* [Help others with questions in GitHub](#help-others-with-questions-in-github){.internal-link target=_blank} (see the section above).
* [Review Pull Requests](#review-pull-requests){.internal-link target=_blank} (see the section above).
Those two tasks are what **consume time the most**. That's the main work of maintaining SQLModel.
If you can help me with that, **you are helping me maintain SQLModel** and making sure it keeps **advancing faster and better**. 🚀
## Join the chat
Join the 👥 <a href="https://discord.gg/VQjSZaeJmf" class="external-link" target="_blank">FastAPI and Friends Discord chat server</a> 👥 and hang out with others in the community. There's a `#sqlmodel` channel.
!!! tip
For questions, ask them in <a href="https://github.com/tiangolo/sqlmodel/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussions</a>, there's a much better chance you will receive help there.
Use the chat only for other general conversations.
### Don't use the chat for questions
Have in mind that as chats allow more "free conversation", it's easy to ask questions that are too general and more difficult to answer, so, you might not receive answers.
In GitHub, the template will guide you to write the right question so that you can more easily get a good answer, or even solve the problem yourself even before asking. And in GitHub I can make sure I always answer everything, even if it takes some time. I can't personally do that with the chat. 😅
Conversations in the chat are also not as easily searchable as in GitHub, so questions and answers might get lost in the conversation.
On the other side, there are thousands of users in the chat, so there's a high chance you'll find someone to talk to there, almost all the time. 😄
## Sponsor the author

View File

@ -90,4 +90,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -202,4 +202,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -148,4 +148,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -81,4 +81,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -34,4 +34,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -61,4 +61,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@ -94,4 +94,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -94,4 +94,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -94,4 +94,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -97,4 +97,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -94,4 +94,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -97,4 +97,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -97,4 +97,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -100,4 +100,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -89,4 +89,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -217,4 +217,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -130,4 +130,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -130,4 +130,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -130,4 +130,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -38,4 +38,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

View File

@ -49,4 +49,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

@ -139,4 +139,4 @@
</root>
</mxGraphModel>
</diagram>
</mxfile>
</mxfile>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -50,7 +50,7 @@ It combines SQLAlchemy and Pydantic and tries to simplify the code you write as
## Requirements
A recent and currently supported version of Python (right now, <a href="https://www.python.org/downloads/" class="external-link" target="_blank">Python supports versions 3.6 and above</a>).
A recent and currently supported <a href="https://www.python.org/downloads/" class="external-link" target="_blank">version of Python</a>.
As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. They will be automatically installed when you install SQLModel.
@ -68,7 +68,7 @@ Successfully installed sqlmodel
## Example
For an introduction to databases, SQL, and everything else, see the <a href="https://sqlmodel.tiangolo.com" target="_blank">SQLModel documentation</a>.
For an introduction to databases, SQL, and everything else, see the <a href="https://sqlmodel.tiangolo.com/databases/" target="_blank">SQLModel documentation</a>.
Here's a quick example. ✨

View File

@ -72,14 +72,14 @@ class Termynal {
* Initialise the widget, get lines, clear container and start animation.
*/
init() {
/**
/**
* Calculates width and height of Termynal container.
* If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS.
*/
*/
const containerStyle = getComputedStyle(this.container);
this.container.style.width = containerStyle.width !== '0px' ?
this.container.style.width = containerStyle.width !== '0px' ?
containerStyle.width : undefined;
this.container.style.minHeight = containerStyle.height !== '0px' ?
this.container.style.minHeight = containerStyle.height !== '0px' ?
containerStyle.height : undefined;
this.container.setAttribute('data-termynal', '');
@ -138,7 +138,7 @@ class Termynal {
restart.innerHTML = "restart ↻"
return restart
}
generateFinish() {
const finish = document.createElement('a')
finish.onclick = (e) => {
@ -215,7 +215,7 @@ class Termynal {
/**
* Converts line data objects into line elements.
*
*
* @param {Object[]} lineData - Dynamically loaded lines.
* @param {Object} line - Line data object.
* @returns {Element[]} - Array of line elements.
@ -231,7 +231,7 @@ class Termynal {
/**
* Helper function for generating attributes string.
*
*
* @param {Object} line - Line data object.
* @returns {string} - String of attributes.
*/

View File

@ -12,9 +12,9 @@
});
});
</script>
<qa-bot
server="https://tiangolo-sqlmodel.docsqa.jina.ai"
theme="infer"
<qa-bot
server="https://tiangolo-sqlmodel.docsqa.jina.ai"
theme="infer"
title="SQLModel Bot"
description="SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness."
style="font-size: 0.8rem"
@ -28,4 +28,4 @@
</dl>
</template>
</qa-bot>
{%- endblock %}
{%- endblock %}

View File

@ -2,13 +2,81 @@
## Latest Changes
* 👷 Refactor CI artifact upload/download for docs previews. PR [#514](https://github.com/tiangolo/sqlmodel/pull/514) by [@tiangolo](https://github.com/tiangolo).
* 🎨 Update inline source examples, hide `#` in annotations (from MkDocs Material). PR [#677](https://github.com/tiangolo/sqlmodel/pull/677) by [@Matthieu-LAURENT39](https://github.com/Matthieu-LAURENT39).
* ✨ Do not allow invalid combinations of field parameters for columns and relationships, `sa_column` excludes `sa_column_args`, `primary_key`, `nullable`, etc.. PR [#681](https://github.com/tiangolo/sqlmodel/pull/681) by [@tiangolo](https://github.com/tiangolo).
## 0.0.10
### Features
* ✨ Add support for all `Field` parameters from Pydantic `1.9.0` and above, make Pydantic `1.9.0` the minimum required version. PR [#440](https://github.com/tiangolo/sqlmodel/pull/440) by [@daniil-berg](https://github.com/daniil-berg).
### Internal
* 🔧 Adopt Ruff for formatting. PR [#679](https://github.com/tiangolo/sqlmodel/pull/679) by [@tiangolo](https://github.com/tiangolo).
## 0.0.9
### Breaking Changes
* 🗑️ Deprecate Python 3.6 and upgrade Poetry and Poetry Version Plugin. PR [#627](https://github.com/tiangolo/sqlmodel/pull/627) by [@tiangolo](https://github.com/tiangolo).
### Features
* ✨ Raise a more clear error when a type is not valid. PR [#425](https://github.com/tiangolo/sqlmodel/pull/425) by [@ddanier](https://github.com/ddanier).
### Fixes
* 🐛 Fix `AsyncSession` type annotations for `exec()`. PR [#58](https://github.com/tiangolo/sqlmodel/pull/58) by [@Bobronium](https://github.com/Bobronium).
* 🐛 Fix allowing using a `ForeignKey` directly, remove repeated column construction from `SQLModelMetaclass.__init__` and upgrade minimum SQLAlchemy to `>=1.4.36`. PR [#443](https://github.com/tiangolo/sqlmodel/pull/443) by [@daniil-berg](https://github.com/daniil-berg).
* 🐛 Fix enum type checks ordering in `get_sqlalchemy_type`. PR [#669](https://github.com/tiangolo/sqlmodel/pull/669) by [@tiangolo](https://github.com/tiangolo).
* 🐛 Fix SQLAlchemy version 1.4.36 breaks SQLModel relationships (#315). PR [#461](https://github.com/tiangolo/sqlmodel/pull/461) by [@byrman](https://github.com/byrman).
### Upgrades
* ⬆️ Upgrade support for SQLAlchemy 1.4.49, update tests. PR [#519](https://github.com/tiangolo/sqlmodel/pull/519) by [@sandrotosi](https://github.com/sandrotosi).
* ⬆ Raise SQLAlchemy version requirement to at least `1.4.29` (related to #434). PR [#439](https://github.com/tiangolo/sqlmodel/pull/439) by [@daniil-berg](https://github.com/daniil-berg).
### Docs
* 📝 Clarify description of in-memory SQLite database in `docs/tutorial/create-db-and-table.md`. PR [#601](https://github.com/tiangolo/sqlmodel/pull/601) by [@SimonCW](https://github.com/SimonCW).
* 📝 Tweak wording in `docs/tutorial/fastapi/multiple-models.md`. PR [#674](https://github.com/tiangolo/sqlmodel/pull/674) by [@tiangolo](https://github.com/tiangolo).
* ✏️ Fix contributing instructions to run tests, update script name. PR [#634](https://github.com/tiangolo/sqlmodel/pull/634) by [@PookieBuns](https://github.com/PookieBuns).
* 📝 Update link to docs for intro to databases. PR [#593](https://github.com/tiangolo/sqlmodel/pull/593) by [@abenezerBelachew](https://github.com/abenezerBelachew).
* 📝 Update docs, use `offset` in example with `limit` and `where`. PR [#273](https://github.com/tiangolo/sqlmodel/pull/273) by [@jbmchuck](https://github.com/jbmchuck).
* 📝 Fix docs for Pydantic's fields using `le` (`lte` is invalid, use `le` ). PR [#207](https://github.com/tiangolo/sqlmodel/pull/207) by [@jrycw](https://github.com/jrycw).
* 📝 Update outdated link in `docs/db-to-code.md`. PR [#649](https://github.com/tiangolo/sqlmodel/pull/649) by [@MatveyF](https://github.com/MatveyF).
* ✏️ Fix typos found with codespell. PR [#520](https://github.com/tiangolo/sqlmodel/pull/520) by [@kianmeng](https://github.com/kianmeng).
* 📝 Fix typos (duplication) in main page. PR [#631](https://github.com/tiangolo/sqlmodel/pull/631) by [@Mr-DRP](https://github.com/Mr-DRP).
* 📝 Update release notes, add second author to PR. PR [#429](https://github.com/tiangolo/sqlmodel/pull/429) by [@br-follow](https://github.com/br-follow).
* 📝 Update instructions about how to make a foreign key required in `docs/tutorial/relationship-attributes/define-relationships-attributes.md`. PR [#474](https://github.com/tiangolo/sqlmodel/pull/474) by [@jalvaradosegura](https://github.com/jalvaradosegura).
* 📝 Update help SQLModel docs. PR [#548](https://github.com/tiangolo/sqlmodel/pull/548) by [@tiangolo](https://github.com/tiangolo).
* ✏️ Fix typo in internal function name `get_sqlachemy_type()`. PR [#496](https://github.com/tiangolo/sqlmodel/pull/496) by [@cmarqu](https://github.com/cmarqu).
* ⬆ Bump actions/cache from 2 to 3. PR [#497](https://github.com/tiangolo/sqlmodel/pull/497) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ✏️ Fix typo in docs. PR [#446](https://github.com/tiangolo/sqlmodel/pull/446) by [@davidbrochart](https://github.com/davidbrochart).
* ⬆ Bump dawidd6/action-download-artifact from 2.24.0 to 2.24.2. PR [#493](https://github.com/tiangolo/sqlmodel/pull/493) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ✏️ Fix typo in `docs/tutorial/create-db-and-table.md`. PR [#477](https://github.com/tiangolo/sqlmodel/pull/477) by [@FluffyDietEngine](https://github.com/FluffyDietEngine).
* ✏️ Fix small typos in docs. PR [#481](https://github.com/tiangolo/sqlmodel/pull/481) by [@micuffaro](https://github.com/micuffaro).
### Internal
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#672](https://github.com/tiangolo/sqlmodel/pull/672) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
* ⬆ Bump dawidd6/action-download-artifact from 2.24.2 to 2.28.0. PR [#660](https://github.com/tiangolo/sqlmodel/pull/660) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ✅ Refactor OpenAPI FastAPI tests to simplify updating them later, this moves things around without changes. PR [#671](https://github.com/tiangolo/sqlmodel/pull/671) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Bump actions/checkout from 3 to 4. PR [#670](https://github.com/tiangolo/sqlmodel/pull/670) by [@dependabot[bot]](https://github.com/apps/dependabot).
* 🔧 Update mypy config, use `strict = true` instead of manual configs. PR [#428](https://github.com/tiangolo/sqlmodel/pull/428) by [@michaeloliverx](https://github.com/michaeloliverx).
* ⬆️ Upgrade MkDocs Material. PR [#668](https://github.com/tiangolo/sqlmodel/pull/668) by [@tiangolo](https://github.com/tiangolo).
* 🎨 Update docs format and references with pre-commit and Ruff. PR [#667](https://github.com/tiangolo/sqlmodel/pull/667) by [@tiangolo](https://github.com/tiangolo).
* 🎨 Run pre-commit on all files and autoformat. PR [#666](https://github.com/tiangolo/sqlmodel/pull/666) by [@tiangolo](https://github.com/tiangolo).
* 👷 Move to Ruff and add pre-commit. PR [#661](https://github.com/tiangolo/sqlmodel/pull/661) by [@tiangolo](https://github.com/tiangolo).
* 🛠️ Add `CITATION.cff` file for academic citations. PR [#13](https://github.com/tiangolo/sqlmodel/pull/13) by [@sugatoray](https://github.com/sugatoray).
* 👷 Update docs deployments to Cloudflare. PR [#630](https://github.com/tiangolo/sqlmodel/pull/630) by [@tiangolo](https://github.com/tiangolo).
* 👷‍♂️ Upgrade CI for docs. PR [#628](https://github.com/tiangolo/sqlmodel/pull/628) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update CI debug mode with Tmate. PR [#629](https://github.com/tiangolo/sqlmodel/pull/629) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update latest changes token. PR [#616](https://github.com/tiangolo/sqlmodel/pull/616) by [@tiangolo](https://github.com/tiangolo).
* ⬆️ Upgrade analytics. PR [#558](https://github.com/tiangolo/sqlmodel/pull/558) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Update new issue chooser to point to GitHub Discussions. PR [#546](https://github.com/tiangolo/sqlmodel/pull/546) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Add template for GitHub Discussion questions and update issues template. PR [#544](https://github.com/tiangolo/sqlmodel/pull/544) by [@tiangolo](https://github.com/tiangolo).
* 👷 Refactor CI artifact upload/download for docs previews. PR [#514](https://github.com/tiangolo/sqlmodel/pull/514) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Bump actions/cache from 2 to 3. PR [#497](https://github.com/tiangolo/sqlmodel/pull/497) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump dawidd6/action-download-artifact from 2.24.0 to 2.24.2. PR [#493](https://github.com/tiangolo/sqlmodel/pull/493) by [@dependabot[bot]](https://github.com/apps/dependabot).
* 🔧 Update Smokeshow coverage threshold. PR [#487](https://github.com/tiangolo/sqlmodel/pull/487) by [@tiangolo](https://github.com/tiangolo).
* 👷 Move from Codecov to Smokeshow. PR [#486](https://github.com/tiangolo/sqlmodel/pull/486) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Bump actions/setup-python from 2 to 4. PR [#411](https://github.com/tiangolo/sqlmodel/pull/411) by [@dependabot[bot]](https://github.com/apps/dependabot).
@ -28,7 +96,7 @@
### Fixes
* 🐛 Fix auto detecting and setting `nullable`, allowing overrides in field. PR [#423](https://github.com/tiangolo/sqlmodel/pull/423) by [@JonasKs](https://github.com/JonasKs).
* 🐛 Fix auto detecting and setting `nullable`, allowing overrides in field. PR [#423](https://github.com/tiangolo/sqlmodel/pull/423) by [@JonasKs](https://github.com/JonasKs) and [@br-follow](https://github.com/br-follow).
* ♻️ Update `expresion.py`, sync from Jinja2 template, implement `inherit_cache` to solve errors like: `SAWarning: Class SelectOfScalar will not make use of SQL compilation caching`. PR [#422](https://github.com/tiangolo/sqlmodel/pull/422) by [@tiangolo](https://github.com/tiangolo).
### Docs

View File

@ -36,7 +36,7 @@ When we create a new `Hero` instance, we don't set the `id`:
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:23-26]!}
# Code below ommitted 👇
# Code below omitted 👇
```
<details>
@ -125,7 +125,7 @@ We can verify by creating a session using a `with` block and adding the objects.
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:23-41]!}
# Code below ommitted 👇
# Code below omitted 👇
```
<details>
@ -198,9 +198,9 @@ INFO Engine COMMIT
// And now our prints
After committing the session
Hero 1:
Hero 2:
Hero 3:
Hero 1:
Hero 2:
Hero 3:
// What is happening here? 😱
```
@ -238,7 +238,7 @@ To confirm and understand how this **automatic expiration and refresh** of data
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:33-58]!}
# Code below ommitted 👇
# Code below omitted 👇
```
<details>
@ -271,21 +271,21 @@ Let's see how it works:
```console
$ python app.py
// Output above ommitted 👆
// Output above omitted 👆
// After committing, the objects are expired and have no values
After committing the session
Hero 1:
Hero 2:
Hero 3:
Hero 1:
Hero 2:
Hero 3:
// Now we will access an attribute like the ID, this is the first print
After committing the session, show IDs
// Notice that before printing the first ID, the Session makes the Engine go to the database to refresh the data 🤓
INFO Engine BEGIN (implicit)
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00017s] (1,)
@ -293,8 +293,8 @@ INFO Engine [generated in 0.00017s] (1,)
Hero 1 ID: 1
// Before the next print, refresh the data for the second object
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.001245s ago] (2,)
@ -302,8 +302,8 @@ INFO Engine [cached since 0.001245s ago] (2,)
Hero 2 ID: 2
// Before the third print, refresh its data
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.002215s ago] (3,)
@ -335,7 +335,7 @@ You can do that too with `session.refresh(object)`:
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:33-67]!}
# Code below ommitted 👇
# Code below omitted 👇
```
<details>
@ -362,23 +362,23 @@ Here's how the output would look like:
```console
$ python app.py
// Output above ommitted 👆
// Output above omitted 👆
// The first refresh
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00024s] (1,)
// The second refresh
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.001487s ago] (2,)
// The third refresh
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.002377s ago] (3,)
@ -427,7 +427,7 @@ And the output shows again the same data:
```console
$ python app.py
// Output above ommitted 👆
// Output above omitted 👆
// By finishing the with block, the Session is closed, including a rollback of any pending transaction that could have been there and was not committed
INFO Engine ROLLBACK
@ -468,12 +468,12 @@ INFO Engine PRAGMA main.table_info("hero")
INFO Engine [raw sql] ()
INFO Engine PRAGMA temp.table_info("hero")
INFO Engine [raw sql] ()
INFO Engine
INFO Engine
CREATE TABLE hero (
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
PRIMARY KEY (id)
)
@ -497,23 +497,23 @@ INFO Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?)
INFO Engine [cached since 0.001483s ago] ('Rusty-Man', 'Tommy Sharp', 48)
INFO Engine COMMIT
After committing the session
Hero 1:
Hero 2:
Hero 3:
Hero 1:
Hero 2:
Hero 3:
After committing the session, show IDs
INFO Engine BEGIN (implicit)
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00029s] (1,)
Hero 1 ID: 1
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.002132s ago] (2,)
Hero 2 ID: 2
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.003367s ago] (3,)
Hero 3 ID: 3
@ -521,16 +521,16 @@ After committing the session, show names
Hero 1 name: Deadpond
Hero 2 name: Spider-Boy
Hero 3 name: Rusty-Man
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00025s] (1,)
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.001583s ago] (2,)
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.002722s ago] (3,)
After refreshing the heroes

View File

@ -168,7 +168,7 @@ Let's assume that now the file structure is:
### Circular Imports and Type Annotations
The problem with circular imports is that Python can't resolve them at <abbr title="While it is executing the program, as oposed to the code as just text in a file stored on disk.">*runtime*</abbr>.
The problem with circular imports is that Python can't resolve them at <abbr title="While it is executing the program, as opposed to the code as just text in a file stored on disk.">*runtime*</abbr>.
But when using Python **type annotations** it's very common to need to declare the type of some variables with classes imported from other files.

View File

@ -159,7 +159,7 @@ As the `Hero` class model now has a field (column, attribute) `team_id`, we can
We haven't committed this hero to the database yet, but there are already a couple of things to pay **attention** to.
If the database already had some teams, we wouldn't even know **what is the ID** that is going to be automatically assigned to each team by the database, for example, we couldn't just guess `1` or `2`.
If the database already had some teams, we wouldn't even know **what is the ID** that is going to be automatically assigned to each team by the database, for example, we couldn't just guess `1` or `2`.
But once the team is created and committed to the database, we can access the object's `id` field to get that ID.
@ -171,8 +171,8 @@ That line alone would generate an output of:
```
INFO Engine BEGIN (implicit)
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
WHERE team.id = ?
INFO Engine [generated in 0.00025s] (2,)
```
@ -199,8 +199,8 @@ Let's now create two more heroes:
When creating `hero_rusty_man`, we are accessing `team_preventers.id`, so that will also trigger a refresh of its data, generating an output of:
```
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
WHERE team.id = ?
INFO Engine [cached since 0.001795s ago] (1,)
```
@ -256,18 +256,18 @@ $ python app.py
INFO Engine BEGIN (implicit)
// Refresh the first hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00021s] (1,)
// Refresh the second hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.001575s ago] (2,)
// Refresh the third hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.002518s ago] (3,)

View File

@ -106,7 +106,7 @@ This is the same model we have been using up to now, we are just adding the new
Most of that should look familiar:
The column will be named `team_id`. It will be an integer, and it could be `NULL` in the database (or `None` in Python), becase there could be some heroes that don't belong to any team.
The column will be named `team_id`. It will be an integer, and it could be `NULL` in the database (or `None` in Python), because there could be some heroes that don't belong to any team.
We add a default of `None` to the `Field()` so we don't have to explicitly pass `team_id=None` when creating a hero.
@ -191,24 +191,24 @@ INFO Engine PRAGMA temp.table_info("hero")
INFO Engine [raw sql] ()
// Create the tables
INFO Engine
INFO Engine
CREATE TABLE team (
id INTEGER,
name VARCHAR NOT NULL,
headquarters VARCHAR NOT NULL,
id INTEGER,
name VARCHAR NOT NULL,
headquarters VARCHAR NOT NULL,
PRIMARY KEY (id)
)
INFO Engine [no key 0.00010s] ()
INFO Engine
INFO Engine
CREATE TABLE hero (
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
team_id INTEGER,
PRIMARY KEY (id),
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
team_id INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(team_id) REFERENCES team (id)
)
@ -229,9 +229,9 @@ So, the first SQL could also be written as:
```SQL
CREATE TABLE team (
id INTEGER,
name TEXT NOT NULL,
headquarters TEXT NOT NULL,
id INTEGER,
name TEXT NOT NULL,
headquarters TEXT NOT NULL,
PRIMARY KEY (id)
)
```
@ -240,12 +240,12 @@ And the second table could be written as:
```SQL hl_lines="8"
CREATE TABLE hero (
id INTEGER,
name TEXT NOT NULL,
secret_name TEXT NOT NULL,
age INTEGER,
team_id INTEGER,
PRIMARY KEY (id),
id INTEGER,
name TEXT NOT NULL,
secret_name TEXT NOT NULL,
age INTEGER,
team_id INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(team_id) REFERENCES team (id)
)
```

View File

@ -203,8 +203,8 @@ $ python app.py
// Previous output omitted 😉
// Get the heroes with their teams
2021-08-09 08:55:50,682 INFO sqlalchemy.engine.Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
FROM hero, team
2021-08-09 08:55:50,682 INFO sqlalchemy.engine.Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
FROM hero, team
WHERE hero.team_id = team.id
2021-08-09 08:55:50,682 INFO sqlalchemy.engine.Engine [no key 0.00015s] ()
@ -323,7 +323,7 @@ $ python app.py
// Previous output omitted 😉
// Select using a JOIN with automatic ON
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
FROM hero JOIN team ON team.id = hero.team_id
INFO Engine [no key 0.00032s] ()
@ -458,7 +458,7 @@ $ python app.py
// Previous output omitted 😉
// SELECT using LEFT OUTER JOIN
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
FROM hero LEFT OUTER JOIN team ON team.id = hero.team_id
INFO Engine [no key 0.00051s] ()
@ -522,9 +522,9 @@ If we run that, it would output:
$ python app.py
// Select only the hero data
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
// But still join with the team table
FROM hero JOIN team ON team.id = hero.team_id
FROM hero JOIN team ON team.id = hero.team_id
// And filter with WHERE to get only the Preventers
WHERE team.name = ?
INFO Engine [no key 0.00066s] ('Preventers',)
@ -564,9 +564,9 @@ And if we run that, it will output:
$ python app.py
// Select the hero and the team data
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
// Join the hero with the team table
FROM hero JOIN team ON team.id = hero.team_id
FROM hero JOIN team ON team.id = hero.team_id
// Filter with WHERE to get only Preventers
WHERE team.name = ?
INFO Engine [no key 0.00018s] ('Preventers',)

View File

@ -56,7 +56,7 @@ We can simply set the `team_id` to `None`, and now it doesn't have a connection
# Code above omitted 👆
{!./docs_src/tutorial/connect/delete/tutorial001.py[ln:31-32]!}
# Previous code here omitted 👈
{!./docs_src/tutorial/connect/delete/tutorial001.py[ln:68-72]!}
@ -94,8 +94,8 @@ INFO Engine COMMIT
// Automatically start a new transaction
INFO Engine BEGIN (implicit)
// Refresh the hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.1661s ago] (3,)

View File

@ -56,7 +56,7 @@ Doing it is just like updating any other field:
# Code above omitted 👆
{!./docs_src/tutorial/connect/update/tutorial001.py[ln:31-32]!}
# Previous code here omitted 👈
{!./docs_src/tutorial/connect/update/tutorial001.py[ln:62-66]!}
@ -94,8 +94,8 @@ INFO Engine COMMIT
// Automatically start a new transaction
INFO Engine BEGIN (implicit)
// Refresh the hero data
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.08837s ago] (3,)

View File

@ -164,6 +164,6 @@ Of course, you can also go and take a full SQL course or read a book about SQL,
We saw how to interact with SQLite databases in files using **DB Browser for SQLite** in a visual user interface.
We also saw how to use it to write some SQL directly to the SQLite database. This will be useful to verify the data in the database is looking correclty, to debug, etc.
We also saw how to use it to write some SQL directly to the SQLite database. This will be useful to verify the data in the database is looking correctly, to debug, etc.
In the next chapters we will start using **SQLModel** to interact with the database, and we will continue to use **DB Browser for SQLite** at the same time to look at the database underneath. 🔍

View File

@ -220,7 +220,7 @@ Each supported database has it's own URL type. For example, for **SQLite** it is
* `sqlite:///databases/local/application.db`
* `sqlite:///db.sqlite`
For SQLAlchemy, there's also a special one, which is a database all *in memory*, this means that it is deleted after the program terminates, and it's also very fast:
SQLite supports a special database that lives all *in memory*. Hence, it's very fast, but be careful, the database gets deleted after the program terminates. You can specify this in-memory database by using just two slash characters (`//`) and no file name:
* `sqlite://`
@ -422,15 +422,15 @@ INFO Engine PRAGMA main.table_info("hero")
INFO Engine [raw sql] ()
INFO Engine PRAGMA temp.table_info("hero")
INFO Engine [raw sql] ()
INFO Engine
INFO Engine
// Finally, the glorious SQL to create the table ✨
CREATE TABLE hero (
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
PRIMARY KEY (id)
)

View File

@ -108,8 +108,8 @@ $ python app.py
// The SELECT with WHERE
INFO Engine BEGIN (implicit)
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.name = ?
INFO Engine [no key 0.00011s] ('Spider-Youngster',)
@ -272,8 +272,8 @@ $ python app.py
INFO Engine BEGIN (implicit)
// SQL to search for the hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.name = ?
INFO Engine [no key 0.00013s] ('Spider-Youngster',)
```

View File

@ -42,7 +42,7 @@ We want to allow clients to set different `offset` and `limit` values.
But we don't want them to be able to set a `limit` of something like `9999`, that's over `9000`! 😱
So, to prevent it, we add additional validation to the `limit` query parameter, declaring that it has to be **l**ess **t**han or **e**qual to `100` with `lte=100`.
So, to prevent it, we add additional validation to the `limit` query parameter, declaring that it has to be **l**ess than or **e**qual to `100` with `le=100`.
This way, a client can decide to take fewer heroes if they want, but not more.

View File

@ -53,11 +53,11 @@ Here's the weird thing, the `id` currently seems also "optional". 🤔
This is because in our **SQLModel** class we declare the `id` with `Optional[int]`, because it could be `None` in memory until we save it in the database and we finally get the actual ID.
But in the responses, we would always send a model from the database, and it would **always have an ID**. So the `id` in the responses could be declared as required too.
But in the responses, we always send a model from the database, so it **always has an ID**. So the `id` in the responses can be declared as required.
This would mean that our application is making the compromise with the clients that if it sends a hero, it would for sure have an `id` with a value, it would not be `None`.
This means that our application is making the promise to the clients that if it sends a hero, it will for sure have an `id` with a value, it will not be `None`.
### Why Is it Important to Compromise with the Responses
### Why Is it Important to Have a Contract for Responses
The ultimate goal of an API is for some **clients to use it**.

View File

@ -84,7 +84,7 @@ In this case, we used `response_model=TeamRead` and `response_model=HeroRead`, s
# Code here omitted 👈
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:159-164]!}
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:158-163]!}
# Code below omitted 👇
```
@ -234,7 +234,7 @@ In the case of the hero, this tells FastAPI to extract the `team` too. And in th
# Code here omitted 👈
{!./docs_src/tutorial/fastapi/relationships/tutorial001.py[ln:168-173]!}
{!./docs_src/tutorial/fastapi/relationships/tutorial001.py[ln:167-172]!}
# Code below omitted 👇
```

View File

@ -177,7 +177,7 @@ And then we remove the previous `with` block with the old **session**.
# Code here omitted 👈
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:55-107]!}
{!./docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py[ln:55-106]!}
```
<details>

View File

@ -92,7 +92,7 @@ These are equivalent and very similar to the **path operations** for the **heroe
```Python hl_lines="3-9 12-20 23-28 31-47 50-57"
# Code above omitted 👆
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:139-193]!}
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:138-192]!}
# Code below omitted 👇
```

View File

@ -82,7 +82,7 @@ But now, we need to deal with a bit of logistics and details we are not paying a
This test looks fine, but there's a problem.
If we run it, it will use the same **production database** that we are using to store our very important **heroes**, and we will end up adding unnecesary data to it, or even worse, in future tests we could end up removing production data.
If we run it, it will use the same **production database** that we are using to store our very important **heroes**, and we will end up adding unnecessary data to it, or even worse, in future tests we could end up removing production data.
So, we should use an independent **testing database**, just for the tests.

View File

@ -64,15 +64,13 @@ $ cd sqlmodel-tutorial
Make sure you have an officially supported version of Python.
Currently it is **Python 3.6** and above (Python 3.5 was already deprecated).
You can check which version you have with:
<div class="termy">
```console
$ python3 --version
Python 3.6.9
Python 3.11
```
</div>
@ -84,8 +82,6 @@ You might want to try with the specific versions, for example with:
* `python3.10`
* `python3.9`
* `python3.8`
* `python3.7`
* `python3.6`
The code would look like this:
@ -136,10 +132,10 @@ Here are the commands you could use:
<div class="termy">
```console
// Remember that you might need to use python3.9 or similar 💡
// Remember that you might need to use python3.9 or similar 💡
// Create the virtual environment using the module "venv"
$ python3 -m venv env
// ...here it creates the virtual enviroment in the directory "env"
// ...here it creates the virtual environment in the directory "env"
// Activate the virtual environment
$ source ./env/bin/activate
// Verify that the virtual environment is active
@ -161,7 +157,7 @@ Here are the commands you could use:
```console
// Create the virtual environment using the module "venv"
# >$ python3 -m venv env
// ...here it creates the virtual enviroment in the directory "env"
// ...here it creates the virtual environment in the directory "env"
// Activate the virtual environment
# >$ .\env\Scripts\Activate.ps1
// Verify that the virtual environment is active

View File

@ -342,10 +342,10 @@ $ python app.py
// Create the table
CREATE TABLE hero (
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
PRIMARY KEY (id)
)
@ -353,8 +353,8 @@ CREATE TABLE hero (
CREATE INDEX ix_hero_name ON hero (name)
// The SELECT with WHERE looks the same
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.name = ?
INFO Engine [no key 0.00014s] ('Deadpond',)

View File

@ -171,7 +171,7 @@ The first step is to import the `Session` class:
```Python hl_lines="3"
{!./docs_src/tutorial/insert/tutorial001.py[ln:1-3]!}
# Code below ommitted 👇
# Code below omitted 👇
```
<details>

View File

@ -93,7 +93,7 @@ $ python app.py
// Previous output omitted 🙈
// Select with LIMIT
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
LIMIT ? OFFSET ?
INFO Engine [no key 0.00014s] (3, 0)
@ -165,7 +165,7 @@ $python app.py
// Previous output omitted 🙈
// Select with LIMIT and OFFSET
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
LIMIT ? OFFSET ?
INFO Engine [no key 0.00020s] (3, 3)
@ -221,7 +221,7 @@ $ python app.py
// Previous output omitted 🙈
// Select last batch with LIMIT and OFFSET
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
LIMIT ? OFFSET ?
INFO Engine [no key 0.00038s] (3, 6)
@ -241,7 +241,7 @@ You probably noticed the new SQL keywords `LIMIT` and `OFFSET`.
You can use them in SQL, at the end of the other parts:
```SQL
SELECT id, name, secret_name, age
SELECT id, name, secret_name, age
FROM hero
LIMIT 3 OFFSET 6
```
@ -271,11 +271,11 @@ Of course, you can also combine `.limit()` and `.offset()` with `.where()` and o
</details>
## Run the Program with Limit and Where on the Command Line
## Run the Program with Limit, Offset, and Where on the Command Line
If we run it on the command line, it will find all the heroes in the database with an age above 32. That would normally be 4 heroes.
But we are limiting the results to only get the first 3:
But we are starting to include after an offset of 1 (so we don't count the first one), and we are limiting the results to only get the first 2 after that:
<div class="termy">
@ -284,18 +284,17 @@ $ python app.py
// Previous output omitted 🙈
// Select with WHERE and LIMIT
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
// Select with WHERE and LIMIT and OFFSET
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.age > ?
LIMIT ? OFFSET ?
INFO Engine [no key 0.00022s] (32, 3, 0)
INFO Engine [no key 0.00022s] (32, 2, 1)
// Print the heroes received, only 3
// Print the heroes received, only 2
[
Hero(age=35, secret_name='Trevor Challa', id=5, name='Black Lion'),
Hero(age=36, secret_name='Steve Weird', id=6, name='Dr. Weird'),
Hero(age=48, secret_name='Tommy Sharp', id=3, name='Rusty-Man')
Hero(age=36, id=6, name='Dr. Weird', secret_name='Steve Weird'),
Hero(age=48, id=3, name='Rusty-Man', secret_name='Tommy Sharp')
]
```

View File

@ -122,16 +122,16 @@ INFO Engine COMMIT
// Automatically start a new transaction
INFO Engine BEGIN (implicit)
// Refresh the data
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00019s] (1,)
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.001959s ago] (2,)
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.003215s ago] (3,)
@ -139,8 +139,8 @@ INFO Engine [cached since 0.003215s ago] (3,)
Deadpond: name='Deadpond' age=None id=1 secret_name='Dive Wilson'
// Accessing the .team attribute triggers a refresh
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
INFO Engine [generated in 0.00025s] (1,)
@ -151,8 +151,8 @@ Deadpond teams: [Team(id=1, name='Z-Force', headquarters='Sister Margarets Ba
Rusty-Man: name='Rusty-Man' age=48 id=2 secret_name='Tommy Sharp'
// Accessing the .team attribute triggers a refresh
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
INFO Engine [cached since 0.001716s ago] (2,)
@ -163,8 +163,8 @@ Rusty-Man Teams: [Team(id=2, name='Preventers', headquarters='Sharp Tower')]
Spider-Boy: name='Spider-Boy' age=None id=3 secret_name='Pedro Parqueador'
// Accessing the .team attribute triggers a refresh
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
INFO Engine [cached since 0.002739s ago] (3,)
@ -179,4 +179,4 @@ INFO Engine ROLLBACK
## Recap
After setting up the model link, using it with **relationship attributes** is fairly straighforward, just Python objects. ✨
After setting up the model link, using it with **relationship attributes** is fairly straightforward, just Python objects. ✨

View File

@ -151,35 +151,35 @@ $ python app.py
// Boilerplate omitted 😉
INFO Engine
INFO Engine
CREATE TABLE team (
id INTEGER,
name VARCHAR NOT NULL,
headquarters VARCHAR NOT NULL,
id INTEGER,
name VARCHAR NOT NULL,
headquarters VARCHAR NOT NULL,
PRIMARY KEY (id)
)
INFO Engine [no key 0.00033s] ()
INFO Engine
INFO Engine
CREATE TABLE hero (
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
PRIMARY KEY (id)
)
INFO Engine [no key 0.00016s] ()
INFO Engine
INFO Engine
// Our shinny new link table ✨
CREATE TABLE heroteamlink (
team_id INTEGER,
hero_id INTEGER,
PRIMARY KEY (team_id, hero_id),
FOREIGN KEY(team_id) REFERENCES team (id),
team_id INTEGER,
hero_id INTEGER,
PRIMARY KEY (team_id, hero_id),
FOREIGN KEY(team_id) REFERENCES team (id),
FOREIGN KEY(hero_id) REFERENCES hero (id)
)

View File

@ -165,16 +165,16 @@ INFO Engine COMMIT
INFO Engine BEGIN (implicit)
// Automatically fetch the data on attribute access
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
WHERE team.id = ?
INFO Engine [generated in 0.00028s] (1,)
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
FROM heroteamlink
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
FROM heroteamlink
WHERE ? = heroteamlink.team_id
INFO Engine [generated in 0.00026s] (1,)
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00024s] (1,)
@ -182,12 +182,12 @@ INFO Engine [generated in 0.00024s] (1,)
Z-Force hero: name='Deadpond' age=None id=1 secret_name='Dive Wilson' is training: False
// Automatically fetch the data on attribute access
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
WHERE team.id = ?
INFO Engine [cached since 0.008822s ago] (2,)
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
FROM heroteamlink
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
FROM heroteamlink
WHERE ? = heroteamlink.team_id
INFO Engine [cached since 0.005778s ago] (2,)
@ -195,8 +195,8 @@ INFO Engine [cached since 0.005778s ago] (2,)
Preventers hero: name='Deadpond' age=None id=1 secret_name='Dive Wilson' is training: True
// Automatically fetch the data on attribute access
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.004196s ago] (2,)
@ -204,8 +204,8 @@ INFO Engine [cached since 0.004196s ago] (2,)
Preventers hero: name='Spider-Boy' age=None id=2 secret_name='Pedro Parqueador' is training: True
// Automatically fetch the data on attribute access
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.006005s ago] (3,)
@ -253,14 +253,14 @@ $ python app.py
INFO Engine BEGIN (implicit)
// Select the hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.name = ?
INFO Engine [no key 0.00014s] ('Spider-Boy',)
// Select the team
INFO Engine SELECT team.id, team.name, team.headquarters
FROM team
INFO Engine SELECT team.id, team.name, team.headquarters
FROM team
WHERE team.name = ?
INFO Engine [no key 0.00012s] ('Z-Force',)
@ -269,18 +269,18 @@ INFO Engine INSERT INTO heroteamlink (team_id, hero_id, is_training) VALUES (?,
INFO Engine [generated in 0.00023s] (1, 2, 1)
// Automatically refresh the data on attribute access
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
FROM heroteamlink
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
FROM heroteamlink
WHERE ? = heroteamlink.team_id
INFO Engine [cached since 0.01514s ago] (1,)
INFO Engine COMMIT
INFO Engine BEGIN (implicit)
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.08953s ago] (2,)
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
FROM heroteamlink
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
FROM heroteamlink
WHERE ? = heroteamlink.hero_id
INFO Engine [generated in 0.00018s] (2,)
@ -291,18 +291,18 @@ Updated Spider-Boy's Teams: [
]
// Automatically refresh team data on attribute access
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
WHERE team.id = ?
INFO Engine [cached since 0.1084s ago] (1,)
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
FROM heroteamlink
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
FROM heroteamlink
WHERE ? = heroteamlink.team_id
INFO Engine [cached since 0.1054s ago] (1,)
// Print team hero links
Z-Force heroes: [
HeroTeamLink(team_id=1, is_training=False, hero_id=1),
HeroTeamLink(team_id=1, is_training=False, hero_id=1),
HeroTeamLink(team_id=1, is_training=True, hero_id=2)
]
```
@ -350,8 +350,8 @@ $ python app.py
// Previous output omitted 🙈
// Automatically fetch team data on attribute access
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
WHERE team.id = ?
INFO Engine [generated in 0.00015s] (2,)
@ -366,16 +366,16 @@ INFO Engine COMMIT
INFO Engine BEGIN (implicit)
// Automatically fetch data on attribute access
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.2004s ago] (2,)
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
FROM heroteamlink
INFO Engine SELECT heroteamlink.team_id AS heroteamlink_team_id, heroteamlink.hero_id AS heroteamlink_hero_id, heroteamlink.is_training AS heroteamlink_is_training
FROM heroteamlink
WHERE ? = heroteamlink.hero_id
INFO Engine [cached since 0.1005s ago] (2,)
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
WHERE team.id = ?
INFO Engine [cached since 0.09707s ago] (2,)
@ -383,8 +383,8 @@ INFO Engine [cached since 0.09707s ago] (2,)
Spider-Boy team: headquarters='Sharp Tower' id=2 name='Preventers' is training: False
// Automatically fetch data on attribute access
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
WHERE team.id = ?
INFO Engine [cached since 0.2097s ago] (1,)

View File

@ -115,12 +115,12 @@ INFO Engine COMMIT
INFO Engine BEGIN (implicit)
// Automatically refresh the data while accessing the attribute .teams
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00044s] (3,)
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
INFO Engine [cached since 0.1648s ago] (3,)
@ -131,8 +131,8 @@ Updated Spider-Boy's Teams: [
]
// Automatically refresh the data while accessing the attribute .heores
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero, heroteamlink
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero, heroteamlink
WHERE ? = heroteamlink.team_id AND hero.id = heroteamlink.hero_id
INFO Engine [cached since 0.1499s ago] (1,)
@ -208,12 +208,12 @@ INFO Engine COMMIT
INFO Engine BEGIN (implicit)
// Automatically refresh the data while accessing the attribute .heroes
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
WHERE team.id = ?
INFO Engine [generated in 0.00029s] (1,)
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero, heroteamlink
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero, heroteamlink
WHERE ? = heroteamlink.team_id AND hero.id = heroteamlink.hero_id
INFO Engine [cached since 0.5625s ago] (1,)
@ -223,12 +223,12 @@ Reverted Z-Force's heroes: [
]
// Automatically refresh the data while accessing the attribute .teams
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.4209s ago] (3,)
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
INFO Engine [cached since 0.5842s ago] (3,)

View File

@ -12,7 +12,7 @@ Let's see the utilities to read a single row.
## Continue From Previous Code
We'll continue with the same examples we have been using in the previous chapters to create and select data and we'll keep udpating them.
We'll continue with the same examples we have been using in the previous chapters to create and select data and we'll keep updating them.
<details>
<summary>👀 Full file preview</summary>
@ -86,8 +86,8 @@ $ python app.py
// Some boilerplate output omitted 😉
// The SELECT with WHERE
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.age <= ?
INFO Engine [no key 0.00021s] (35,)
@ -132,8 +132,8 @@ $ python app.py
// Some boilerplate output omitted 😉
// The SELECT with WHERE
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.age <= ?
INFO Engine [no key 0.00021s] (35,)
@ -180,8 +180,8 @@ $ python app.py
// Some boilerplate output omitted 😉
// The SELECT with WHERE
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.name = ?
INFO Engine [no key 0.00015s] ('Deadpond',)
@ -203,8 +203,8 @@ $ python app.py
// Some boilerplate output omitted 😉
// The SELECT with WHERE
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.name = ?
INFO Engine [no key 0.00015s] ('Deadpond',)
@ -274,8 +274,8 @@ $ python app.py
// Some boilerplate output omitted 😉
// SELECT with WHERE
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.age < ?
INFO Engine [no key 0.00014s] (25,)
@ -370,8 +370,8 @@ $ python app.py
// Some boilerplate output omitted 😉
// SELECT with WHERE
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00021s] (1,)
@ -413,8 +413,8 @@ $ python app.py
// SELECT with WHERE
INFO Engine BEGIN (implicit)
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00024s] (9001,)

View File

@ -123,7 +123,7 @@ Now let's update **Spider-Boy**, removing him from the team by setting `hero_spi
</details>
The first important thing is, we *haven't commited* the hero yet, so accessing the list of heroes would not trigger an automatic refresh.
The first important thing is, we *haven't committed* the hero yet, so accessing the list of heroes would not trigger an automatic refresh.
But in our code, in this exact point in time, we already said that **Spider-Boy** is no longer part of the **Preventers**. 🔥
@ -144,10 +144,10 @@ But now, what happens when we print the `preventers_team.heroes`?
``` hl_lines="3"
Preventers Team Heroes again: [
Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2),
Hero(name='Spider-Boy', age=None, id=3, secret_name='Pedro Parqueador', team_id=2, team=None),
Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2),
Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2),
Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2),
Hero(name='Spider-Boy', age=None, id=3, secret_name='Pedro Parqueador', team_id=2, team=None),
Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2),
Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2),
Hero(name='Captain North America', age=93, id=8, secret_name='Esteban Rogelios', team_id=2)
]
```
@ -182,15 +182,15 @@ Now, if we commit it and print again:
When we access `preventers_team.heroes` after the `commit`, that triggers a refresh, so we get the latest list, without **Spider-Boy**, so that's fine again:
```
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age, hero.team_id AS hero_team_id
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age, hero.team_id AS hero_team_id
FROM hero
WHERE ? = hero.team_id
2021-08-13 11:15:24,658 INFO sqlalchemy.engine.Engine [cached since 0.1924s ago] (2,)
Preventers Team Heroes after commit: [
Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2),
Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2),
Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2),
Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2),
Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2),
Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2),
Hero(name='Captain North America', age=93, id=8, secret_name='Esteban Rogelios', team_id=2)
]
```
@ -260,9 +260,9 @@ That second print would output:
```
Preventers Team Heroes again: [
Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2),
Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2),
Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2),
Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2),
Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2),
Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2),
Hero(name='Captain North America', age=93, id=8, secret_name='Esteban Rogelios', team_id=2)
]
```

View File

@ -115,9 +115,7 @@ This means that this attribute could be `None`, or it could be a full `Team` obj
This is because the related **`team_id` could also be `None`** (or `NULL` in the database).
If it was required for a `Hero` instance to belong to a `Team`, then the `team_id` would be `int` instead of `Optional[int]`.
And the `team` attribute would be a `Team` instead of `Optional[Team]`.
If it was required for a `Hero` instance to belong to a `Team`, then the `team_id` would be `int` instead of `Optional[int]`, its `Field` would be `Field(foreign_key="team.id")` instead of `Field(default=None, foreign_key="team.id")` and the `team` attribute would be a `Team` instead of `Optional[Team]`.
## Relationship Attributes With Lists

View File

@ -52,7 +52,7 @@ With what we have learned **up to now**, we could use a `select()` statement, th
## Get Relationship Team - New Way
But now that we have the **relationship attributes**, we can just access them, and **SQLModel** (actually SQLAlchemy) will go and fetch the correspoinding data from the database, and make it available in the attribute. ✨
But now that we have the **relationship attributes**, we can just access them, and **SQLModel** (actually SQLAlchemy) will go and fetch the corresponding data from the database, and make it available in the attribute. ✨
So, the highlighted block above, has the same results as the block below:
@ -111,8 +111,8 @@ That would print a list with all the heroes in the Preventers team:
$ python app.py
// Automatically fetch the heroes
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age, hero.team_id AS hero_team_id
FROM hero
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age, hero.team_id AS hero_team_id
FROM hero
WHERE ? = hero.team_id
INFO Engine [cached since 0.8774s ago] (2,)

View File

@ -190,7 +190,7 @@ First we have to import `select` from `sqlmodel` at the top of the file:
```Python hl_lines="3"
{!./docs_src/tutorial/select/tutorial001.py[ln:1-3]!}
# More code below ommitted 👇
# More code below omitted 👇
```
<details>
@ -274,7 +274,7 @@ This `session.exec(statement)` will generate this output:
```
INFO Engine BEGIN (implicit)
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine [no key 0.00032s] ()
```
@ -455,7 +455,7 @@ In this chapter we are touching some of them.
### SQLModel's `select`
When importing from `sqlmodel` the `select()` function, you are using **SQLModel**'s version of `select`.
When importing from `sqlmodel` the `select()` function, you are using **SQLModel**'s version of `select`.
SQLAchemy also has it's own `select`, and SQLModel's `select` uses SQLAlchemy's `select` internally.
@ -472,7 +472,7 @@ SQLAlchemy's own `Session` has a method `session.execute()`. It doesn't have a `
If you see SQLAlchemy tutorials, they will always use `session.execute()`.
**SQLModel**'s own `Session` inherits directly from SQLAlchemy's `Session`, and adds this additonal method `session.exec()`. Underneath, it uses the same `session.execute()`.
**SQLModel**'s own `Session` inherits directly from SQLAlchemy's `Session`, and adds this additional method `session.exec()`. Underneath, it uses the same `session.execute()`.
But `session.exec()` does several **tricks** combined with the tricks in `session()` to give you the **best editor support**, with **autocompletion** and **inline errors** everywhere, even after getting data from a select. ✨

View File

@ -132,8 +132,8 @@ $ python app.py
// Some boilerplate and previous output omitted 😉
// The SELECT with WHERE
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.name = ?
INFO Engine [no key 0.00017s] ('Spider-Boy',)
@ -275,8 +275,8 @@ $ python app.py
// Previous output omitted 🙈
// The SQL to SELECT the fresh hero data
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00018s] (2,)
```

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