Compare commits

..

102 Commits
0.0.6 ... 0.0.7

Author SHA1 Message Date
Sebastián Ramírez
f9522b3913 🔖 Release version 0.0.7 2022-08-28 01:59:44 +02:00
Sebastián Ramírez
e7848923ec 📝 Update release notes 2022-08-28 01:59:04 +02:00
github-actions
eb12bbc640 📝 Update release notes 2022-08-27 23:53:33 +00:00
Yasser Tahiri
6216409f96 ♻ Refactor internal statements to simplify code (#53) 2022-08-28 01:53:02 +02:00
github-actions
6fe256ec2c 📝 Update release notes 2022-08-27 23:50:48 +00:00
Amin Alaee
92f52a3fc5 ♻ Refactor internal imports to reduce redundancy (#272)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:50:12 +00:00
github-actions
2bc915ed04 📝 Update release notes 2022-08-27 23:50:09 +00:00
Raphael Gibson
42b0e6eace Allow setting unique in Field() for a column (#83)
Co-authored-by: Raphael Gibson <raphael.araujo@estantemagica.com.br>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-28 01:49:29 +02:00
github-actions
1ca288028c 📝 Update release notes 2022-08-27 23:44:16 +00:00
kurtportelli
a2cda8377f 📝 Update docs for models for updating, id should not be updatable (#335)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-28 01:43:42 +02:00
github-actions
8ac82e7101 📝 Update release notes 2022-08-27 23:22:53 +00:00
phi-friday
5429e9b6aa 🐛 Fix type annotations for Model.parse_obj(), and Model.validate() (#321)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-28 01:22:09 +02:00
github-actions
c743647a52 📝 Update release notes 2022-08-27 23:18:19 +00:00
Rabin Adhikari
475578757f 🐛 Fix Select and SelectOfScalar to inherit cache to avoid warning: SAWarning: Class SelectOfScalar will not make use of SQL compilation caching (#234)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-28 01:17:37 +02:00
github-actions
5e0ac5b56c 📝 Update release notes 2022-08-27 23:11:00 +00:00
byrman
d380736043 🐛 Fix handling validators for non-default values (#253)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:10:23 +00:00
github-actions
71d6fcc31b 📝 Update release notes 2022-08-27 22:59:54 +00:00
statt8900
680602b7eb 🐛 Fix fields marked as "set" in models (#117)
Co-authored-by: Michael Statt <michael.statt@modelyst.io>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 22:59:09 +00:00
github-actions
9c68ce12ec 📝 Update release notes 2022-08-27 22:49:17 +00:00
Chris White
eef0b7770b 🐛 Fix Enum handling in SQLAlchemy (#165)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-28 00:48:44 +02:00
github-actions
2fab4817fe 📝 Update release notes 2022-08-27 22:28:46 +00:00
Andrew Bolster
5ea9340def Update GUID handling to use stdlib UUID.hex instead of an int (#26)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-28 00:28:09 +02:00
github-actions
db3ad598c5 📝 Update release notes 2022-08-27 22:19:35 +00:00
Evangelos Anagnostopoulos
9830ee0d89 🐛 Fix setting nullable property of Fields that don't accept None (#79)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-28 00:18:57 +02:00
github-actions
2407ecd2bf 📝 Update release notes 2022-08-27 22:07:38 +00:00
Yoann Mosteiro
ee576ab279 ✏ Fix broken variable/typo in docs for Read Relationships, hero_spider_boy.id => hero_spider_boy.team_id (#106)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 22:06:56 +00:00
github-actions
ae1b8b5585 📝 Update release notes 2022-08-27 21:55:59 +00:00
Jorge Alvarado
e48fb2874b 🎨 Remove unwanted highlight in the docs (#233)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:55:15 +02:00
github-actions
7d3bf70a76 📝 Update release notes 2022-08-27 21:37:31 +00:00
Prashanth Rao
91d0785b1c ✏ Fix typos in docs/databases.md and docs/tutorial/index.md (#35)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:36:58 +02:00
github-actions
5dfef7ede7 📝 Update release notes 2022-08-27 21:32:10 +00:00
Jorge Alvarado
0aaf39d539 ✏ Fix typo in docs/tutorial/relationship-attributes/define-relationships-attributes.md (#239)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:31:38 +02:00
github-actions
c0a6b2dd8b 📝 Update release notes 2022-08-27 21:26:06 +00:00
Joe Mudryk
87a02b4c46 ✏ Fix typo in docs/tutorial/fastapi/simple-hero-api.md (#80)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 21:25:29 +00:00
github-actions
14fc1f510e 📝 Update release notes 2022-08-27 21:23:41 +00:00
VictorGambarini
ad0766fe3e ✏ Fix typos in multiple files in the docs (#400)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:22:59 +02:00
github-actions
1e69c00538 📝 Update release notes 2022-08-27 21:09:00 +00:00
github-actions
bf15380733 📝 Update release notes 2022-08-27 21:08:31 +00:00
Marcio Mazza
a993c2141d ✏ Fix typo in docs/tutorial/code-structure.md (#344)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:08:20 +02:00
gr8jam
deed65095f ✏ Fix typo in docs/db-to-code.md (#155)
Co-authored-by: gr8jam <matej.jeglic@gmail.si>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:07:48 +02:00
github-actions
61294af824 📝 Update release notes 2022-08-27 21:06:58 +00:00
Fardad13
106fb1fe9b ✏ Fix typo in docs/contributing.md (#323)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:06:15 +02:00
github-actions
6b433a0de4 📝 Update release notes 2022-08-27 21:05:21 +00:00
Jack Homan
4de5a41720 ✏ Fix typo in docs/tutorial/fastapi/tests.md (#265)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:04:38 +02:00
github-actions
dc5876c727 📝 Update release notes 2022-08-27 21:02:59 +00:00
github-actions
5f6b5bfd7f 📝 Update release notes 2022-08-27 21:02:37 +00:00
Jorge Alvarado
e5fdc371f6 ✏ Fix typo in docs/tutorial/where.md (#286)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:00:53 +02:00
cirrusj
452f18d8bc ✏ Fix typos in docs/tutorial/fastapi/update.md (#268)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 23:00:09 +02:00
github-actions
04b8b3eedf 📝 Update release notes 2022-08-27 20:56:00 +00:00
Hao Wang
426da7c443 ✏ Fix typo in docs/tutorial/fastapi/simple-hero-api.md (#247)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:55:27 +00:00
github-actions
a5116a372c 📝 Update release notes 2022-08-27 20:54:16 +00:00
Gal Bracha
015f7acbc5 ✏ Fix typos in docs/tutorial/automatic-id-none-refresh.md, docs/tutorial/fastapi/update.md, docs/tutorial/select.md (#185)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:53:34 +00:00
github-actions
34e125357f 📝 Update release notes 2022-08-27 20:53:09 +00:00
github-actions
c0efc7b370 📝 Update release notes 2022-08-27 20:52:33 +00:00
Sean Eulenberg
48ada0cd5d ✏ Fix typo in docs/databases.md (#177)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:52:24 +00:00
wmcgee3
aa5803fbbb ✏ Fix typos in docs/tutorial/fastapi/update.md (#162)
Co-authored-by: pwildenhain <35195136+pwildenhain@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:51:46 +00:00
github-actions
8bee55e23b 📝 Update release notes 2022-08-27 20:51:10 +00:00
Måns Magnusson
6f1ffccd4f ✏ Fix typos in docs/tutorial/code-structure.md, docs/tutorial/fastapi/multiple-models.md, docs/tutorial/fastapi/simple-hero-api.md, docs/tutorial/many-to-many/index.md (#116)
Co-authored-by: moonso <mans.magnusson@scilifelab.se>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:50:33 +00:00
github-actions
13544c0f44 📝 Update release notes 2022-08-27 20:48:52 +00:00
Chris Goddard
184c8eb5a9 ✏ Fix typo in docs/tutorial/fastapi/teams.md (#154)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:48:09 +00:00
github-actions
acc27dabc9 📝 Update release notes 2022-08-27 20:41:29 +00:00
Saman Nezafat
d032c3cfea ✏ Fix typo variable in example about relationships and back_populates, always use hero instead of owner (#120)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:40:57 +00:00
github-actions
006cf488e8 📝 Update release notes 2022-08-27 20:34:14 +00:00
Feanil Patel
63dd44dc86 ✏ Fix typo in docs/tutorial/fastapi/tests.md (#113)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:33:41 +00:00
github-actions
f602794f07 📝 Update release notes 2022-08-27 20:33:32 +00:00
Fedor Kuznetsov
f3063a8e16 ✏ Fix typo in docs/tutorial/where.md (#72)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:32:58 +00:00
github-actions
f67a13a5fb 📝 Update release notes 2022-08-27 20:32:46 +00:00
Dhiraj Gupta
5dff4d15e8 ✏ Fix typo in docs/tutorial/code-structure.md (#91)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:32:02 +00:00
github-actions
943892ddb2 📝 Update release notes 2022-08-27 20:31:46 +00:00
mborus
31beaf1017 ✏ Fix broken link to newsletter sign-up in docs/help.md (#84)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:30:59 +00:00
github-actions
9664c8814c 📝 Update release notes 2022-08-27 20:20:44 +00:00
Brent
7bb99f2bd5 ⬆ Update development requirement for FastAPI from ^0.68.0 to ^0.68.1 (#48)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:20:05 +00:00
github-actions
4a08ee89ee 📝 Update release notes 2022-08-27 20:15:17 +00:00
xginn8
0197c6e211 ✏ Fix typos in docs/tutorial/many-to-many/create-models-with-link.md (#45)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:14:23 +00:00
github-actions
6da8dcfc8e 📝 Update release notes 2022-08-27 20:14:11 +00:00
Jakob Jul Elben
dc4dc42ec5 Raise an exception when using a Pydantic field type with no matching SQLAlchemy type (#18)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:13:32 +00:00
github-actions
db29f53295 📝 Update release notes 2022-08-27 20:09:33 +00:00
Sebastián Ramírez
bc6dc0bafc Revert upgrade Poetry, to make a release that supports Python 3.6 first (#417) 2022-08-27 22:08:25 +02:00
github-actions
36b0c1ba08 📝 Update release notes 2022-08-27 18:40:28 +00:00
github-actions
dc0ecbb2c2 📝 Update release notes 2022-08-27 18:40:20 +00:00
Sebastián Ramírez
aca18da21e 👷 Add dependabot for GitHub Actions (#410) 2022-08-27 20:39:53 +02:00
Sebastián Ramírez
f7d1bbe5b6 ⬆️ Upgrade Poetry to version ==1.2.0b1 (#303) 2022-08-27 20:39:37 +02:00
github-actions
296a0935d1 📝 Update release notes 2022-08-27 18:36:49 +00:00
Ryan Grose
0049436cd4 ✏ Fix typo in docs/tutorial/index.md (#398)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 18:36:08 +00:00
github-actions
f4500c6ba4 📝 Update release notes 2022-08-27 18:22:18 +00:00
Robert Rosca
c830c71e28 ⬆ Upgrade constrain for SQLAlchemy = ">=1.4.17,<=1.4.41" (#371)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:21:38 +02:00
github-actions
ea18162391 📝 Update release notes 2022-08-27 18:11:12 +00:00
byrman
4dd7b890d4 🐛 Fix SQLAlchemy version 1.4.36 breaks SQLModel relationships (#315) (#322)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2022-08-27 20:10:38 +02:00
github-actions
4d20051793 📝 Update release notes 2022-04-16 09:30:54 +00:00
Sebastián Ramírez
88683f6e2c 👷 Add CI for Python 3.10 (#305) 2022-04-16 09:30:19 +00:00
github-actions
d6229b3937 📝 Update release notes 2022-04-16 09:25:22 +00:00
Sebastián Ramírez
e523e1e4c3 📝 Add Jina's QA Bot to the docs to help people that want to ask quick questions (#263)
Co-authored-by: yanlong.wang <yanlong.wang@naiver.org>
Co-authored-by: Han Xiao <han.xiao@jina.ai>
2022-04-16 11:24:53 +02:00
github-actions
6d969c5845 📝 Update release notes 2022-04-16 09:20:01 +00:00
Sebastián Ramírez
b94d393924 👷 Upgrade Codecov GitHub Action (#304) 2022-04-16 11:19:30 +02:00
github-actions
e009ecb704 📝 Update release notes 2022-04-16 09:13:48 +00:00
Sebastián Ramírez
03e861d048 Add new Session.get() parameter execution_options (#302) 2022-04-16 11:13:19 +02:00
github-actions
8e97c93de0 📝 Update release notes 2022-02-13 17:14:34 +00:00
Sebastián Ramírez
7176d89e48 💚 Only run CI on push when on master, to avoid duplicate runs on PRs (#244) 2022-02-13 17:14:02 +00:00
github-actions
e6f8c00bbe 📝 Update release notes 2022-01-08 16:54:48 +00:00
Sebastián Ramírez
c873aa3930 🔧 Upgrade MkDocs Material and update configs (#217) 2022-01-08 17:49:07 +01:00
github-actions
800a5f232f 📝 Update release notes 2022-01-08 16:37:03 +00:00
Sebastián Ramírez
8d1b6f079a ⬆ Upgrade mypy, fix type annotations (#218) 2022-01-08 16:36:19 +00:00
46 changed files with 518 additions and 168 deletions

View File

@@ -1,5 +1,11 @@
version: 2
updates:
# GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
# Python
- package-ecosystem: "pip"
directory: "/"
schedule:

View File

@@ -1,6 +1,8 @@
name: Build Docs
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize]
workflow_dispatch:

View File

@@ -2,6 +2,8 @@ name: Test
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize]
workflow_dispatch:
@@ -16,7 +18,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: ["3.6.15", "3.7", "3.8", "3.9", "3.10"]
fail-fast: false
steps:
@@ -52,9 +54,9 @@ jobs:
if: steps.cache.outputs.cache-hit != 'true'
run: python -m poetry install
- name: Lint
if: ${{ matrix.python-version != '3.6' }}
if: ${{ matrix.python-version != '3.6.15' }}
run: python -m poetry run bash scripts/lint.sh
- name: Test
run: python -m poetry run bash scripts/test.sh
- name: Upload coverage
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v2

View File

@@ -42,7 +42,7 @@ $ poetry shell
</div>
That will set up the environment variables needed dand will start a new shell with them.
That will set up the environment variables needed and start a new shell with them.
#### Using your local SQLModel

View File

@@ -274,7 +274,7 @@ The language is called **SQL**, the name comes from for **Structured Query Langu
Nevertheless, the language is not only used to *query* for data. It is also used to create records/rows, to update them, to delete them. And to manipulate the database, create tables, etc.
This language is supported by all these databases that handle multiple tables, that's why they are called **SQL Databases**. Although, each database has small variations in the SQL language they support.
This language is supported by all these databases that handle multiple tables, that's why they are called **SQL Databases**. Although, each database has small variations in the SQL language they support (*dialect*).
Let's imagine that the table holding the heroes is called the `hero` table. An example of a SQL query to get all the data from it could look like:

View File

@@ -143,7 +143,7 @@ If the user provides this ID:
2
```
...the would be this table (with a single row):
...the result would be this table (with a single row):
<table>
<tr>

View File

@@ -12,7 +12,7 @@ And there are several ways to get help too.
## Subscribe to the FastAPI and Friends newsletter
You can subscribe to the (infrequent) [**FastAPI and friends** newsletter](/newsletter/){.internal-link target=_blank} to stay updated about:
You can subscribe to the (infrequent) <a href="https://fastapi.tiangolo.com/newsletter" class="external-link" target="_blank">**FastAPI and friends** newsletter</a> to stay updated about:
* News about FastAPI and friends, including SQLModel 🚀
* Guides 📝

31
docs/overrides/main.html Normal file
View File

@@ -0,0 +1,31 @@
{% extends "base.html" %}
{%- block scripts %}
{{ super() }}
<script src="https://cdn.jsdelivr.net/npm/qabot@0.4"></script>
<script>
// This prevents the global search from interfering with qa-bot's internal text input.
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('qa-bot').forEach((x) => {
x.addEventListener('keydown', (event) => {
event.stopPropagation();
});
});
});
</script>
<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"
>
<template>
<dl>
<dt>You can ask questions about SQLModel. Try:</dt>
<dd>Which Python version is supported?</dd>
<dd>How SQLModel interacts with the database?</dd>
<dd>How can I link tables?</dd>
</dl>
</template>
</qa-bot>
{%- endblock %}

View File

@@ -3,6 +3,70 @@
## Latest Changes
## 0.0.7
### Features
* ✨ Allow setting `unique` in `Field()` for a column. PR [#83](https://github.com/tiangolo/sqlmodel/pull/83) by [@raphaelgibson](https://github.com/raphaelgibson).
* ✨ Update GUID handling to use stdlib `UUID.hex` instead of an `int`. PR [#26](https://github.com/tiangolo/sqlmodel/pull/26) by [@andrewbolster](https://github.com/andrewbolster).
* ✨ Raise an exception when using a Pydantic field type with no matching SQLAlchemy type. PR [#18](https://github.com/tiangolo/sqlmodel/pull/18) by [@elben10](https://github.com/elben10).
* ⬆ Upgrade constrain for SQLAlchemy = ">=1.4.17,<=1.4.41". PR [#371](https://github.com/tiangolo/sqlmodel/pull/371) by [@RobertRosca](https://github.com/RobertRosca).
* ✨ Add new `Session.get()` parameter `execution_options`. PR [#302](https://github.com/tiangolo/sqlmodel/pull/302) by [@tiangolo](https://github.com/tiangolo).
### Fixes
* 🐛 Fix type annotations for `Model.parse_obj()`, and `Model.validate()`. PR [#321](https://github.com/tiangolo/sqlmodel/pull/321) by [@phi-friday](https://github.com/phi-friday).
* 🐛 Fix `Select` and `SelectOfScalar` to inherit cache to avoid warning: `SAWarning: Class SelectOfScalar will not make use of SQL compilation caching`. PR [#234](https://github.com/tiangolo/sqlmodel/pull/234) by [@rabinadk1](https://github.com/rabinadk1).
* 🐛 Fix handling validators for non-default values. PR [#253](https://github.com/tiangolo/sqlmodel/pull/253) by [@byrman](https://github.com/byrman).
* 🐛 Fix fields marked as "set" in models. PR [#117](https://github.com/tiangolo/sqlmodel/pull/117) by [@statt8900](https://github.com/statt8900).
* 🐛 Fix Enum handling in SQLAlchemy. PR [#165](https://github.com/tiangolo/sqlmodel/pull/165) by [@chriswhite199](https://github.com/chriswhite199).
* 🐛 Fix setting nullable property of Fields that don't accept `None`. PR [#79](https://github.com/tiangolo/sqlmodel/pull/79) by [@van51](https://github.com/van51).
* 🐛 Fix SQLAlchemy version 1.4.36 breaks SQLModel relationships (#315). PR [#322](https://github.com/tiangolo/sqlmodel/pull/322) by [@byrman](https://github.com/byrman).
### Docs
* 📝 Update docs for models for updating, `id` should not be updatable. PR [#335](https://github.com/tiangolo/sqlmodel/pull/335) by [@kurtportelli](https://github.com/kurtportelli).
* ✏ Fix broken variable/typo in docs for Read Relationships, `hero_spider_boy.id` => `hero_spider_boy.team_id`. PR [#106](https://github.com/tiangolo/sqlmodel/pull/106) by [@yoannmos](https://github.com/yoannmos).
* 🎨 Remove unwanted highlight in the docs. PR [#233](https://github.com/tiangolo/sqlmodel/pull/233) by [@jalvaradosegura](https://github.com/jalvaradosegura).
* ✏ Fix typos in `docs/databases.md` and `docs/tutorial/index.md`. PR [#35](https://github.com/tiangolo/sqlmodel/pull/35) by [@prrao87](https://github.com/prrao87).
* ✏ Fix typo in `docs/tutorial/relationship-attributes/define-relationships-attributes.md`. PR [#239](https://github.com/tiangolo/sqlmodel/pull/239) by [@jalvaradosegura](https://github.com/jalvaradosegura).
* ✏ Fix typo in `docs/tutorial/fastapi/simple-hero-api.md`. PR [#80](https://github.com/tiangolo/sqlmodel/pull/80) by [@joemudryk](https://github.com/joemudryk).
* ✏ Fix typos in multiple files in the docs. PR [#400](https://github.com/tiangolo/sqlmodel/pull/400) by [@VictorGambarini](https://github.com/VictorGambarini).
* ✏ Fix typo in `docs/tutorial/code-structure.md`. PR [#344](https://github.com/tiangolo/sqlmodel/pull/344) by [@marciomazza](https://github.com/marciomazza).
* ✏ Fix typo in `docs/db-to-code.md`. PR [#155](https://github.com/tiangolo/sqlmodel/pull/155) by [@gr8jam](https://github.com/gr8jam).
* ✏ Fix typo in `docs/contributing.md`. PR [#323](https://github.com/tiangolo/sqlmodel/pull/323) by [@Fardad13](https://github.com/Fardad13).
* ✏ Fix typo in `docs/tutorial/fastapi/tests.md`. PR [#265](https://github.com/tiangolo/sqlmodel/pull/265) by [@johnhoman](https://github.com/johnhoman).
* ✏ Fix typo in `docs/tutorial/where.md`. PR [#286](https://github.com/tiangolo/sqlmodel/pull/286) by [@jalvaradosegura](https://github.com/jalvaradosegura).
* ✏ Fix typos in `docs/tutorial/fastapi/update.md`. PR [#268](https://github.com/tiangolo/sqlmodel/pull/268) by [@cirrusj](https://github.com/cirrusj).
* ✏ Fix typo in `docs/tutorial/fastapi/simple-hero-api.md`. PR [#247](https://github.com/tiangolo/sqlmodel/pull/247) by [@hao-wang](https://github.com/hao-wang).
* ✏ Fix typos in `docs/tutorial/automatic-id-none-refresh.md`, `docs/tutorial/fastapi/update.md`, `docs/tutorial/select.md`. PR [#185](https://github.com/tiangolo/sqlmodel/pull/185) by [@rootux](https://github.com/rootux).
* ✏ Fix typo in `docs/databases.md`. PR [#177](https://github.com/tiangolo/sqlmodel/pull/177) by [@seandlg](https://github.com/seandlg).
* ✏ Fix typos in `docs/tutorial/fastapi/update.md`. PR [#162](https://github.com/tiangolo/sqlmodel/pull/162) by [@wmcgee3](https://github.com/wmcgee3).
* ✏ Fix typos in `docs/tutorial/code-structure.md`, `docs/tutorial/fastapi/multiple-models.md`, `docs/tutorial/fastapi/simple-hero-api.md`, `docs/tutorial/many-to-many/index.md`. PR [#116](https://github.com/tiangolo/sqlmodel/pull/116) by [@moonso](https://github.com/moonso).
* ✏ Fix typo in `docs/tutorial/fastapi/teams.md`. PR [#154](https://github.com/tiangolo/sqlmodel/pull/154) by [@chrisgoddard](https://github.com/chrisgoddard).
* ✏ Fix typo variable in example about relationships and `back_populates`, always use `hero` instead of `owner`. PR [#120](https://github.com/tiangolo/sqlmodel/pull/120) by [@onionj](https://github.com/onionj).
* ✏ Fix typo in `docs/tutorial/fastapi/tests.md`. PR [#113](https://github.com/tiangolo/sqlmodel/pull/113) by [@feanil](https://github.com/feanil).
* ✏ Fix typo in `docs/tutorial/where.md`. PR [#72](https://github.com/tiangolo/sqlmodel/pull/72) by [@ZettZet](https://github.com/ZettZet).
* ✏ Fix typo in `docs/tutorial/code-structure.md`. PR [#91](https://github.com/tiangolo/sqlmodel/pull/91) by [@dhiraj](https://github.com/dhiraj).
* ✏ Fix broken link to newsletter sign-up in `docs/help.md`. PR [#84](https://github.com/tiangolo/sqlmodel/pull/84) by [@mborus](https://github.com/mborus).
* ✏ Fix typos in `docs/tutorial/many-to-many/create-models-with-link.md`. PR [#45](https://github.com/tiangolo/sqlmodel/pull/45) by [@xginn8](https://github.com/xginn8).
* ✏ Fix typo in `docs/tutorial/index.md`. PR [#398](https://github.com/tiangolo/sqlmodel/pull/398) by [@ryangrose](https://github.com/ryangrose).
### Internal
* ♻ Refactor internal statements to simplify code. PR [#53](https://github.com/tiangolo/sqlmodel/pull/53) by [@yezz123](https://github.com/yezz123).
* ♻ Refactor internal imports to reduce redundancy. PR [#272](https://github.com/tiangolo/sqlmodel/pull/272) by [@aminalaee](https://github.com/aminalaee).
* ⬆ Update development requirement for FastAPI from `^0.68.0` to `^0.68.1`. PR [#48](https://github.com/tiangolo/sqlmodel/pull/48) by [@alucarddelta](https://github.com/alucarddelta).
* ⏪ Revert upgrade Poetry, to make a release that supports Python 3.6 first. PR [#417](https://github.com/tiangolo/sqlmodel/pull/417) by [@tiangolo](https://github.com/tiangolo).
* 👷 Add dependabot for GitHub Actions. PR [#410](https://github.com/tiangolo/sqlmodel/pull/410) by [@tiangolo](https://github.com/tiangolo).
* ⬆️ Upgrade Poetry to version `==1.2.0b1`. PR [#303](https://github.com/tiangolo/sqlmodel/pull/303) by [@tiangolo](https://github.com/tiangolo).
* 👷 Add CI for Python 3.10. PR [#305](https://github.com/tiangolo/sqlmodel/pull/305) by [@tiangolo](https://github.com/tiangolo).
* 📝 Add Jina's QA Bot to the docs to help people that want to ask quick questions. PR [#263](https://github.com/tiangolo/sqlmodel/pull/263) by [@tiangolo](https://github.com/tiangolo).
* 👷 Upgrade Codecov GitHub Action. PR [#304](https://github.com/tiangolo/sqlmodel/pull/304) by [@tiangolo](https://github.com/tiangolo).
* 💚 Only run CI on push when on master, to avoid duplicate runs on PRs. PR [#244](https://github.com/tiangolo/sqlmodel/pull/244) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Upgrade MkDocs Material and update configs. PR [#217](https://github.com/tiangolo/sqlmodel/pull/217) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Upgrade mypy, fix type annotations. PR [#218](https://github.com/tiangolo/sqlmodel/pull/218) by [@tiangolo](https://github.com/tiangolo).
## 0.0.6
### Breaking Changes

View File

@@ -1,6 +1,6 @@
# Automatic IDs, None Defaults, and Refreshing Data
In the previous chapter we saw how to add rows to the database using **SQLModel**.
In the previous chapter, we saw how to add rows to the database using **SQLModel**.
Now let's talk a bit about why the `id` field **can't be `NULL`** on the database because it's a **primary key**, and we declare it using `Field(primary_key=True)`.
@@ -11,7 +11,7 @@ But the same `id` field actually **can be `None`** in the Python code, so we dec
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:6-10]!}
# Code below ommitted 👇
# Code below omitted 👇
```
<details>
@@ -68,7 +68,7 @@ If we ran this code before saving the hero to the database and the `hero_1.id` w
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
```
But by declaring it with `Optional[int]` the editor will help us to avoid writing broken code by showing us a warning telling us that the code could be invalid if `hero_1.id` is `None`. 🔍
But by declaring it with `Optional[int]`, the editor will help us to avoid writing broken code by showing us a warning telling us that the code could be invalid if `hero_1.id` is `None`. 🔍
## Print the Default `id` Values
@@ -79,7 +79,7 @@ We can confirm that by printing our heroes before adding them to the database:
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:23-31]!}
# Code below ommitted 👇
# Code below omitted 👇
```
<details>
@@ -98,7 +98,7 @@ That will output:
```console
$ python app.py
// Output above ommitted 👆
// Output above omitted 👆
Before interacting with the database
Hero 1: id=None name='Deadpond' secret_name='Dive Wilson' age=None
@@ -118,7 +118,7 @@ What happens when we `add` these objects to the **session**?
After we add the `Hero` instance objects to the **session**, the IDs are *still* `None`.
We can verify by creating a session using a `with` block, and adding the objects. And then printing them again:
We can verify by creating a session using a `with` block and adding the objects. And then printing them again:
```Python hl_lines="19-21"
# Code above omitted 👆
@@ -144,7 +144,7 @@ This will, again, output the `id`s of the objects as `None`:
```console
$ python app.py
// Output above ommitted 👆
// Output above omitted 👆
After adding to the session
Hero 1: id=None name='Deadpond' secret_name='Dive Wilson' age=None
@@ -165,7 +165,7 @@ Then we can `commit` the changes in the session, and print again:
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial001.py[ln:33-48]!}
# Code below ommitted 👇
# Code below omitted 👇
```
<details>
@@ -184,7 +184,7 @@ And now, something unexpected happens, look at the output, it seems as if the `H
```console
$ python app.py
// Output above ommitted 👆
// Output above omitted 👆
// Here the engine talks to the database, the SQL part
INFO Engine BEGIN (implicit)
@@ -450,7 +450,7 @@ Now let's review all this code once again.
And as we created the **engine** with `echo=True`, we can see the SQL statements being executed at each step.
```{ .python .annotate hl_lines="54" }
```{ .python .annotate }
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial002.py!}
```

View File

@@ -8,7 +8,7 @@ The class `Hero` has a reference to the class `Team` internally.
But the class `Team` also has a reference to the class `Hero`.
So, if those two classes where in separate files and you tried to import the classes in each other's file directly, it would result in a **circular import**. 🔄
So, if those two classes were in separate files and you tried to import the classes in each other's file directly, it would result in a **circular import**. 🔄
And Python will not be able to handle it and will throw an error. 🚨
@@ -170,7 +170,7 @@ Let's assume that now the file structure is:
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>.
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.
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.
And the files with those classes might **also need to import** more things from the first files.
@@ -198,7 +198,7 @@ It has a value of `True` for editors and tools that analyze the code with the ty
But when Python is executing, its value is `False`.
So, we can us it in an `if` block and import things inside the `if` block. And they will be "imported" only for editors, but not at runtime.
So, we can use it in an `if` block and import things inside the `if` block. And they will be "imported" only for editors, but not at runtime.
### Hero Model File

View File

@@ -39,6 +39,6 @@ After deleting it successfully, we just return a response of:
## Recap
That's it, feel free to try it out in the interactve docs UI to delete some heroes. 💥
That's it, feel free to try it out in the interactive docs UI to delete some heroes. 💥
Using **FastAPI** to read data and combining it with **SQLModel** makes it quite straightforward to delete data from the database.

View File

@@ -2,14 +2,14 @@
When a client sends a request to get all the heroes, we have been returning them all.
But if we had **thousands** of heroes that could consume a lot of **computational resources**, network bandwith, etc.
But if we had **thousands** of heroes that could consume a lot of **computational resources**, network bandwidth, etc.
So we probably want to limit it.
So, we probably want to limit it.
Let's use the same **offset** and **limit** we learned about in the previous tutorial chapters for the API.
!!! info
In many cases this is also called **pagination**.
In many cases, this is also called **pagination**.
## Add a Limit and Offset to the Query Parameters
@@ -38,13 +38,13 @@ And by default, we will return a maximum of `100` heroes, so `limit` will have a
</details>
We want to allow clients to set a different `offset` and `limit` values.
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`.
This way, a client can decide to take less heroes if they want, but not more.
This way, a client can decide to take fewer heroes if they want, but not more.
!!! info
If you need to refresh how query parameters and their validation work, check out the docs in FastAPI:

View File

@@ -2,7 +2,7 @@
We have been using the same `Hero` model to declare the schema of the data we receive in the API, the table model in the database, and the schema of the data we send back in responses.
But in most of the cases there are slight differences, let's use multiple models to solve it.
But in most of the cases, there are slight differences. Let's use multiple models to solve it.
Here you will see the main and biggest feature of **SQLModel**. 😎
@@ -10,7 +10,7 @@ Here you will see the main and biggest feature of **SQLModel**. 😎
Let's start by reviewing the automatically generated schemas from the docs UI.
For input we have:
For input, we have:
<img class="shadow" alt="Interactive API docs UI" src="/img/tutorial/fastapi/simple-hero-api/image01.png">
@@ -20,7 +20,7 @@ This means that the client could try to use the same ID that already exists in t
That's not what we want.
We want the client to only send the data that is needed to create a new hero:
We want the client only to send the data that is needed to create a new hero:
* `name`
* `secret_name`
@@ -63,7 +63,7 @@ The ultimate goal of an API is for some **clients to use it**.
The clients could be a frontend application, a command line program, a graphical user interface, a mobile application, another backend application, etc.
And the code those clients write depend on what our API tells them they **need to send**, and what they can **expect to receive**.
And the code those clients write depends on what our API tells them they **need to send**, and what they can **expect to receive**.
Making both sides very clear will make it much easier to interact with the API.
@@ -164,7 +164,7 @@ Let's first check how is the process to create a hero now:
Let's check that in detail.
Now we use the type annotation `HeroCreate` for the request JSON data, in the `hero` parameter of the **path operation function**.
Now we use the type annotation `HeroCreate` for the request JSON data in the `hero` parameter of the **path operation function**.
```Python hl_lines="3"
# Code above omitted 👆
@@ -180,9 +180,9 @@ The method `.from_orm()` reads data from another object with attributes and crea
The alternative is `Hero.parse_obj()` that reads data from a dictionary.
But as in this case we have a `HeroCreate` instance in the `hero` variable, this is an object with attributes, so we use `.from_orm()` to read those attributes.
But as in this case, we have a `HeroCreate` instance in the `hero` variable. This is an object with attributes, so we use `.from_orm()` to read those attributes.
With this we create a new `Hero` instance (the one for the database) and put it in the variable `db_hero` from the data in the `hero` variable that is the `HeroCreate` instance we received from the request.
With this, we create a new `Hero` instance (the one for the database) and put it in the variable `db_hero` from the data in the `hero` variable that is the `HeroCreate` instance we received from the request.
```Python hl_lines="3"
# Code above omitted 👆
@@ -192,7 +192,7 @@ With this we create a new `Hero` instance (the one for the database) and put it
# Code below omitted 👇
```
Then we just `add` it to the **session**, `commit`, and `refresh` it, and finally we return the same `db_hero` variable that has the just refreshed `Hero` instance.
Then we just `add` it to the **session**, `commit`, and `refresh` it, and finally, we return the same `db_hero` variable that has the just refreshed `Hero` instance.
Because it is just refreshed, it has the `id` field set with a new ID taken from the database.
@@ -206,30 +206,30 @@ And now that we return it, FastAPI will validate the data with the `response_mod
# Code below omitted 👇
```
This will validate that all the data that we promised is there, and will remove any data we didn't declare.
This will validate that all the data that we promised is there and will remove any data we didn't declare.
!!! tip
This filtering could be very important, and could be a very good security feature, for example to make sure you filter private data, hashed passwords, etc.
This filtering could be very important and could be a very good security feature, for example, to make sure you filter private data, hashed passwords, etc.
You can read more about it in the <a href="https://fastapi.tiangolo.com/tutorial/response-model/" class="external-link" target="_blank">FastAPI docs about Response Model</a>.
In particular, it will make sure that the `id` is there, and that it is indeed an integer (and not `None`).
In particular, it will make sure that the `id` is there and that it is indeed an integer (and not `None`).
## Shared Fields
But looking closely, we could see that these models have a lot of **duplicated information**.
All **the 3 models** declare that thay share some **common fields** that look exactly the same:
All **the 3 models** declare that they share some **common fields** that look exactly the same:
* `name`, required
* `secret_name`, required
* `age`, optional
And then they declare other fields with some differences (in this case only about the `id`).
And then they declare other fields with some differences (in this case, only about the `id`).
We want to **avoid duplicated information** if possible.
This is important if, for example, in the future we decide to **refactor the code** and rename one field (column). For example, from `secret_name` to `secret_identity`.
This is important if, for example, in the future, we decide to **refactor the code** and rename one field (column). For example, from `secret_name` to `secret_identity`.
If we have that duplicated in multiple models, we could easily forget to update one of them. But if we **avoid duplication**, there's only one place that would need updating. ✨
@@ -361,9 +361,9 @@ And because we can't leave the empty space when creating a new class, but we don
This means that there's nothing else special in this class apart from the fact that it is named `HeroCreate` and that it inherits from `HeroBase`.
As an alternative, we could use `HeroBase` directly in the API code instead of `HeroCreate`, but it would show up in the auomatic docs UI with that name "`HeroBase`" which could be **confusing** for clients. Instead, "`HeroCreate`" is a bit more explicit about what it is for.
As an alternative, we could use `HeroBase` directly in the API code instead of `HeroCreate`, but it would show up in the automatic docs UI with that name "`HeroBase`" which could be **confusing** for clients. Instead, "`HeroCreate`" is a bit more explicit about what it is for.
On top of that, we could easily decide in the future that we want to receive **more data** when creating a new hero apart from the data in `HeroBase` (for example a password), and now we already have the class to put those extra fields.
On top of that, we could easily decide in the future that we want to receive **more data** when creating a new hero apart from the data in `HeroBase` (for example, a password), and now we already have the class to put those extra fields.
### The `HeroRead` **Data Model**
@@ -390,7 +390,7 @@ This one just declares that the `id` field is required when reading a hero from
## Review the Updated Docs UI
The FastAPI code is still the same as above, we still use `Hero`, `HeroCreate`, and `HeroRead`. But now we define them in a smarter way with inheritance.
The FastAPI code is still the same as above, we still use `Hero`, `HeroCreate`, and `HeroRead`. But now, we define them in a smarter way with inheritance.
So, we can jump to the docs UI right away and see how they look with the updated data.
@@ -400,7 +400,7 @@ Let's see the new UI for creating a hero:
<img class="shadow" alt="Interactive API docs UI" src="/img/tutorial/fastapi/multiple-models/image02.png">
Nice! It now shows that to create a hero, we just pass the `name`, `secret_name`, and optinally `age`.
Nice! It now shows that to create a hero, we just pass the `name`, `secret_name`, and optionally `age`.
We no longer pass an `id`.
@@ -416,7 +416,7 @@ And if we check the schema for the **Read Heroes** *path operation* it will also
## Inheritance and Table Models
We just saw how powerful inheritance of these models can be.
We just saw how powerful the inheritance of these models could be.
This is a very simple example, and it might look a bit... meh. 😅

View File

@@ -42,7 +42,7 @@ But if the integer is not the ID of any hero in the database, it will not find a
So, we check it in an `if` block, if it's `None`, we raise an `HTTPException` with a `404` status code.
And to use it we first import `HTTPException` from `fastapi`.
And to use it, we first import `HTTPException` from `fastapi`.
This will let the client know that they probably made a mistake on their side and requested a hero that doesn't exist in the database.

View File

@@ -55,11 +55,11 @@ And the same way, we declared the `TeamRead` with only the same base fields of t
# Code here omitted 👈
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:32-37]!}
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:31-36]!}
# Code here omitted 👈
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:46-47]!}
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:45-46]!}
# Code below omitted 👇
```
@@ -80,11 +80,11 @@ In this case, we used `response_model=TeamRead` and `response_model=HeroRead`, s
```Python hl_lines="3 8 12 17"
# Code above omitted 👆
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:105-110]!}
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:104-109]!}
# Code here omitted 👈
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:160-165]!}
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:159-164]!}
# Code below omitted 👇
```
@@ -102,7 +102,7 @@ In this case, we used `response_model=TeamRead` and `response_model=HeroRead`, s
Now let's stop for a second and think about it.
We cannot simply include *all* the data including all the internal relationships, because each **hero** has an attribute `team` with their team, and then that **team** also has an attribute `heroes` with all the **heroes** in the team, including this one.
We cannot simply include *all* the data, including all the internal relationships, because each **hero** has an attribute `team` with their team, and then that **team** also has an attribute `heroes` with all the **heroes** in the team, including this one.
If we tried to include everything, we could make the server application **crash** trying to extract **infinite data**, going through the same hero and team over and over again internally, something like this:
@@ -152,7 +152,7 @@ If we tried to include everything, we could make the server application **crash*
}
```
As you can see, in this example we would get the hero **Rusty-Man**, and from this hero we would get the team **Preventers**, and then from this team we would get its heroes, of course, including **Rusty-Man**... 😱
As you can see, in this example, we would get the hero **Rusty-Man**, and from this hero we would get the team **Preventers**, and then from this team we would get its heroes, of course, including **Rusty-Man**... 😱
So we start again, and in the end, the server would just crash trying to get all the data with a `"Maximum recursion error"`, we would not even get a response like the one above.
@@ -164,7 +164,7 @@ This is a decision that will depend on **each application**.
In our case, let's say that if we get a **list of heroes**, we don't want to also include each of their teams in each one.
And if we get a **list of teams**, we don't want to get a a list of the heroes for each one.
And if we get a **list of teams**, we don't want to get a list of the heroes for each one.
But if we get a **single hero**, we want to include the team data (without the team's heroes).
@@ -195,7 +195,7 @@ We'll add them **after** the other models so that we can easily reference the pr
</details>
These two models are very **simple in code**, but there's a lot happening here, let's check it out.
These two models are very **simple in code**, but there's a lot happening here. Let's check it out.
### Inheritance and Type Annotations
@@ -203,7 +203,7 @@ The `HeroReadWithTeam` **inherits** from `HeroRead`, which means that it will ha
And then it adds the **new field** `team`, which could be `None`, and is declared with the type `TeamRead` with the base fields for reading a team.
Then we do the same for the `TeamReadWithHeroes`, it **inherits** from `TeamRead`, and declare the **new field** `heroes` which is a list of `HeroRead`.
Then we do the same for the `TeamReadWithHeroes`, it **inherits** from `TeamRead`, and declares the **new field** `heroes`, which is a list of `HeroRead`.
### Data Models Without Relationship Attributes
@@ -213,7 +213,7 @@ Instead, here these are only **data models** that will tell FastAPI **which attr
### Reference to Other Models
Also notice that the field `team` is not declared with this new `TeamReadWithHeroes`, because that would again create that infinite recursion of data. Instead, we declare it with the normal `TeamRead` model.
Also, notice that the field `team` is not declared with this new `TeamReadWithHeroes`, because that would again create that infinite recursion of data. Instead, we declare it with the normal `TeamRead` model.
And the same for `TeamReadWithHeroes`, the model used for the new field `heroes` uses `HeroRead` to get only each hero's data.
@@ -326,7 +326,7 @@ Now we get the list of **heroes** included:
## Recap
Using the same techniques to declare additonal **data models** we can tell FastAPI what data to return in the responses, even when we return **table models**.
Using the same techniques to declare additional **data models**, we can tell FastAPI what data to return in the responses, even when we return **table models**.
Here we almost **didn't have to change the FastAPI app** code, but of course, there will be cases where you need to get the data and process it in different ways in the *path operation function* before returning it.
@@ -334,4 +334,4 @@ But even in those cases, you will be able to define the **data models** to use i
By this point, you already have a very robust API to handle data in a SQL database combining **SQLModel** with **FastAPI**, and implementing **best practices**, like data validation, conversion, filtering, and documentation. ✨
In the next chapter I'll tell you how to implement automated **testing** for your application using FastAPI and SQLModel. ✅
In the next chapter, I'll tell you how to implement automated **testing** for your application using FastAPI and SQLModel. ✅

View File

@@ -22,7 +22,7 @@ You can see that there's a possible "Successful Response" with a code `200`, but
<img class="shadow" alt="API docs UI without response data schemas" src="/img/tutorial/fastapi/response-model/image01.png">
Right now we only tell FastAPI the data we want to receive, but we don't tell it yet the data we want to send back.
Right now, we only tell FastAPI the data we want to receive, but we don't tell it yet the data we want to send back.
Let's do that now. 🤓

View File

@@ -90,7 +90,7 @@ We import `Depends()` from `fastapi`. Then we use it in the *path operation func
You can read more about it in the FastAPI documentation <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#order-the-parameters-as-you-need-tricks" class="external-link" target="_blank">Path Parameters and Numeric Validations - Order the parameters as you need, tricks</a>
The value of a dependency will **only be used for one request**, FastAPI will call it right before calling your code, and will give you the value from that dependency.
The value of a dependency will **only be used for one request**, FastAPI will call it right before calling your code and will give you the value from that dependency.
If it had `yield`, then it will continue the rest of the execution once you are done sending the response. In the case of the **session**, it will finish the cleanup code from the `with` block, closing the session, etc.

View File

@@ -23,7 +23,7 @@ $ python -m pip install fastapi "uvicorn[standard]"
```
</div>
s
## **SQLModel** Code - Models, Engine
Now let's start with the SQLModel code.
@@ -158,7 +158,7 @@ Here we use the **same** class model to define the **request body** that will be
Because **FastAPI** is based on Pydantic, it will use the same model (the Pydantic part) to do automatic data validation and <abbr title="also called serialization, marshalling">conversion</abbr> from the JSON request to an object that is an actual instance of the `Hero` class.
And then because this same **SQLModel** object is not only a **Pydantic** model instance but also a **SQLAlchemy** model instance, we can use it directly in a **session** to create the row in the database.
And then, because this same **SQLModel** object is not only a **Pydantic** model instance but also a **SQLAlchemy** model instance, we can use it directly in a **session** to create the row in the database.
So we can use intuitive standard Python **type annotations**, and we don't have to duplicate a lot of the code for the database models and the API data models. 🎉
@@ -190,13 +190,13 @@ When a client sends a request to the **path** `/heroes/` with a `GET` HTTP **ope
## One Session per Request
Remember that we shoud use a SQLModel **session** per each group of operations and if we need other unrelated operations we should use a different session?
Remember that we should use a SQLModel **session** per each group of operations and if we need other unrelated operations we should use a different session?
Here it is much more obvious.
We should normally have **one session per request** in most of the cases.
In some isolated cases we would want to have new sessions inside, so, **more than one session** per request.
In some isolated cases, we would want to have new sessions inside, so, **more than one session** per request.
But we would **never want to *share* the same session** among different requests.
@@ -277,7 +277,7 @@ And then you can get them back with the **Read Heroes** *path operation*:
Now you can terminate that Uvicorn server by going back to the terminal and pressing <kbd>Ctrl+C</kbd>.
And then you can open **DB Browser for SQLite** and check the database, to explore the data and confirm that it indeed saved the heroes. 🎉
And then, you can open **DB Browser for SQLite** and check the database, to explore the data and confirm that it indeed saved the heroes. 🎉
<img class="shadow" alt="DB Browser for SQLite showing the heroes" src="/img/tutorial/fastapi/simple-hero-api/db-browser-01.png">
@@ -287,4 +287,4 @@ Good job! This is already a FastAPI **web API** application to interact with the
There are several things we can improve and extend. For example, we want the database to decide the ID of each new hero, we don't want to allow a user to send it.
We will do all those improvements in the next chapters. 🚀
We will make all those improvements in the next chapters. 🚀

View File

@@ -12,14 +12,14 @@ Let's add the models for the teams.
It's the same process we did for heroes, with a base model, a **table model**, and some other **data models**.
We have a `TeamBase` **data model**, and from it we inherit with a `Team` **table model**.
We have a `TeamBase` **data model**, and from it, we inherit with a `Team` **table model**.
Then we also inherit from the `TeamBase` for the `TeamCreate` and `TeamRead` **data models**.
And we also create a `TeamUpdate` **data model**.
```Python hl_lines="7-9 12-15 18-19 22-23 26-29"
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:1-29]!}
```Python hl_lines="7-9 12-15 18-19 22-23 26-28"
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:1-28]!}
# Code below omitted 👇
```
@@ -42,7 +42,7 @@ Let's now update the `Hero` models too.
```Python hl_lines="3-8 11-15 17-18 21-22 25-29"
# Code above omitted 👆
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:32-58]!}
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:31-57]!}
# Code below omitted 👇
```
@@ -66,10 +66,10 @@ And even though the `HeroBase` is *not* a **table model**, we can declare `team_
Notice that the **relationship attributes**, the ones with `Relationship()`, are **only** in the **table models**, as those are the ones that are handled by **SQLModel** with SQLAlchemy and that can have the automatic fetching of data from the database when we access them.
```Python hl_lines="11 39"
```Python hl_lines="11 38"
# Code above omitted 👆
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:7-58]!}
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:7-57]!}
# Code below omitted 👇
```
@@ -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:140-194]!}
{!./docs_src/tutorial/fastapi/teams/tutorial001.py[ln:139-193]!}
# Code below omitted 👇
```
@@ -108,9 +108,9 @@ These are equivalent and very similar to the **path operations** for the **heroe
## Using Relationships Attributes
Up to this point we are actually not using the **relationship attributes**, but we could access them in our code.
Up to this point, we are actually not using the **relationship attributes**, but we could access them in our code.
In the next chapter we will play more with them.
In the next chapter, we will play more with them.
## Check the Docs UI

View File

@@ -76,13 +76,13 @@ Let's start with a simple test, with just the basic test code we need the check
That's the **core** of the code we need for all the tests later.
But now we need to deal with a bit of logistics and details we are not paying attention to just yet. 🤓
But now, we need to deal with a bit of logistics and details we are not paying attention to just yet. 🤓
## Testing Database
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 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 unnecesary 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.
@@ -155,7 +155,7 @@ That way, when we call `.create_all()` all the **table models** are correctly re
## Memory Database
Now we are not using the production database, instead we use a **new testing database** with the `testing.db` file, which is great.
Now we are not using the production database. Instead, we use a **new testing database** with the `testing.db` file, which is great.
But SQLite also supports having an **in memory** database. This means that all the database is only in memory, and it is never saved in a file on disk.
@@ -171,7 +171,7 @@ Other alternatives and ideas 👀
</summary>
Before arriving at the idea of using an **in-memory database** we could have explored other alternatives and ideas.
The first, is that we are not deleting the file after we finish the test, so, the next test could have **leftover data**. So, the right thing would be to delete the file right after finishing the test. 🔥
The first is that we are not deleting the file after we finish the test, so the next test could have **leftover data**. So, the right thing would be to delete the file right after finishing the test. 🔥
But if each test has to create a new file and then delete it afterwards, running all the tests could be **a bit slow**.
@@ -179,7 +179,7 @@ Right now, we have a file `testing.db` that is used by all the tests (we only ha
So, if we tried to run the tests at the same time **in parallel** to try to speed things up a bit, they would clash trying to use the *same* `testing.db` file.
Of couse, we could also fix that, using some **random name** for each testing database file... but in the case of SQLite, we have an even better alternative with just using an **in-memory database**. ✨
Of course, we could also fix that, using some **random name** for each testing database file... but in the case of SQLite, we have an even better alternative by just using an **in-memory database**. ✨
</details>
@@ -208,7 +208,7 @@ And all the other tests can do the same.
Great, that works, and you could replicate all that process in each of the test functions.
But we had to add a lot of **boilerplate code** to handle the custom database, creating it in memory, the custom session, the dependency override.
But we had to add a lot of **boilerplate code** to handle the custom database, creating it in memory, the custom session, and the dependency override.
Do we really have to duplicate all that for **each test**? No, we can do better! 😎
@@ -242,12 +242,12 @@ Let's see the first code example with a fixture:
**pytest** fixtures work in a very similar way to FastAPI dependencies, but have some minor differences:
* In pytest fixtures we need to add a decorator of `@pytest.fixture()` on top.
* In pytest fixtures, we need to add a decorator of `@pytest.fixture()` on top.
* To use a pytest fixture in a function, we have to declare the parameter with the **exact same name**. In FastAPI we have to **explicitly use `Depends()`** with the actual function inside it.
But apart from the way we declare them and how we tell the framework that we want to have them in the function, they **work in a very similar way**.
Now we create lot's of tests, and re-use that same fixture in all of them, saving us that **boilerplate code**.
Now we create lot's of tests and re-use that same fixture in all of them, saving us that **boilerplate code**.
**pytest** will make sure to run them right before (and finish them right after) each test function. So, each test function will actually have its own database, engine, and session.
@@ -255,7 +255,7 @@ Now we create lot's of tests, and re-use that same fixture in all of them, savin
Awesome, that fixture helps us prevent a lot of duplicated code.
But currently we still have to write some code in the test function that will be repetitive for other tests, right now we:
But currently, we still have to write some code in the test function that will be repetitive for other tests, right now we:
* create the **dependency override**
* put it in the `app.dependency_overrides`
@@ -277,7 +277,7 @@ So, we can create a **client fixture** that will be used in all the tests, and i
!!! tip
Check out the number bubbles to see what is done by each line of code.
Now we have a **client fixture** that in turns uses the **session fixture**.
Now we have a **client fixture** that, in turn, uses the **session fixture**.
And in the actual test function, we just have to declare that we require this **client fixture**.
@@ -285,7 +285,7 @@ And in the actual test function, we just have to declare that we require this **
At this point, it all might seem like we just did a lot of changes for nothing, to get **the same result**. 🤔
But normally we will create **lots of other test functions**. And now all the boilerplate and complexity is **writen only once**, in those two fixtures.
But normally we will create **lots of other test functions**. And now all the boilerplate and complexity is **written only once**, in those two fixtures.
Let's add some more tests:
@@ -311,13 +311,13 @@ Let's add some more tests:
That's why we add these two extra tests here.
Now, any additional test functions can be as **simple** as the first one, they just have to **declate the `client` parameter** to get the `TestClient` **fixture** with all the database stuff setup. Nice! 😎
Now, any additional test functions can be as **simple** as the first one, they just have to **declare the `client` parameter** to get the `TestClient` **fixture** with all the database stuff setup. Nice! 😎
## Why Two Fixtures
Now, seeing the code we could think, why do we put **two fixtures** instead of **just one** with all the code? And that makes total sense!
Now, seeing the code, we could think, why do we put **two fixtures** instead of **just one** with all the code? And that makes total sense!
For these examples, **that would have been simpler**, there's no need to separate that code in two fixtures for them...
For these examples, **that would have been simpler**, there's no need to separate that code into two fixtures for them...
But for the next test function, we will require **both fixtures**, the **client** and the **session**.
@@ -340,7 +340,7 @@ But for the next test function, we will require **both fixtures**, the **client*
</details>
In this test function we want to check that the *path operation* to **read a list of heroes** actually sends us heroes.
In this test function, we want to check that the *path operation* to **read a list of heroes** actually sends us heroes.
But if the **database is empty**, we would get an **empty list**, and we wouldn't know if the hero data is being sent correctly or not.
@@ -362,7 +362,7 @@ The function for the **client fixture** and the actual testing function will **b
## Add the Rest of the Tests
Using the same ideas, requiring the fixtures, creating data that we need for the tests, etc. we can now add the rest of the tests, they look quite similar to what we have done up to now.
Using the same ideas, requiring the fixtures, creating data that we need for the tests, etc., we can now add the rest of the tests. They look quite similar to what we have done up to now.
```Python hl_lines="3 18 33"
# Code above omitted 👆
@@ -406,9 +406,9 @@ project/test_main.py <font color="#A6E22E">....... [100%]</font>
Did you read all that? Wow, I'm impressed! 😎
Adding tests to your application will give you a lot of **certainty** that everything is **working correctly**, as you indended.
Adding tests to your application will give you a lot of **certainty** that everything is **working correctly**, as you intended.
And tests will be notoriously useful when **refactoring** your code, **changing things**, **adding features**. Because tests they can help catch a lot of errors that can be easily introduced by refactoring.
And tests will be notoriously useful when **refactoring** your code, **changing things**, **adding features**. Because tests can help catch a lot of errors that can be easily introduced by refactoring.
And they will give you the confidence to work faster and **more efficiently**, because you know that you are checking if you are **not breaking anything**. 😅

View File

@@ -4,7 +4,7 @@ Now let's see how to update data in the database with a **FastAPI** *path operat
## `HeroUpdate` Model
We want clients to be able to udpate the `name`, the `secret_name`, and the `age` of a hero.
We want clients to be able to update the `name`, the `secret_name`, and the `age` of a hero.
But we don't want them to have to include all the data again just to **update a single field**.
@@ -61,7 +61,7 @@ We will use a `PATCH` HTTP operation. This is used to **partially update data**,
</details>
We also read the `hero_id` from the *path parameter* an the request body, a `HeroUpdate`.
We also read the `hero_id` from the *path parameter* and the request body, a `HeroUpdate`.
### Read the Existing Hero
@@ -100,7 +100,7 @@ But that also means that if we just call `hero.dict()` we will get a dictionary
}
```
And then if we update the hero in the database with this data, we would be removing any existing values, and that's probably **not what the client intended**.
And then, if we update the hero in the database with this data, we would be removing any existing values, and that's probably **not what the client intended**.
But fortunately Pydantic models (and so SQLModel models) have a parameter we can pass to the `.dict()` method for that: `exclude_unset=True`.
@@ -200,7 +200,7 @@ We are **not simply omitting** the data that has the **default values**.
And we are **not simply omitting** anything that is `None`.
This means that, if a model in the database **has a value different than the default**, the client could **reset it to the same value as the default**, or even `None`, and we would **still notice it** and **update it accordingly**. 🤯🚀
This means that if a model in the database **has a value different than the default**, the client could **reset it to the same value as the default**, or even `None`, and we would **still notice it** and **update it accordingly**. 🤯🚀
So, if the client wanted to intentionally remove the `age` of a hero, they could just send a JSON with:
@@ -218,11 +218,11 @@ And when getting the data with `hero.dict(exclude_unset=True)`, we would get:
}
```
So, we would use that value and upate the `age` to `None` in the database, **just as the client intended**.
So, we would use that value and update the `age` to `None` in the database, **just as the client intended**.
Notice that `age` here is `None`, and **we still detected it**.
Also that `name` was not even sent, and we don't *accidentaly* set it to `None` or something, we just didn't touch it, because the client didn't sent it, so we are **pefectly fine**, even in these corner cases. ✨
Also, that `name` was not even sent, and we don't *accidentally* set it to `None` or something. We just didn't touch it because the client didn't send it, so we are **perfectly fine**, even in these corner cases. ✨
These are some of the advantages of Pydantic, that we can use with SQLModel. 🎉

View File

@@ -2,11 +2,11 @@
## Type hints
If you need a refreshed about how to use Python type hints (type annotations), check <a href="https://fastapi.tiangolo.com/python-types/" class="external-link" target="_blank">FastAPI's Python types intro</a>.
If you need a refresher about how to use Python type hints (type annotations), check <a href="https://fastapi.tiangolo.com/python-types/" class="external-link" target="_blank">FastAPI's Python types intro</a>.
You can also check the <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">mypy cheat sheet</a>.
**SQLModel** uses type annotations for everything, this way you can use a familiar Python syntax and get all the editor support posible, with autocompletion and in-editor error checking.
**SQLModel** uses type annotations for everything, this way you can use a familiar Python syntax and get all the editor support possible, with autocompletion and in-editor error checking.
## Intro

View File

@@ -40,7 +40,7 @@ And **both fields are primary keys**. We hadn't used this before. 🤓
Let's see the `Team` model, it's almost identical as before, but with a little change:
```Python hl_lines="8"
# Code above ommited 👆
# Code above omitted 👆
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:15-20]!}
@@ -56,7 +56,7 @@ Let's see the `Team` model, it's almost identical as before, but with a little c
</details>
The **relationship attribute `heroes`** is still a list of heroes, annotatted as `List["Hero"]`. Again, we use `"Hero"` in quotes because we haven't declared that class yet by this point in the code (but as you know, editors and **SQLModel** understand that).
The **relationship attribute `heroes`** is still a list of heroes, annotated as `List["Hero"]`. Again, we use `"Hero"` in quotes because we haven't declared that class yet by this point in the code (but as you know, editors and **SQLModel** understand that).
We use the same **`Relationship()`** function.
@@ -69,7 +69,7 @@ And here's the important part to allow the **many-to-many** relationship, we use
Let's see the other side, here's the `Hero` model:
```Python hl_lines="9"
# Code above ommited 👆
# Code above omitted 👆
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:23-29]!}
@@ -102,7 +102,7 @@ And now we have a **`link_model=HeroTeamLink`**. ✨
The same as before, we will have the rest of the code to create the **engine**, and a function to create all the tables `create_db_and_tables()`.
```Python hl_lines="9"
# Code above ommited 👆
# Code above omitted 👆
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:32-39]!}
@@ -122,7 +122,7 @@ The same as before, we will have the rest of the code to create the **engine**,
And as in previous examples, we will add that function to a function `main()`, and we will call that `main()` function in the main block:
```Python hl_lines="4"
# Code above ommited 👆
# Code above omitted 👆
{!./docs_src/tutorial/many_to_many/tutorial001.py[ln:78-79]!}
# We will do more stuff here later 👈
@@ -149,7 +149,7 @@ If you run the code in the command line, it would output:
```console
$ python app.py
// Boilerplate ommited 😉
// Boilerplate omitted 😉
INFO Engine
CREATE TABLE team (

View File

@@ -60,7 +60,7 @@ Notice that each hero can only have **one** connection. But each team can receiv
## Introduce Many-to-Many
But let's say that as **Deadpond** is a great chracter, they recruit him to the new **Preventers** team, but he's still part of the **Z-Force** team too.
But let's say that as **Deadpond** is a great character, they recruit him to the new **Preventers** team, but he's still part of the **Z-Force** team too.
So, now, we need to be able to have a hero that is connected to **many** teams. And then, each team, should still be able to receive **many** heroes. So we need a **Many-to-Many** relationship.

View File

@@ -96,7 +96,7 @@ Next, use that `Relationship` to declare a new attribute in the model classes:
## What Are These Relationship Attributes
This new attributes are not the same as fields, they **don't represent a column** directly in the database, and their value is not a singular value like an integer. Their value is the actual **entire object** that is related.
These new attributes are not the same as fields, they **don't represent a column** directly in the database, and their value is not a singular value like an integer. Their value is the actual **entire object** that is related.
So, in the case of a `Hero` instance, if you call `hero.team`, you will get the entire `Team` instance object that this hero belongs to. ✨

View File

@@ -88,7 +88,7 @@ You can try that out in **DB Browser for SQLite**:
### A SQL Shortcut
If we want to get all the columns like in this case above, in SQL there's a shortcut, instead of specifying each of the column names wew could write a `*`:
If we want to get all the columns like in this case above, in SQL there's a shortcut, instead of specifying each of the column names we could write a `*`:
```SQL
SELECT *

View File

@@ -865,7 +865,7 @@ It would be an error telling you that
This is because as we are using pure and plain Python annotations for the fields, `age` is indeed annotated as `Optional[int]`, which means `int` or `None`.
By using this simple and standard Python type annotations We get the benefit of the extra simplicity and the inline error checks when creating or using instances. ✨
By using this simple and standard Python type annotations we get the benefit of the extra simplicity and the inline error checks when creating or using instances. ✨
And when we use these special **class attributes** in a `.where()`, during execution of the program, the special class attribute will know that the comparison only applies for the values that are not `NULL` in the database, and it will work correctly.

View File

@@ -24,7 +24,6 @@ class TeamRead(TeamBase):
class TeamUpdate(SQLModel):
id: Optional[int] = None
name: Optional[str] = None
headquarters: Optional[str] = None

View File

@@ -36,7 +36,7 @@ class Hero(SQLModel, table=True):
team: Optional[Team] = Relationship(back_populates="heroes")
weapon_id: Optional[int] = Field(default=None, foreign_key="weapon.id")
weapon: Optional[Weapon] = Relationship(back_populates="owner")
weapon: Optional[Weapon] = Relationship(back_populates="hero")
powers: List[Power] = Relationship(back_populates="hero")

View File

@@ -99,7 +99,7 @@ def select_heroes():
result = session.exec(statement)
hero_spider_boy = result.one()
statement = select(Team).where(Team.id == hero_spider_boy.id)
statement = select(Team).where(Team.id == hero_spider_boy.team_id)
result = session.exec(statement)
team = result.first()
print("Spider-Boy's team:", team)

View File

@@ -3,6 +3,7 @@ site_description: SQLModel, SQL databases in Python, designed for simplicity, co
site_url: https://sqlmodel.tiangolo.com/
theme:
name: material
custom_dir: docs/overrides
palette:
- scheme: default
primary: deep purple
@@ -28,9 +29,6 @@ theme:
repo_name: tiangolo/sqlmodel
repo_url: https://github.com/tiangolo/sqlmodel
edit_uri: ''
google_analytics:
- UA-205713594-2
- auto
nav:
- SQLModel: index.md
- features.md
@@ -104,12 +102,15 @@ markdown_extensions:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_div_format ''
format: !!python/name:pymdownx.superfences.fence_code_format ''
- pymdownx.tabbed:
alternate_style: true
- mdx_include
extra:
analytics:
provider: google
property: UA-205713594-2
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/sqlmodel
@@ -129,6 +130,5 @@ extra_css:
- css/custom.css
extra_javascript:
- https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js
- js/termynal.js
- js/custom.js

View File

@@ -31,20 +31,20 @@ classifiers = [
[tool.poetry.dependencies]
python = "^3.6.1"
SQLAlchemy = ">=1.4.17,<1.5.0"
SQLAlchemy = ">=1.4.17,<=1.4.41"
pydantic = "^1.8.2"
sqlalchemy2-stubs = {version = "*", allow-prereleases = true}
[tool.poetry.dev-dependencies]
pytest = "^6.2.4"
mypy = "^0.910"
mypy = "0.930"
flake8 = "^3.9.2"
black = {version = "^21.5-beta.1", python = "^3.7"}
mkdocs = "^1.2.1"
mkdocs-material = "^7.1.9"
mkdocs-material = "^8.1.4"
mdx-include = "^1.4.1"
coverage = {extras = ["toml"], version = "^5.5"}
fastapi = "^0.68.0"
fastapi = "^0.68.1"
requests = "^2.26.0"
autoflake = "^1.4"
isort = "^5.9.3"
@@ -102,3 +102,5 @@ strict_equality = true
[[tool.mypy.overrides]]
module = "sqlmodel.sql.expression"
warn_unused_ignores = false
# invalidate CI cache: 1

View File

@@ -1,4 +1,4 @@
__version__ = "0.0.6"
__version__ = "0.0.7"
# Re-export from SQLAlchemy
from sqlalchemy.engine import create_mock_engine as create_mock_engine

View File

@@ -6,7 +6,6 @@ from decimal import Decimal
from enum import Enum
from pathlib import Path
from typing import (
TYPE_CHECKING,
AbstractSet,
Any,
Callable,
@@ -24,25 +23,17 @@ from typing import (
cast,
)
from pydantic import BaseModel
from pydantic import BaseConfig, BaseModel
from pydantic.errors import ConfigError, DictError
from pydantic.fields import SHAPE_SINGLETON
from pydantic.fields import FieldInfo as PydanticFieldInfo
from pydantic.fields import ModelField, Undefined, UndefinedType
from pydantic.main import BaseConfig, ModelMetaclass, validate_model
from pydantic.main import ModelMetaclass, validate_model
from pydantic.typing import ForwardRef, NoArgAnyCallable, resolve_annotations
from pydantic.utils import ROOT_KEY, Representation
from sqlalchemy import (
Boolean,
Column,
Date,
DateTime,
Float,
ForeignKey,
Integer,
Interval,
Numeric,
inspect,
)
from sqlalchemy import Boolean, Column, Date, DateTime
from sqlalchemy import Enum as sa_Enum
from sqlalchemy import Float, ForeignKey, Integer, Interval, Numeric, inspect
from sqlalchemy.orm import RelationshipProperty, declared_attr, registry, relationship
from sqlalchemy.orm.attributes import set_attribute
from sqlalchemy.orm.decl_api import DeclarativeMeta
@@ -70,6 +61,7 @@ class FieldInfo(PydanticFieldInfo):
primary_key = kwargs.pop("primary_key", False)
nullable = kwargs.pop("nullable", Undefined)
foreign_key = kwargs.pop("foreign_key", Undefined)
unique = kwargs.pop("unique", False)
index = kwargs.pop("index", Undefined)
sa_column = kwargs.pop("sa_column", Undefined)
sa_column_args = kwargs.pop("sa_column_args", Undefined)
@@ -89,6 +81,7 @@ class FieldInfo(PydanticFieldInfo):
self.primary_key = primary_key
self.nullable = nullable
self.foreign_key = foreign_key
self.unique = unique
self.index = index
self.sa_column = sa_column
self.sa_column_args = sa_column_args
@@ -150,6 +143,7 @@ def Field(
regex: Optional[str] = None,
primary_key: bool = False,
foreign_key: Optional[Any] = None,
unique: bool = False,
nullable: Union[bool, UndefinedType] = Undefined,
index: Union[bool, UndefinedType] = Undefined,
sa_column: Union[Column, UndefinedType] = Undefined, # type: ignore
@@ -180,6 +174,7 @@ def Field(
regex=regex,
primary_key=primary_key,
foreign_key=foreign_key,
unique=unique,
nullable=nullable,
index=index,
sa_column=sa_column,
@@ -370,6 +365,7 @@ class SQLModelMetaclass(ModelMetaclass, DeclarativeMeta):
relationship_to, *rel_args, **rel_kwargs
)
dict_used[rel_name] = rel_value
setattr(cls, rel_name, rel_value) # Fix #315
DeclarativeMeta.__init__(cls, classname, bases, dict_used, **kw)
else:
ModelMetaclass.__init__(cls, classname, bases, dict_, **kw)
@@ -395,7 +391,7 @@ def get_sqlachemy_type(field: ModelField) -> Any:
if issubclass(field.type_, time):
return Time
if issubclass(field.type_, Enum):
return Enum
return sa_Enum(field.type_)
if issubclass(field.type_, bytes):
return LargeBinary
if issubclass(field.type_, Decimal):
@@ -415,6 +411,7 @@ def get_sqlachemy_type(field: ModelField) -> Any:
return AutoString
if issubclass(field.type_, uuid.UUID):
return GUID
raise ValueError(f"The field {field.name} has no matching SQLAlchemy type")
def get_column_from_field(field: ModelField) -> Column: # type: ignore
@@ -423,7 +420,6 @@ def get_column_from_field(field: ModelField) -> Column: # type: ignore
return sa_column
sa_type = get_sqlachemy_type(field)
primary_key = getattr(field.field_info, "primary_key", False)
nullable = not field.required
index = getattr(field.field_info, "index", Undefined)
if index is Undefined:
index = False
@@ -431,14 +427,17 @@ def get_column_from_field(field: ModelField) -> Column: # type: ignore
field_nullable = getattr(field.field_info, "nullable")
if field_nullable != Undefined:
nullable = field_nullable
nullable = not primary_key and _is_field_nullable(field)
args = []
foreign_key = getattr(field.field_info, "foreign_key", None)
unique = getattr(field.field_info, "unique", False)
if foreign_key:
args.append(ForeignKey(foreign_key))
kwargs = {
"primary_key": primary_key,
"nullable": nullable,
"index": index,
"unique": unique,
}
sa_default = Undefined
if field.field_info.default_factory:
@@ -453,7 +452,7 @@ def get_column_from_field(field: ModelField) -> Column: # type: ignore
sa_column_kwargs = getattr(field.field_info, "sa_column_kwargs", Undefined)
if sa_column_kwargs is not Undefined:
kwargs.update(cast(Dict[Any, Any], sa_column_kwargs))
return Column(sa_type, *args, **kwargs)
return Column(sa_type, *args, **kwargs) # type: ignore
class_registry = weakref.WeakValueDictionary() # type: ignore
@@ -494,9 +493,6 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry
def __init__(__pydantic_self__, **data: Any) -> None:
# Uses something other than `self` the first arg to allow "self" as a
# settable attribute
if TYPE_CHECKING:
__pydantic_self__.__dict__: Dict[str, Any] = {}
__pydantic_self__.__fields_set__: Set[str] = set()
values, fields_set, validation_error = validate_model(
__pydantic_self__.__class__, data
)
@@ -509,9 +505,9 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry
# Do not set values as in Pydantic, pass them through setattr, so SQLAlchemy
# can handle them
# object.__setattr__(__pydantic_self__, '__dict__', values)
object.__setattr__(__pydantic_self__, "__fields_set__", fields_set)
for key, value in values.items():
setattr(__pydantic_self__, key, value)
object.__setattr__(__pydantic_self__, "__fields_set__", fields_set)
non_pydantic_keys = data.keys() - values.keys()
for key in non_pydantic_keys:
if key in __pydantic_self__.__sqlmodel_relationships__:
@@ -523,9 +519,8 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry
return
else:
# Set in SQLAlchemy, before Pydantic to trigger events and updates
if getattr(self.__config__, "table", False):
if is_instrumented(self, name):
set_attribute(self, name, value)
if getattr(self.__config__, "table", False) and is_instrumented(self, name):
set_attribute(self, name, value)
# Set in Pydantic model to trigger possible validation changes, only for
# non relationship values
if name not in self.__sqlmodel_relationships__:
@@ -568,8 +563,8 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry
@classmethod
def parse_obj(
cls: Type["SQLModel"], obj: Any, update: Optional[Dict[str, Any]] = None
) -> "SQLModel":
cls: Type[_TSQLModel], obj: Any, update: Optional[Dict[str, Any]] = None
) -> _TSQLModel:
obj = cls._enforce_dict_if_root(obj)
# SQLModel, support update dict
if update is not None:
@@ -583,7 +578,7 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry
# From Pydantic, override to enforce validation with dict
@classmethod
def validate(cls: Type["SQLModel"], value: Any) -> "SQLModel":
def validate(cls: Type[_TSQLModel], value: Any) -> _TSQLModel:
if isinstance(value, cls):
return value.copy() if cls.__config__.copy_on_model_validation else value
@@ -592,7 +587,7 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry
values, fields_set, validation_error = validate_model(cls, value)
if validation_error:
raise validation_error
model = cls(**values)
model = cls(**value)
# Reset fields set, this would have been done in Pydantic in __init__
object.__setattr__(model, "__fields_set__", fields_set)
return model
@@ -608,14 +603,14 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry
return cls(**value_as_dict)
# From Pydantic, override to only show keys from fields, omit SQLAlchemy attributes
def _calculate_keys( # type: ignore
def _calculate_keys(
self,
include: Optional[Mapping[Union[int, str], Any]],
exclude: Optional[Mapping[Union[int, str], Any]],
exclude_unset: bool,
update: Optional[Dict[str, Any]] = None,
) -> Optional[AbstractSet[str]]:
if include is None and exclude is None and exclude_unset is False:
if include is None and exclude is None and not exclude_unset:
# Original in Pydantic:
# return None
# Updated to not return SQLAlchemy attributes
@@ -633,7 +628,6 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry
# Do not include relationships as that would easily lead to infinite
# recursion, or traversing the whole database
keys = self.__fields__.keys() # | self.__sqlmodel_relationships__.keys()
if include is not None:
keys &= include.keys()
@@ -648,3 +642,13 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry
@declared_attr # type: ignore
def __tablename__(cls) -> str:
return cls.__name__.lower()
def _is_field_nullable(field: ModelField) -> bool:
if not field.required:
# Taken from [Pydantic](https://github.com/samuelcolvin/pydantic/blob/v1.8.2/pydantic/fields.py#L946-L947)
is_optional = field.allow_none and (
field.shape != SHAPE_SINGLETON or not field.sub_fields
)
return is_optional and field.default is None and field.default_factory is None
return False

View File

@@ -128,6 +128,7 @@ class Session(_Session):
populate_existing: bool = False,
with_for_update: Optional[Union[Literal[True], Mapping[str, Any]]] = None,
identity_token: Optional[Any] = None,
execution_options: Optional[Mapping[Any, Any]] = util.EMPTY_DICT,
) -> Optional[_TSelectParam]:
return super().get(
entity,
@@ -136,4 +137,5 @@ class Session(_Session):
populate_existing=populate_existing,
with_for_update=with_for_update,
identity_token=identity_token,
execution_options=execution_options,
)

View File

@@ -27,14 +27,14 @@ _TSelect = TypeVar("_TSelect")
if sys.version_info.minor >= 7:
class Select(_Select, Generic[_TSelect]):
pass
inherit_cache = True
# This is not comparable to sqlalchemy.sql.selectable.ScalarSelect, that has a different
# purpose. This is the same as a normal SQLAlchemy Select class where there's only one
# entity, so the result will be converted to a scalar by default. This way writing
# for loops on the results will feel natural.
class SelectOfScalar(_Select, Generic[_TSelect]):
pass
inherit_cache = True
else:
from typing import GenericMeta # type: ignore
@@ -43,10 +43,10 @@ else:
pass
class _Py36Select(_Select, Generic[_TSelect], metaclass=GenericSelectMeta):
pass
inherit_cache = True
class _Py36SelectOfScalar(_Select, Generic[_TSelect], metaclass=GenericSelectMeta):
pass
inherit_cache = True
# Cast them for editors to work correctly, from several tricks tried, this works
# for both VS Code and PyCharm

View File

@@ -1,11 +1,10 @@
import uuid
from typing import Any, Optional, cast
from sqlalchemy import types
from sqlalchemy import CHAR, types
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.engine.interfaces import Dialect
from sqlalchemy.sql.type_api import TypeEngine
from sqlalchemy.types import CHAR, TypeDecorator
class AutoString(types.TypeDecorator): # type: ignore
@@ -23,7 +22,7 @@ class AutoString(types.TypeDecorator): # type: ignore
# Reference form SQLAlchemy docs: https://docs.sqlalchemy.org/en/14/core/custom_types.html#backend-agnostic-guid-type
# with small modifications
class GUID(TypeDecorator): # type: ignore
class GUID(types.TypeDecorator): # type: ignore
"""Platform-independent GUID type.
Uses PostgreSQL's UUID type, otherwise uses
@@ -47,10 +46,10 @@ class GUID(TypeDecorator): # type: ignore
return str(value)
else:
if not isinstance(value, uuid.UUID):
return f"{uuid.UUID(value).int:x}"
return uuid.UUID(value).hex
else:
# hexstring
return f"{value.int:x}"
return value.hex
def process_result_value(self, value: Any, dialect: Dialect) -> Optional[uuid.UUID]:
if value is None:

72
tests/test_enums.py Normal file
View File

@@ -0,0 +1,72 @@
import enum
import uuid
from sqlalchemy import create_mock_engine
from sqlalchemy.sql.type_api import TypeEngine
from sqlmodel import Field, SQLModel
"""
Tests related to Enums
Associated issues:
* https://github.com/tiangolo/sqlmodel/issues/96
* https://github.com/tiangolo/sqlmodel/issues/164
"""
class MyEnum1(enum.Enum):
A = "A"
B = "B"
class MyEnum2(enum.Enum):
C = "C"
D = "D"
class BaseModel(SQLModel):
id: uuid.UUID = Field(primary_key=True)
enum_field: MyEnum2
class FlatModel(SQLModel, table=True):
id: uuid.UUID = Field(primary_key=True)
enum_field: MyEnum1
class InheritModel(BaseModel, table=True):
pass
def pg_dump(sql: TypeEngine, *args, **kwargs):
dialect = sql.compile(dialect=postgres_engine.dialect)
sql_str = str(dialect).rstrip()
if sql_str:
print(sql_str + ";")
def sqlite_dump(sql: TypeEngine, *args, **kwargs):
dialect = sql.compile(dialect=sqlite_engine.dialect)
sql_str = str(dialect).rstrip()
if sql_str:
print(sql_str + ";")
postgres_engine = create_mock_engine("postgresql://", pg_dump)
sqlite_engine = create_mock_engine("sqlite://", sqlite_dump)
def test_postgres_ddl_sql(capsys):
SQLModel.metadata.create_all(bind=postgres_engine, checkfirst=False)
captured = capsys.readouterr()
assert "CREATE TYPE myenum1 AS ENUM ('A', 'B');" in captured.out
assert "CREATE TYPE myenum2 AS ENUM ('C', 'D');" in captured.out
def test_sqlite_ddl_sql(capsys):
SQLModel.metadata.create_all(bind=sqlite_engine, checkfirst=False)
captured = capsys.readouterr()
assert "enum_field VARCHAR(1) NOT NULL" in captured.out
assert "CREATE TYPE" not in captured.out

21
tests/test_fields_set.py Normal file
View File

@@ -0,0 +1,21 @@
from datetime import datetime, timedelta
from sqlmodel import Field, SQLModel
def test_fields_set():
class User(SQLModel):
username: str
email: str = "test@test.com"
last_updated: datetime = Field(default_factory=datetime.now)
user = User(username="bob")
assert user.__fields_set__ == {"username"}
user = User(username="bob", email="bob@test.com")
assert user.__fields_set__ == {"username", "email"}
user = User(
username="bob",
email="bob@test.com",
last_updated=datetime.now() - timedelta(days=1),
)
assert user.__fields_set__ == {"username", "email", "last_updated"}

93
tests/test_main.py Normal file
View File

@@ -0,0 +1,93 @@
from typing import Optional
import pytest
from sqlalchemy.exc import IntegrityError
from sqlmodel import Field, Session, SQLModel, create_engine
def test_should_allow_duplicate_row_if_unique_constraint_is_not_passed(clear_sqlmodel):
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")
hero_2 = Hero(name="Deadpond", secret_name="Dive Wilson")
engine = create_engine("sqlite://")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
session.add(hero_1)
session.commit()
session.refresh(hero_1)
with Session(engine) as session:
session.add(hero_2)
session.commit()
session.refresh(hero_2)
with Session(engine) as session:
heroes = session.query(Hero).all()
assert len(heroes) == 2
assert heroes[0].name == heroes[1].name
def test_should_allow_duplicate_row_if_unique_constraint_is_false(clear_sqlmodel):
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str = Field(unique=False)
age: Optional[int] = None
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
hero_2 = Hero(name="Deadpond", secret_name="Dive Wilson")
engine = create_engine("sqlite://")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
session.add(hero_1)
session.commit()
session.refresh(hero_1)
with Session(engine) as session:
session.add(hero_2)
session.commit()
session.refresh(hero_2)
with Session(engine) as session:
heroes = session.query(Hero).all()
assert len(heroes) == 2
assert heroes[0].name == heroes[1].name
def test_should_raise_exception_when_try_to_duplicate_row_if_unique_constraint_is_true(
clear_sqlmodel,
):
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str = Field(unique=True)
age: Optional[int] = None
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
hero_2 = Hero(name="Deadpond", secret_name="Dive Wilson")
engine = create_engine("sqlite://")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
session.add(hero_1)
session.commit()
session.refresh(hero_1)
with pytest.raises(IntegrityError):
with Session(engine) as session:
session.add(hero_2)
session.commit()
session.refresh(hero_2)

View File

@@ -0,0 +1,21 @@
from typing import Optional
import pytest
from sqlmodel import Field, SQLModel
def test_missing_sql_type():
class CustomType:
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, v):
return v
with pytest.raises(ValueError):
class Item(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
item: CustomType

View File

@@ -9,7 +9,7 @@ def test_create_db_and_table(cov_tmp_path: Path):
assert "BEGIN" in result.stdout
assert 'PRAGMA main.table_info("hero")' in result.stdout
assert "CREATE TABLE hero (" in result.stdout
assert "id INTEGER," in result.stdout
assert "id INTEGER NOT NULL," in result.stdout
assert "name VARCHAR NOT NULL," in result.stdout
assert "secret_name VARCHAR NOT NULL," in result.stdout
assert "age INTEGER," in result.stdout

View File

@@ -442,7 +442,6 @@ openapi_schema = {
"title": "TeamUpdate",
"type": "object",
"properties": {
"id": {"title": "Id", "type": "integer"},
"name": {"title": "Name", "type": "string"},
"headquarters": {"title": "Headquarters", "type": "string"},
},

View File

@@ -81,7 +81,7 @@ expected_calls = [
],
[
"Spider-Boy's team:",
{"headquarters": "Wakaland Capital City", "id": 3, "name": "Wakaland"},
{"headquarters": "Sharp Tower", "id": 2, "name": "Preventers"},
],
[
"Spider-Boy's team again:",

33
tests/test_validation.py Normal file
View File

@@ -0,0 +1,33 @@
from typing import Optional
import pytest
from pydantic import validator
from pydantic.error_wrappers import ValidationError
from sqlmodel import SQLModel
def test_validation(clear_sqlmodel):
"""Test validation of implicit and explict None values.
# For consistency with pydantic, validators are not to be called on
# arguments that are not explicitly provided.
https://github.com/tiangolo/sqlmodel/issues/230
https://github.com/samuelcolvin/pydantic/issues/1223
"""
class Hero(SQLModel):
name: Optional[str] = None
secret_name: Optional[str] = None
age: Optional[int] = None
@validator("name", "secret_name", "age")
def reject_none(cls, v):
assert v is not None
return v
Hero.validate({"age": 25})
with pytest.raises(ValidationError):
Hero.validate({"name": None, "age": 25})