🔧 Update config with new pymdown extensions (#712)

* 🔧 Update config with new pymdown extensions

* 📝 Update admonition blocks syntax

* 📝 Update syntax for tabs with new pymdown extensions
This commit is contained in:
Sebastián Ramírez 2023-11-28 21:50:33 +01:00 committed by GitHub
parent 71baff6015
commit a95bd3873d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 702 additions and 353 deletions

View File

@ -21,16 +21,22 @@ In most cases this would probably not be a problem, for example measuring views
Pydantic has special support for `Decimal` types using the <a href="https://pydantic-docs.helpmanual.io/usage/types/#arguments-to-condecimal" class="external-link" target="_blank">`condecimal()` special function</a>. Pydantic has special support for `Decimal` types using the <a href="https://pydantic-docs.helpmanual.io/usage/types/#arguments-to-condecimal" class="external-link" target="_blank">`condecimal()` special function</a>.
!!! tip /// tip
Pydantic 1.9, that will be released soon, has improved support for `Decimal` types, without needing to use the `condecimal()` function. Pydantic 1.9, that will be released soon, has improved support for `Decimal` types, without needing to use the `condecimal()` function.
But meanwhile, you can already use this feature with `condecimal()` in **SQLModel** it as it's explained here. But meanwhile, you can already use this feature with `condecimal()` in **SQLModel** it as it's explained here.
///
When you use `condecimal()` you can specify the number of digits and decimal places to support. They will be validated by Pydantic (for example when using FastAPI) and the same information will also be used for the database columns. When you use `condecimal()` you can specify the number of digits and decimal places to support. They will be validated by Pydantic (for example when using FastAPI) and the same information will also be used for the database columns.
!!! info /// info
For the database, **SQLModel** will use <a href="https://docs.sqlalchemy.org/en/14/core/type_basics.html#sqlalchemy.types.DECIMAL" class="external-link" target="_blank">SQLAlchemy's `DECIMAL` type</a>. For the database, **SQLModel** will use <a href="https://docs.sqlalchemy.org/en/14/core/type_basics.html#sqlalchemy.types.DECIMAL" class="external-link" target="_blank">SQLAlchemy's `DECIMAL` type</a>.
///
## Decimals in SQLModel ## Decimals in SQLModel
Let's say that each hero in the database will have an amount of money. We could make that field a `Decimal` type using the `condecimal()` function: Let's say that each hero in the database will have an amount of money. We could make that field a `Decimal` type using the `condecimal()` function:
@ -72,9 +78,12 @@ We are also saying that the number of decimal places (to the right of the decima
* `123` * `123`
* Even though this number doesn't have any decimals, we still have 3 places saved for them, which means that we can **only use 2 places** for the **integer part**, and this number has 3 integer digits. So, the allowed number of integer digits is `max_digits` - `decimal_places` = 2. * Even though this number doesn't have any decimals, we still have 3 places saved for them, which means that we can **only use 2 places** for the **integer part**, and this number has 3 integer digits. So, the allowed number of integer digits is `max_digits` - `decimal_places` = 2.
!!! tip /// tip
Make sure you adjust the number of digits and decimal places for your own needs, in your own application. 🤓 Make sure you adjust the number of digits and decimal places for your own needs, in your own application. 🤓
///
## Create models with Decimals ## Create models with Decimals
When creating new models you can actually pass normal (`float`) numbers, Pydantic will automatically convert them to `Decimal` types, and **SQLModel** will store them as `Decimal` types in the database (using SQLAlchemy). When creating new models you can actually pass normal (`float`) numbers, Pydantic will automatically convert them to `Decimal` types, and **SQLModel** will store them as `Decimal` types in the database (using SQLAlchemy).
@ -142,7 +151,10 @@ Total money: 3.300
</div> </div>
!!! warning /// warning
Although Decimal types are supported and used in the Python side, not all databases support it. In particular, SQLite doesn't support decimals, so it will convert them to the same floating `NUMERIC` type it supports. Although Decimal types are supported and used in the Python side, not all databases support it. In particular, SQLite doesn't support decimals, so it will convert them to the same floating `NUMERIC` type it supports.
But decimals are supported by most of the other SQL databases. 🎉 But decimals are supported by most of the other SQL databases. 🎉
///

View File

@ -1,10 +1,13 @@
# Intro to Databases # Intro to Databases
!!! info /// info
Are you a seasoned developer and already know everything about databases? 🤓 Are you a seasoned developer and already know everything about databases? 🤓
Then you can skip to the [Tutorial - User Guide: First Steps](tutorial/index.md){.internal-link target=_blank} right away. Then you can skip to the [Tutorial - User Guide: First Steps](tutorial/index.md){.internal-link target=_blank} right away.
///
If you don't know everything about databases, here's a quick overview. If you don't know everything about databases, here's a quick overview.
You can always study much more on your own later. You can always study much more on your own later.
@ -17,9 +20,12 @@ So, what is a database?
A **database** is a system to store and manage data in a structured and very efficient way. A **database** is a system to store and manage data in a structured and very efficient way.
!!! tip /// tip
It's very common to abbreviate the word "database" as **"DB"**. It's very common to abbreviate the word "database" as **"DB"**.
///
As there's a lot of information about databases, and it can get very technical and academic, I'll give you a quick overview about some of the main concepts here. As there's a lot of information about databases, and it can get very technical and academic, I'll give you a quick overview about some of the main concepts here.
I'll even tell you a bit about different types of databases, including the ones not covered by SQLModel ("NoSQL" databases). I'll even tell you a bit about different types of databases, including the ones not covered by SQLModel ("NoSQL" databases).
@ -28,9 +34,12 @@ I'll even tell you a bit about different types of databases, including the ones
When starting to program, it might **not be obvious** why having a database apart from the code for your program is a **good idea**. Let's start with that. When starting to program, it might **not be obvious** why having a database apart from the code for your program is a **good idea**. Let's start with that.
!!! tip /// tip
If that's obvious to you, just continue in the next section below. 👇 If that's obvious to you, just continue in the next section below. 👇
///
In your code you already have **variables**, **dictionaries**, **lists**, etc. They all store **data** in some way already. Why would you need to have a separate database? In your code you already have **variables**, **dictionaries**, **lists**, etc. They all store **data** in some way already. Why would you need to have a separate database?
If you look closely, your code is **static**, it doesn't really change over time *once you run it*. Of course, you change the code frequently, adding features, etc, but once you start Python running your code, the program stays as it was when you started it. And if you change the code, the program will only change **once you run it again**. If you look closely, your code is **static**, it doesn't really change over time *once you run it*. Of course, you change the code frequently, adding features, etc, but once you start Python running your code, the program stays as it was when you started it. And if you change the code, the program will only change **once you run it again**.
@ -308,9 +317,12 @@ Next, it receives the data and puts it in Python objects that you can continue t
I'll tell you more about SQL, SQLModel, how to use them, and how they are related in the next sections. I'll tell you more about SQL, SQLModel, how to use them, and how they are related in the next sections.
!!! info "Technical Details" /// info | Technical Details
SQLModel is built on top of SQLAlchemy. It is, in fact, just <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> and <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a> mixed together with some sugar on top. SQLModel is built on top of SQLAlchemy. It is, in fact, just <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> and <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a> mixed together with some sugar on top.
///
## NoSQL Databases ## NoSQL Databases
Although SQL Databases are the oldest and most commonly used type of database, there's another (very interesting) category, the one of **NoSQL Databases**. Although SQL Databases are the oldest and most commonly used type of database, there's another (very interesting) category, the one of **NoSQL Databases**.

View File

@ -172,9 +172,12 @@ The difference in the final SQL statement is subtle, but it changes the meaning
SELECT * FROM hero WHERE id = "2; DROP TABLE hero;"; SELECT * FROM hero WHERE id = "2; DROP TABLE hero;";
``` ```
!!! tip /// tip
Notice the double quotes (`"`) making it a string instead of more raw SQL. Notice the double quotes (`"`) making it a string instead of more raw SQL.
///
The database will not find any record with that ID: The database will not find any record with that ID:
```SQL ```SQL
@ -187,9 +190,12 @@ Then your code will continue to execute and calmly tell the user that it couldn'
But we never deleted the `hero` table. 🎉 But we never deleted the `hero` table. 🎉
!!! info /// info
Of course, there are also other ways to do SQL data sanitization without using a tool like **SQLModel**, but it's still a nice feature you get by default. Of course, there are also other ways to do SQL data sanitization without using a tool like **SQLModel**, but it's still a nice feature you get by default.
///
### Editor Support ### Editor Support
Check that Python snippet above again. Check that Python snippet above again.
@ -291,9 +297,12 @@ There are many ORMs available apart from **SQLModel**, you can read more about s
## SQL Table Names ## SQL Table Names
!!! info "Technical Background" /// info | Technical Background
This is a bit of boring background for SQL purists. Feel free to skip this section. 😉 This is a bit of boring background for SQL purists. Feel free to skip this section. 😉
///
When working with pure SQL, it's common to name the tables in plural. So, the table would be named `heroes` instead of `hero`, because it could contain multiple rows, each with one hero. When working with pure SQL, it's common to name the tables in plural. So, the table would be named `heroes` instead of `hero`, because it could contain multiple rows, each with one hero.
Nevertheless, **SQLModel** and many other similar tools can generate a table name automatically from your code, as you will see later in the tutorial. Nevertheless, **SQLModel** and many other similar tools can generate a table name automatically from your code, as you will see later in the tutorial.
@ -304,5 +313,8 @@ 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. So, to keep things consistent, I'll keep using the same table names that **SQLModel** would have generated.
!!! tip /// 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

@ -40,13 +40,16 @@ You won't need to keep guessing the types of different attributes in your models
<img class="shadow" src="/img/index/autocompletion01.png"> <img class="shadow" src="/img/index/autocompletion01.png">
!!! info /// info
Don't worry, adopting this in-development standard only affects/improves editor support. Don't worry, adopting this in-development standard only affects/improves editor support.
It doesn't affect performance or correctness. And if the in-progress standard was deprecated your code won't be affected. It doesn't affect performance or correctness. And if the in-progress standard was deprecated your code won't be affected.
Meanwhile, you will get inline errors (like type checks) and autocompletion on places you wouldn't get with any other library. 🎉 Meanwhile, you will get inline errors (like type checks) and autocompletion on places you wouldn't get with any other library. 🎉
///
## Short ## Short
**SQLModel** has **sensible defaults** for everything, with **optional configurations** everywhere. **SQLModel** has **sensible defaults** for everything, with **optional configurations** everywhere.

View File

@ -157,13 +157,16 @@ And if there's any other style or consistency need, I'll ask directly for that,
* Then **comment** saying that you did that, that's how I will know you really checked it. * Then **comment** saying that you did that, that's how I will know you really checked it.
!!! info /// info
Unfortunately, I can't simply trust PRs that just have several approvals. 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. 😅 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. 🤓 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. * 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 ### Tests
@ -209,11 +212,14 @@ If you can help me with that, **you are helping me maintain SQLModel** and makin
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. 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 /// 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. 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. Use the chat only for other general conversations.
///
### Don't use the chat for questions ### 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. 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.

View File

@ -445,11 +445,14 @@ Hero 3: age=48 id=3 name='Rusty-Man' secret_name='Tommy Sharp'
Now let's review all this code once again. Now let's review all this code once again.
!!! tip /// tip
Each one of the numbered bubbles shows what each line will print in the output. Each one of the numbered bubbles shows what each line will print in the output.
And as we created the **engine** with `echo=True`, we can see the SQL statements being executed at each step. And as we created the **engine** with `echo=True`, we can see the SQL statements being executed at each step.
///
```{ .python .annotate } ```{ .python .annotate }
{!./docs_src/tutorial/automatic_id_none_refresh/tutorial002.py!} {!./docs_src/tutorial/automatic_id_none_refresh/tutorial002.py!}
``` ```

View File

@ -149,11 +149,14 @@ Let's say that for some reason you hate the idea of having all the database mode
You can also do it. 😎 There's a couple of things to keep in mind. 🤓 You can also do it. 😎 There's a couple of things to keep in mind. 🤓
!!! warning /// warning
This is a bit more advanced. This is a bit more advanced.
If the solution above already worked for you, that might be enough for you, and you can continue in the next chapter. 🤓 If the solution above already worked for you, that might be enough for you, and you can continue in the next chapter. 🤓
///
Let's assume that now the file structure is: Let's assume that now the file structure is:
``` ```

View File

@ -37,9 +37,12 @@ Each row in the table `hero` will point to a row in the table `team`:
<img alt="table relationships" src="/img/tutorial/relationships/select/relationships2.svg"> <img alt="table relationships" src="/img/tutorial/relationships/select/relationships2.svg">
!!! info /// info
We will later update **Spider-Boy** to add him to the **Preventers** team too, but not yet. We will later update **Spider-Boy** to add him to the **Preventers** team too, but not yet.
///
We will continue with the code in the previous example and we will add more things to it. We will continue with the code in the previous example and we will add more things to it.
<details> <details>

View File

@ -126,9 +126,12 @@ This is the name of the **table** in the database, so it is `"team"`, not the na
If you had a custom table name, you would use that custom table name. If you had a custom table name, you would use that custom table name.
!!! info /// info
You can learn about setting a custom table name for a model in the Advanced User Guide. You can learn about setting a custom table name for a model in the Advanced User Guide.
///
### Create the Tables ### Create the Tables
Now we can add the same code as before to create the engine and the function to create the tables: Now we can add the same code as before to create the engine and the function to create the tables:
@ -167,9 +170,12 @@ And as before, we'll call this function from another function `main()`, and we'l
## Run the Code ## Run the Code
!!! tip /// tip
Before running the code, make sure you delete the file `database.db` to make sure you start from scratch. Before running the code, make sure you delete the file `database.db` to make sure you start from scratch.
///
If we run the code we have up to now, it will go and create the database file `database.db` and the tables in it we just defined, `team` and `hero`: If we run the code we have up to now, it will go and create the database file `database.db` and the tables in it we just defined, `team` and `hero`:
<div class="termy"> <div class="termy">

View File

@ -6,7 +6,10 @@ But the main advantage and feature of SQL databases is being able to handle rela
Let's see how to use **SQLModel** to manage connected data in the next chapters. 🤝 Let's see how to use **SQLModel** to manage connected data in the next chapters. 🤝
!!! tip /// tip
We will extend this further in the next group of chapters making it even more convenient to work with in Python code, using **relationship attributes**. We will extend this further in the next group of chapters making it even more convenient to work with in Python code, using **relationship attributes**.
But you should start in this group of chapters first. 🤓 But you should start in this group of chapters first. 🤓
///

View File

@ -62,9 +62,12 @@ FROM hero, team
WHERE hero.team_id = team.id WHERE hero.team_id = team.id
``` ```
!!! info /// info
Because we have two columns called `name`, one for `hero` and one for `team`, we can specify them with the prefix of the table name and the dot to make it explicit what we refer to. Because we have two columns called `name`, one for `hero` and one for `team`, we can specify them with the prefix of the table name and the dot to make it explicit what we refer to.
///
Notice that now in the `WHERE` part we are not comparing one column with a literal value (like `hero.name = "Deadpond"`), but we are comparing two columns. Notice that now in the `WHERE` part we are not comparing one column with a literal value (like `hero.name = "Deadpond"`), but we are comparing two columns.
It means, more or less: It means, more or less:
@ -99,7 +102,8 @@ You can go ahead and try it in **DB Browser for SQLite**:
<img class="shadow" src="/img/tutorial/relationships/select/image01.png"> <img class="shadow" src="/img/tutorial/relationships/select/image01.png">
!!! note /// note
Wait, what about Spider-Boy? 😱 Wait, what about Spider-Boy? 😱
He doesn't have a team, so his `team_id` is `NULL` in the database. And this SQL is comparing that `NULL` from the `team_id` with all the `id` fields in the rows in the `team` table. He doesn't have a team, so his `team_id` is `NULL` in the database. And this SQL is comparing that `NULL` from the `team_id` with all the `id` fields in the rows in the `team` table.
@ -108,6 +112,8 @@ You can go ahead and try it in **DB Browser for SQLite**:
But we'll see how to fix that later with a `LEFT JOIN`. But we'll see how to fix that later with a `LEFT JOIN`.
///
## Select Related Data with **SQLModel** ## Select Related Data with **SQLModel**
Now let's use SQLModel to do the same select. Now let's use SQLModel to do the same select.
@ -164,11 +170,14 @@ For each iteration in the `for` loop we get a a tuple with an instance of the cl
And in this `for` loop we assign them to the variable `hero` and the variable `team`. And in this `for` loop we assign them to the variable `hero` and the variable `team`.
!!! info /// info
There was a lot of research, design, and work behind **SQLModel** to make this provide the best possible developer experience. There was a lot of research, design, and work behind **SQLModel** to make this provide the best possible developer experience.
And you should get autocompletion and inline errors in your editor for both `hero` and `team`. 🎉 And you should get autocompletion and inline errors in your editor for both `hero` and `team`. 🎉
///
## Add It to Main ## Add It to Main
As always, we must remember to add this new `select_heroes()` function to the `main()` function to make sure it is executed when we call this program from the command line. As always, we must remember to add this new `select_heroes()` function to the `main()` function to make sure it is executed when we call this program from the command line.
@ -281,11 +290,14 @@ Also in **DB Browser for SQLite**:
<img class="shadow" src="/img/tutorial/relationships/select/image02.png"> <img class="shadow" src="/img/tutorial/relationships/select/image02.png">
!!! tip /// tip
Why bother with all this if the result is the same? Why bother with all this if the result is the same?
This `JOIN` will be useful in a bit to be able to also get Spider-Boy, even if he doesn't have a team. This `JOIN` will be useful in a bit to be able to also get Spider-Boy, even if he doesn't have a team.
///
## Join Tables in **SQLModel** ## Join Tables in **SQLModel**
The same way there's a `.where()` available when using `select()`, there's also a `.join()`. The same way there's a `.where()` available when using `select()`, there's also a `.join()`.
@ -420,9 +432,12 @@ And that would return the following result, including **Spider-Boy** 🎉:
</tr> </tr>
</table> </table>
!!! tip /// tip
The only difference between this query and the previous is that extra `LEFT OUTER`. The only difference between this query and the previous is that extra `LEFT OUTER`.
///
And here's another of the SQL variations, you could write `LEFT OUTER JOIN` or just `LEFT JOIN`, it means the same. And here's another of the SQL variations, you could write `LEFT OUTER JOIN` or just `LEFT JOIN`, it means the same.
## Join Tables in **SQLModel** with `LEFT OUTER` ## Join Tables in **SQLModel** with `LEFT OUTER`

View File

@ -42,9 +42,12 @@ Click the button <kbd>New Database</kbd>.
A dialog should show up. Go to the [project directory you created](./index.md#create-a-project){.internal-link target=_blank} and save the file with a name of `database.db`. A dialog should show up. Go to the [project directory you created](./index.md#create-a-project){.internal-link target=_blank} and save the file with a name of `database.db`.
!!! tip /// tip
It's common to save SQLite database files with an extension of `.db`. Sometimes also `.sqlite`. It's common to save SQLite database files with an extension of `.db`. Sometimes also `.sqlite`.
///
## Create a Table ## Create a Table
After doing that, it might prompt you to create a new table right away. After doing that, it might prompt you to create a new table right away.

View File

@ -33,9 +33,12 @@ The first thing we need to do is create a class to represent the data in the tab
A class like this that represents some data is commonly called a **model**. A class like this that represents some data is commonly called a **model**.
!!! tip /// tip
That's why this package is called `SQLModel`. Because it's mainly used to create **SQL Models**. That's why this package is called `SQLModel`. Because it's mainly used to create **SQL Models**.
///
For that, we will import `SQLModel` (plus other things we will also use) and create a class `Hero` that inherits from `SQLModel` and represents the **table model** for our heroes: For that, we will import `SQLModel` (plus other things we will also use) and create a class `Hero` that inherits from `SQLModel` and represents the **table model** for our heroes:
```Python hl_lines="3 6" ```Python hl_lines="3 6"
@ -57,11 +60,14 @@ This class `Hero` **represents the table** for our heroes. And each instance we
We use the config `table=True` to tell **SQLModel** that this is a **table model**, it represents a table. We use the config `table=True` to tell **SQLModel** that this is a **table model**, it represents a table.
!!! info /// info
It's also possible to have models without `table=True`, those would be only **data models**, without a table in the database, they would not be **table models**. It's also possible to have models without `table=True`, those would be only **data models**, without a table in the database, they would not be **table models**.
Those **data models** will be **very useful later**, but for now, we'll just keep adding the `table=True` configuration. Those **data models** will be **very useful later**, but for now, we'll just keep adding the `table=True` configuration.
///
## Define the Fields, Columns ## Define the Fields, Columns
The next step is to define the fields or columns of the class by using standard Python type annotations. The next step is to define the fields or columns of the class by using standard Python type annotations.
@ -112,20 +118,26 @@ And we also set the default value of `age` to `None`.
</details> </details>
!!! tip /// tip
We also define `id` with `Optional`. But we will talk about `id` below. We also define `id` with `Optional`. But we will talk about `id` below.
///
This way, we tell **SQLModel** that `age` is not required when validating data and that it has a default value of `None`. This way, we tell **SQLModel** that `age` is not required when validating data and that it has a default value of `None`.
And we also tell it that, in the SQL database, the default value of `age` is `NULL` (the SQL equivalent to Python's `None`). And we also tell it that, in the SQL database, the default value of `age` is `NULL` (the SQL equivalent to Python's `None`).
So, this column is "nullable" (can be set to `NULL`). So, this column is "nullable" (can be set to `NULL`).
!!! info /// info
In terms of **Pydantic**, `age` is an **optional field**. In terms of **Pydantic**, `age` is an **optional field**.
In terms of **SQLAlchemy**, `age` is a **nullable column**. In terms of **SQLAlchemy**, `age` is a **nullable column**.
///
### Primary Key `id` ### Primary Key `id`
Now let's review the `id` field. This is the <abbr title="That unique identifier of each row in a specific table.">**primary key**</abbr> of the table. Now let's review the `id` field. This is the <abbr title="That unique identifier of each row in a specific table.">**primary key**</abbr> of the table.
@ -207,11 +219,14 @@ Creating the **engine** is very simple, just call `create_engine()` with a URL f
You should normally have a single **engine** object for your whole application and re-use it everywhere. You should normally have a single **engine** object for your whole application and re-use it everywhere.
!!! tip /// tip
There's another related thing called a **Session** that normally should *not* be a single object per application. There's another related thing called a **Session** that normally should *not* be a single object per application.
But we will talk about it later. But we will talk about it later.
///
### Engine Database URL ### Engine Database URL
Each supported database has it's own URL type. For example, for **SQLite** it is `sqlite:///` followed by the file path. For example: Each supported database has it's own URL type. For example, for **SQLite** it is `sqlite:///` followed by the file path. For example:
@ -272,9 +287,12 @@ engine = create_engine(sqlite_url)
### Engine Technical Details ### Engine Technical Details
!!! tip /// tip
If you didn't know about SQLAlchemy before and are just learning **SQLModel**, you can probably skip this section, scroll below. If you didn't know about SQLAlchemy before and are just learning **SQLModel**, you can probably skip this section, scroll below.
///
You can read a lot more about the engine in the <a href="https://docs.sqlalchemy.org/en/14/tutorial/engine.html" class="external-link" target="_blank">SQLAlchemy documentation</a>. You can read a lot more about the engine in the <a href="https://docs.sqlalchemy.org/en/14/tutorial/engine.html" class="external-link" target="_blank">SQLAlchemy documentation</a>.
**SQLModel** defines it's own `create_engine()` function. It is the same as SQLAlchemy's `create_engine()`, but with the difference that it defaults to use `future=True` (which means that it uses the style of the latest SQLAlchemy, 1.4, and the future 2.0). **SQLModel** defines it's own `create_engine()` function. It is the same as SQLAlchemy's `create_engine()`, but with the difference that it defaults to use `future=True` (which means that it uses the style of the latest SQLAlchemy, 1.4, and the future 2.0).
@ -289,13 +307,16 @@ Now everything is in place to finally create the database and table:
{!./docs_src/tutorial/create_db_and_table/tutorial001.py!} {!./docs_src/tutorial/create_db_and_table/tutorial001.py!}
``` ```
!!! tip /// tip
Creating the engine doesn't create the `database.db` file. Creating the engine doesn't create the `database.db` file.
But once we run `SQLModel.metadata.create_all(engine)`, it creates the `database.db` file **and** creates the `hero` table in that database. But once we run `SQLModel.metadata.create_all(engine)`, it creates the `database.db` file **and** creates the `hero` table in that database.
Both things are done in this single step. Both things are done in this single step.
///
Let's unwrap that: Let's unwrap that:
```Python ```Python
@ -404,9 +425,12 @@ Put the code it in a file `app.py` if you haven't already.
</details> </details>
!!! tip /// tip
Remember to [activate the virtual environment](./index.md#create-a-python-virtual-environment){.internal-link target=_blank} before running it. Remember to [activate the virtual environment](./index.md#create-a-python-virtual-environment){.internal-link target=_blank} before running it.
///
Now run the program with Python: Now run the program with Python:
<div class="termy"> <div class="termy">
@ -442,7 +466,8 @@ INFO Engine COMMIT
</div> </div>
!!! info /// info
I simplified the output above a bit to make it easier to read. I simplified the output above a bit to make it easier to read.
But in reality, instead of showing: But in reality, instead of showing:
@ -457,6 +482,8 @@ INFO Engine COMMIT
2021-07-25 21:37:39,175 INFO sqlalchemy.engine.Engine BEGIN (implicit) 2021-07-25 21:37:39,175 INFO sqlalchemy.engine.Engine BEGIN (implicit)
``` ```
///
### `TEXT` or `VARCHAR` ### `TEXT` or `VARCHAR`
In the example in the previous chapter we created the table using `TEXT` for some columns. In the example in the previous chapter we created the table using `TEXT` for some columns.
@ -479,9 +506,12 @@ Additional to the difference between those two data types, some databases like M
To make it easier to start using **SQLModel** right away independent of the database you use (even with MySQL), and without any extra configurations, by default, `str` fields are interpreted as `VARCHAR` in most databases and `VARCHAR(255)` in MySQL, this way you know the same class will be compatible with the most popular databases without extra effort. To make it easier to start using **SQLModel** right away independent of the database you use (even with MySQL), and without any extra configurations, by default, `str` fields are interpreted as `VARCHAR` in most databases and `VARCHAR(255)` in MySQL, this way you know the same class will be compatible with the most popular databases without extra effort.
!!! tip /// tip
You will learn how to change the maximum length of string columns later in the Advanced Tutorial - User Guide. You will learn how to change the maximum length of string columns later in the Advanced Tutorial - User Guide.
///
### Verify the Database ### Verify the Database
Now, open the database with **DB Browser for SQLite**, you will see that the program created the table `hero` just as before. 🎉 Now, open the database with **DB Browser for SQLite**, you will see that the program created the table `hero` just as before. 🎉
@ -519,20 +549,26 @@ We don't want that to happen like that, only when we **intend** it to happen, th
Now we would be able to, for example, import the `Hero` class in some other file without having those **side effects**. Now we would be able to, for example, import the `Hero` class in some other file without having those **side effects**.
!!! tip /// tip
😅 **Spoiler alert**: The function is called `create_db_and_tables()` because we will have more **tables** in the future with other classes apart from `Hero`. 🚀 😅 **Spoiler alert**: The function is called `create_db_and_tables()` because we will have more **tables** in the future with other classes apart from `Hero`. 🚀
///
### Create Data as a Script ### Create Data as a Script
We prevented the side effects when importing something from your `app.py` file. We prevented the side effects when importing something from your `app.py` file.
But we still want it to **create the database and table** when we call it with Python directly as an independent script from the terminal, just as as above. But we still want it to **create the database and table** when we call it with Python directly as an independent script from the terminal, just as as above.
!!! tip /// tip
Think of the word **script** and **program** as interchangeable. Think of the word **script** and **program** as interchangeable.
The word **script** often implies that the code could be run independently and easily. Or in some cases it refers to a relatively simple program. The word **script** often implies that the code could be run independently and easily. Or in some cases it refers to a relatively simple program.
///
For that we can use the special variable `__name__` in an `if` block: For that we can use the special variable `__name__` in an `if` block:
```Python hl_lines="23-24" ```Python hl_lines="23-24"
@ -559,11 +595,14 @@ $ python app.py
from app import Hero from app import Hero
``` ```
!!! tip /// tip
That `if` block using `if __name__ == "__main__":` is sometimes called the "**main block**". That `if` block using `if __name__ == "__main__":` is sometimes called the "**main block**".
The official name (in the <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">Python docs</a>) is "**Top-level script environment**". The official name (in the <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">Python docs</a>) is "**Top-level script environment**".
///
#### More details #### More details
Let's say your file is named `myapp.py`. Let's say your file is named `myapp.py`.
@ -614,9 +653,12 @@ if __name__ == "__main__":
...will **not** be executed. ...will **not** be executed.
!!! info /// info
For more information, check <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">the official Python docs</a>. For more information, check <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">the official Python docs</a>.
///
## Last Review ## Last Review
After those changes, you could run it again, and it would generate the same output as before. After those changes, you could run it again, and it would generate the same output as before.
@ -631,9 +673,12 @@ Now, let's give the code a final look:
{!./docs_src/tutorial/create_db_and_table/annotations/en/tutorial003.md!} {!./docs_src/tutorial/create_db_and_table/annotations/en/tutorial003.md!}
!!! tip /// tip
Review what each line does by clicking each number bubble in the code. 👆 Review what each line does by clicking each number bubble in the code. 👆
///
## Recap ## Recap
We learnt how to use **SQLModel** to define how a table in the database should look like, and we created a database and a table using **SQLModel**. We learnt how to use **SQLModel** to define how a table in the database should look like, and we created a database and a table using **SQLModel**.

View File

@ -333,9 +333,12 @@ Now let's review all that code:
{!./docs_src/tutorial/delete/annotations/en/tutorial002.md!} {!./docs_src/tutorial/delete/annotations/en/tutorial002.md!}
!!! tip /// tip
Check out the number bubbles to see what is done by each line of code. Check out the number bubbles to see what is done by each line of code.
///
## Recap ## Recap
To delete rows with **SQLModel** you just have to `.delete()` them with the **session**, and then, as always, `.commit()` the session to save the changes to the database. 🔥 To delete rows with **SQLModel** you just have to `.delete()` them with the **session**, and then, as always, `.commit()` the session to save the changes to the database. 🔥

View File

@ -8,9 +8,12 @@ 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. Let's use the same **offset** and **limit** we learned about in the previous tutorial chapters for the API.
!!! info /// 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 ## Add a Limit and Offset to the Query Parameters
Let's add `limit` and `offset` to the query parameters. Let's add `limit` and `offset` to the query parameters.
@ -46,13 +49,16 @@ So, to prevent it, we add additional validation to the `limit` query parameter,
This way, a client can decide to take fewer heroes if they want, but not more. This way, a client can decide to take fewer heroes if they want, but not more.
!!! info /// info
If you need to refresh how query parameters and their validation work, check out the docs in FastAPI: If you need to refresh how query parameters and their validation work, check out the docs in FastAPI:
* <a href="https://fastapi.tiangolo.com/tutorial/query-params/" class="external-link" target="_blank">Query Parameters</a> * <a href="https://fastapi.tiangolo.com/tutorial/query-params/" class="external-link" target="_blank">Query Parameters</a>
* <a href="https://fastapi.tiangolo.com/tutorial/query-params-str-validations/" class="external-link" target="_blank">Query Parameters and String Validations</a> * <a href="https://fastapi.tiangolo.com/tutorial/query-params-str-validations/" class="external-link" target="_blank">Query Parameters and String Validations</a>
* <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/" class="external-link" target="_blank">Path Parameters and Numeric Validations</a> * <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/" class="external-link" target="_blank">Path Parameters and Numeric Validations</a>
///
## Check the Docs UI ## Check the Docs UI
Now we can see that the docs UI shows the new parameters to control **limit** and **offset** of our data. Now we can see that the docs UI shows the new parameters to control **limit** and **offset** of our data.

View File

@ -136,9 +136,12 @@ But `HeroCreate` and `HeroRead` don't have `table = True`. They are only **data
This also means that `SQLModel.metadata.create_all()` won't create tables in the database for `HeroCreate` and `HeroRead`, because they don't have `table = True`, which is exactly what we want. 🚀 This also means that `SQLModel.metadata.create_all()` won't create tables in the database for `HeroCreate` and `HeroRead`, because they don't have `table = True`, which is exactly what we want. 🚀
!!! tip /// tip
We will improve this code to avoid duplicating the fields, but for now we can continue learning with these models. We will improve this code to avoid duplicating the fields, but for now we can continue learning with these models.
///
## Use Multiple Models to Create a Hero ## Use Multiple Models to Create a Hero
Let's now see how to use these new models in the FastAPI application. Let's now see how to use these new models in the FastAPI application.
@ -208,11 +211,14 @@ And now that we return it, FastAPI will validate the data with the `response_mod
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 /// 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>. 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 ## Shared Fields

View File

@ -8,9 +8,12 @@ Let's add a new *path operation* to read one single hero.
We want to get the hero based on the `id`, so we will use a **path parameter** `hero_id`. We want to get the hero based on the `id`, so we will use a **path parameter** `hero_id`.
!!! info /// info
If you need to refresh how *path parameters* work, including their data validation, check the <a href="https://fastapi.tiangolo.com/tutorial/path-params/" class="external-link" target="_blank">FastAPI docs about Path Parameters</a>. If you need to refresh how *path parameters* work, including their data validation, check the <a href="https://fastapi.tiangolo.com/tutorial/path-params/" class="external-link" target="_blank">FastAPI docs about Path Parameters</a>.
///
```Python hl_lines="8" ```Python hl_lines="8"
{!./docs_src/tutorial/fastapi/read_one/tutorial001.py[ln:1-4]!} {!./docs_src/tutorial/fastapi/read_one/tutorial001.py[ln:1-4]!}

View File

@ -100,11 +100,14 @@ Additionally, because the schemas are defined in using a standard, there are man
For example, client generators, that can automatically create the code necessary to talk to your API in many languages. For example, client generators, that can automatically create the code necessary to talk to your API in many languages.
!!! info /// info
If you are curious about the standards, FastAPI generates OpenAPI, that internally uses JSON Schema. If you are curious about the standards, FastAPI generates OpenAPI, that internally uses JSON Schema.
You can read about all that in the <a href="https://fastapi.tiangolo.com/tutorial/first-steps/#openapi" class="external-link" target="_blank">FastAPI docs - First Steps</a>. You can read about all that in the <a href="https://fastapi.tiangolo.com/tutorial/first-steps/#openapi" class="external-link" target="_blank">FastAPI docs - First Steps</a>.
///
## Recap ## Recap
Use the `response_model` to tell FastAPI the schema of the data you want to send back and have awesome data APIs. 😎 Use the `response_model` to tell FastAPI the schema of the data you want to send back and have awesome data APIs. 😎

View File

@ -81,7 +81,8 @@ We import `Depends()` from `fastapi`. Then we use it in the *path operation func
</details> </details>
!!! tip /// tip
Here's a tip about that `*,` thing in the parameters. Here's a tip about that `*,` thing in the parameters.
Here we are passing the parameter `session` that has a "default value" of `Depends(get_session)` before the parameter `hero`, that doesn't have any default value. Here we are passing the parameter `session` that has a "default value" of `Depends(get_session)` before the parameter `hero`, that doesn't have any default value.
@ -90,6 +91,8 @@ 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> 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. 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

@ -62,11 +62,14 @@ But here we will make sure we don't share the same **session** in more than one
And we also need to disable it because in **FastAPI** each request could be handled by multiple interacting threads. And we also need to disable it because in **FastAPI** each request could be handled by multiple interacting threads.
!!! info /// info
That's enough information for now, you can read more about it in the <a href="https://fastapi.tiangolo.com/async/" class="external-link" target="_blank">FastAPI docs for `async` and `await`</a>. That's enough information for now, you can read more about it in the <a href="https://fastapi.tiangolo.com/async/" class="external-link" target="_blank">FastAPI docs for `async` and `await`</a>.
The main point is, by ensuring you **don't share** the same **session** with more than one request, the code is already safe. The main point is, by ensuring you **don't share** the same **session** with more than one request, the code is already safe.
///
## **FastAPI** App ## **FastAPI** App
The next step is to create the **FastAPI** app. The next step is to create the **FastAPI** app.
@ -119,9 +122,12 @@ This should be called only once at startup, not before every request, so we put
## Create Heroes *Path Operation* ## Create Heroes *Path Operation*
!!! info /// info
If you need a refresher on what a **Path Operation** is (an endpoint with a specific HTTP Operation) and how to work with it in FastAPI, check out the <a href="https://fastapi.tiangolo.com/tutorial/first-steps/" class="external-link" target="_blank">FastAPI First Steps docs</a>. If you need a refresher on what a **Path Operation** is (an endpoint with a specific HTTP Operation) and how to work with it in FastAPI, check out the <a href="https://fastapi.tiangolo.com/tutorial/first-steps/" class="external-link" target="_blank">FastAPI First Steps docs</a>.
///
Let's create the **path operation** code to create a new hero. Let's create the **path operation** code to create a new hero.
It will be called when a user sends a request with a `POST` **operation** to the `/heroes/` **path**: It will be called when a user sends a request with a `POST` **operation** to the `/heroes/` **path**:
@ -143,13 +149,16 @@ It will be called when a user sends a request with a `POST` **operation** to the
</details> </details>
!!! info /// info
If you need a refresher on some of those concepts, checkout the FastAPI documentation: If you need a refresher on some of those concepts, checkout the FastAPI documentation:
* <a href="https://fastapi.tiangolo.com/tutorial/first-steps/" class="external-link" target="_blank">First Steps</a> * <a href="https://fastapi.tiangolo.com/tutorial/first-steps/" class="external-link" target="_blank">First Steps</a>
* <a href="https://fastapi.tiangolo.com/tutorial/path-params/" class="external-link" target="_blank">Path Parameters - Data Validation and Data Conversion</a> * <a href="https://fastapi.tiangolo.com/tutorial/path-params/" class="external-link" target="_blank">Path Parameters - Data Validation and Data Conversion</a>
* <a href="https://fastapi.tiangolo.com/tutorial/body/" class="external-link" target="_blank">Request Body</a> * <a href="https://fastapi.tiangolo.com/tutorial/body/" class="external-link" target="_blank">Request Body</a>
///
## The **SQLModel** Advantage ## The **SQLModel** Advantage
Here's where having our **SQLModel** class models be both **SQLAlchemy** models and **Pydantic** models at the same time shine. ✨ Here's where having our **SQLModel** class models be both **SQLAlchemy** models and **Pydantic** models at the same time shine. ✨
@ -162,9 +171,12 @@ And then, because this same **SQLModel** object is not only a **Pydantic** model
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. 🎉 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. 🎉
!!! tip /// tip
We will improve this further later, but for now, it already shows the power of having **SQLModel** classes be both **SQLAlchemy** models and **Pydantic** models at the same time. We will improve this further later, but for now, it already shows the power of having **SQLModel** classes be both **SQLAlchemy** models and **Pydantic** models at the same time.
///
## Read Heroes *Path Operation* ## Read Heroes *Path Operation*
Now let's add another **path operation** to read all the heroes: Now let's add another **path operation** to read all the heroes:
@ -226,12 +238,15 @@ $ uvicorn main:app
</div> </div>
!!! info /// info
The command `uvicorn main:app` refers to: The command `uvicorn main:app` refers to:
* `main`: the file `main.py` (the Python "module"). * `main`: the file `main.py` (the Python "module").
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. * `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
///
### Uvicorn `--reload` ### Uvicorn `--reload`
During development (and only during development), you can also add the option `--reload` to Uvicorn. During development (and only during development), you can also add the option `--reload` to Uvicorn.

View File

@ -71,9 +71,12 @@ Let's start with a simple test, with just the basic test code we need the check
{!./docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_001.md!} {!./docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_001.md!}
!!! tip /// tip
Check out the number bubbles to see what is done by each line of code. Check out the number bubbles to see what is done by each line of code.
///
That's the **core** of the code we need for all the tests later. 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. 🤓
@ -116,9 +119,12 @@ That way we protect the production database and we have better control of the da
{!./docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_002.md!} {!./docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_002.md!}
!!! tip /// tip
Check out the number bubbles to see what is done by each line of code. Check out the number bubbles to see what is done by each line of code.
///
## Create the Engine and Session for Testing ## Create the Engine and Session for Testing
Now let's create that **session** object that will be used during testing. Now let's create that **session** object that will be used during testing.
@ -197,9 +203,12 @@ We just have to change a couple of parameters in the **engine**.
{!./docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_004.md!} {!./docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_004.md!}
!!! tip /// tip
Check out the number bubbles to see what is done by each line of code. Check out the number bubbles to see what is done by each line of code.
///
That's it, now the test will run using the **in-memory database**, which will be faster and probably safer. That's it, now the test will run using the **in-memory database**, which will be faster and probably safer.
And all the other tests can do the same. And all the other tests can do the same.
@ -214,9 +223,12 @@ Do we really have to duplicate all that for **each test**? No, we can do better!
We are using **pytest** to run the tests. And pytest also has a very similar concept to the **dependencies in FastAPI**. We are using **pytest** to run the tests. And pytest also has a very similar concept to the **dependencies in FastAPI**.
!!! info /// info
In fact, pytest was one of the things that inspired the design of the dependencies in FastAPI. In fact, pytest was one of the things that inspired the design of the dependencies in FastAPI.
///
It's a way for us to declare some **code that should be run before** each test and **provide a value** for the test function (that's pretty much the same as FastAPI dependencies). It's a way for us to declare some **code that should be run before** each test and **provide a value** for the test function (that's pretty much the same as FastAPI dependencies).
In fact, it also has the same trick of allowing to use `yield` instead of `return` to provide the value, and then **pytest** makes sure that the code after `yield` is executed *after* the function with the test is done. In fact, it also has the same trick of allowing to use `yield` instead of `return` to provide the value, and then **pytest** makes sure that the code after `yield` is executed *after* the function with the test is done.
@ -237,9 +249,12 @@ Let's see the first code example with a fixture:
{!./docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_005.md!} {!./docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_005.md!}
!!! tip /// tip
Check out the number bubbles to see what is done by each line of code. Check out the number bubbles to see what is done by each line of code.
///
**pytest** fixtures work in a very similar way to FastAPI dependencies, but have some minor differences: **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.
@ -274,9 +289,12 @@ So, we can create a **client fixture** that will be used in all the tests, and i
{!./docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_006.md!} {!./docs_src/tutorial/fastapi/app_testing/tutorial001/annotations/en/test_main_006.md!}
!!! tip /// tip
Check out the number bubbles to see what is done by each line of code. Check out the number bubbles to see what is done by each line of code.
///
Now we have a **client fixture** that, in turn, 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**. And in the actual test function, we just have to declare that we require this **client fixture**.
@ -306,11 +324,14 @@ Let's add some more tests:
</details> </details>
!!! tip /// tip
It's always **good idea** to not only test the normal case, but also that **invalid data**, **errors**, and **corner cases** are handled correctly. It's always **good idea** to not only test the normal case, but also that **invalid data**, **errors**, and **corner cases** are handled correctly.
That's why we add these two extra tests here. 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 **declare 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 ## Why Two Fixtures

View File

@ -12,11 +12,14 @@ So, we need to have all those fields **marked as optional**.
And because the `HeroBase` has some of them as *required* and not optional, we will need to **create a new model**. And because the `HeroBase` has some of them as *required* and not optional, we will need to **create a new model**.
!!! tip /// tip
Here is one of those cases where it probably makes sense to use an **independent model** instead of trying to come up with a complex tree of models inheriting from each other. Here is one of those cases where it probably makes sense to use an **independent model** instead of trying to come up with a complex tree of models inheriting from each other.
Because each field is **actually different** (we just change it to `Optional`, but that's already making it different), it makes sense to have them in their own model. Because each field is **actually different** (we just change it to `Optional`, but that's already making it different), it makes sense to have them in their own model.
///
So, let's create this new `HeroUpdate` model: So, let's create this new `HeroUpdate` model:
```Python hl_lines="21-24" ```Python hl_lines="21-24"

View File

@ -57,9 +57,12 @@ $ cd sqlmodel-tutorial
</div> </div>
!!! tip /// tip
Make sure you don't name it also `sqlmodel`, so that you don't end up overriding the name of the package. Make sure you don't name it also `sqlmodel`, so that you don't end up overriding the name of the package.
///
### Make sure you have Python ### Make sure you have Python
Make sure you have an officially supported version of Python. Make sure you have an officially supported version of Python.
@ -119,16 +122,19 @@ In very short, a virtual environment is a small directory that contains a copy o
And when you "activate" it, any package that you install, for example with `pip`, will be installed in that virtual environment. And when you "activate" it, any package that you install, for example with `pip`, will be installed in that virtual environment.
!!! tip /// tip
There are other tools to manage virtual environments, like <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a>. There are other tools to manage virtual environments, like <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a>.
And there are alternatives that are particularly useful for deployment like <a href="https://docs.docker.com/get-started/" class="external-link" target="_blank">Docker</a> and other types of containers. In this case, the "virtual environment" is not just the Python standard files and the installed packages, but the whole system. And there are alternatives that are particularly useful for deployment like <a href="https://docs.docker.com/get-started/" class="external-link" target="_blank">Docker</a> and other types of containers. In this case, the "virtual environment" is not just the Python standard files and the installed packages, but the whole system.
///
Go ahead and create a Python virtual environment for this project. And make sure to also upgrade `pip`. Go ahead and create a Python virtual environment for this project. And make sure to also upgrade `pip`.
Here are the commands you could use: Here are the commands you could use:
=== "Linux, macOS, Linux in Windows" /// tab | Linux, macOS, Linux in Windows
<div class="termy"> <div class="termy">
@ -151,7 +157,9 @@ Here are the commands you could use:
</div> </div>
=== "Windows PowerShell" ///
/// tab | Windows PowerShell
<div class="termy"> <div class="termy">
@ -175,6 +183,8 @@ Here are the commands you could use:
</div> </div>
///
## Install **SQLModel** ## Install **SQLModel**
Now, after making sure we are inside of a virtual environment in some way, we can install **SQLModel**: Now, after making sure we are inside of a virtual environment in some way, we can install **SQLModel**:

View File

@ -73,13 +73,16 @@ You repeat this process **a few more times**, and you finally arrive at the lett
You had to open the dictionary a few times, maybe **5 or 10**. That's actually **very little work** compared to what it could have been. You had to open the dictionary a few times, maybe **5 or 10**. That's actually **very little work** compared to what it could have been.
!!! note "Technical Details" /// note | Technical Details
Do you like **fancy words**? Cool! Programmers tend to like fancy words. 😅 Do you like **fancy words**? Cool! Programmers tend to like fancy words. 😅
That <abbr title="a recipe, a sequence of predefined steps that achieve a result">algorithm</abbr> I showed you above is called **Binary Search**. That <abbr title="a recipe, a sequence of predefined steps that achieve a result">algorithm</abbr> I showed you above is called **Binary Search**.
It's called like that because you **search** something by splitting the dictionary (or any ordered list of things) in **two** ("binary" means "two") parts. And you do that process multiple times until you find what you want. It's called like that because you **search** something by splitting the dictionary (or any ordered list of things) in **two** ("binary" means "two") parts. And you do that process multiple times until you find what you want.
///
### An Index and a Novel ### An Index and a Novel
Let's now imagine you are reading a **novel book**. And someone told you that at some point, they mention a **database**, and you want to find that chapter. Let's now imagine you are reading a **novel book**. And someone told you that at some point, they mention a **database**, and you want to find that chapter.
@ -297,11 +300,14 @@ We use the same `Field()` again as we did before, and set `index=True`. That's i
Notice that we didn't set an argument of `default=None` or anything similar. This means that **SQLModel** (thanks to Pydantic) will keep it as a **required** field. Notice that we didn't set an argument of `default=None` or anything similar. This means that **SQLModel** (thanks to Pydantic) will keep it as a **required** field.
!!! info /// info
SQLModel (actually SQLAlchemy) will **automatically generate the index name** for you. SQLModel (actually SQLAlchemy) will **automatically generate the index name** for you.
In this case the generated name would be `ix_hero_name`. In this case the generated name would be `ix_hero_name`.
///
## Query Data ## Query Data
Now, to query the data using the field `name` and the new index we don't have to do anything special or different in the code, it's just **the same code**. Now, to query the data using the field `name` and the new index we don't have to do anything special or different in the code, it's just **the same code**.

View File

@ -70,9 +70,12 @@ You can try that SQL statement in **DB Explorer for SQLite**.
Make sure to open the same database we already created by clicking <kbd>Open Database</kbd> and selecting the same `database.db` file. Make sure to open the same database we already created by clicking <kbd>Open Database</kbd> and selecting the same `database.db` file.
!!! tip /// tip
If you don't have that `database.db` file with the table `hero`, you can re-create it by running the Python program at the top. 👆 If you don't have that `database.db` file with the table `hero`, you can re-create it by running the Python program at the top. 👆
///
Then go to the <kbd>Execute SQL</kbd> tab and copy the SQL from above. Then go to the <kbd>Execute SQL</kbd> tab and copy the SQL from above.
It would look like this: It would look like this:
@ -141,11 +144,14 @@ We'll create 3 right away, for the 3 heroes:
</details> </details>
!!! tip /// tip
The code above in this file (the omitted code) is just the same code that you see at the top of this chapter. The code above in this file (the omitted code) is just the same code that you see at the top of this chapter.
The same code we used before to create the `Hero` model. The same code we used before to create the `Hero` model.
///
We are putting that in a function `create_heroes()`, to call it later once we finish it. We are putting that in a function `create_heroes()`, to call it later once we finish it.
If you are trying the code interactively, you could also write that directly. If you are trying the code interactively, you could also write that directly.
@ -204,9 +210,12 @@ Then we can create a new session:
The new `Session` takes an `engine` as a parameter. And it will use the **engine** underneath. The new `Session` takes an `engine` as a parameter. And it will use the **engine** underneath.
!!! tip /// tip
We will see a better way to create a **session** using a `with` block later. We will see a better way to create a **session** using a `with` block later.
///
## Add Model Instances to the Session ## Add Model Instances to the Session
Now that we have some hero model instances (some objects in memory) and a **session**, the next step is to add them to the session: Now that we have some hero model instances (some objects in memory) and a **session**, the next step is to add them to the session:
@ -237,11 +246,14 @@ And once we are ready, we can **commit** those changes, and then the **session**
This makes the interactions with the database more efficient (plus some extra benefits). This makes the interactions with the database more efficient (plus some extra benefits).
!!! info "Technical Details" /// info | Technical Details
The session will create a new transaction and execute all the SQL code in that transaction. The session will create a new transaction and execute all the SQL code in that transaction.
This ensures that the data is saved in a single batch, and that it will all succeed or all fail, but it won't leave the database in a broken state. This ensures that the data is saved in a single batch, and that it will all succeed or all fail, but it won't leave the database in a broken state.
///
## Commit the Session Changes ## Commit the Session Changes
Now that we have the heroes in the **session** and that we are ready to save all that to the database, we can **commit** the changes: Now that we have the heroes in the **session** and that we are ready to save all that to the database, we can **commit** the changes:
@ -433,9 +445,12 @@ Let's focus on the new code:
{!./docs_src/tutorial/insert/annotations/en/tutorial003.md!} {!./docs_src/tutorial/insert/annotations/en/tutorial003.md!}
!!! tip /// tip
Review what each line does by clicking each number bubble in the code. 👆 Review what each line does by clicking each number bubble in the code. 👆
///
You can now put it in a `app.py` file and run it with Python. And you will see an output like the one shown above. You can now put it in a `app.py` file and run it with Python. And you will see an output like the one shown above.
After that, if you open the database with **DB Browser for SQLite**, you will see the data you just created in the <kbd>Browse Data</kbd> tab: After that, if you open the database with **DB Browser for SQLite**, you will see the data you just created in the <kbd>Browse Data</kbd> tab:

View File

@ -110,20 +110,26 @@ INFO Engine [no key 0.00014s] (3, 0)
Great! We got only 3 heroes as we wanted. Great! We got only 3 heroes as we wanted.
!!! tip /// tip
We will check out that SQL code more in a bit. We will check out that SQL code more in a bit.
///
## Select with Offset and Limit ## Select with Offset and Limit
Now we can limit the results to get only the first 3. Now we can limit the results to get only the first 3.
But imagine we are in a user interface showing the results in batches of 3 heroes at a time. But imagine we are in a user interface showing the results in batches of 3 heroes at a time.
!!! tip /// tip
This is commonly called "pagination". Because the user interface would normally show a "page" of a predefined number of heroes at a time. This is commonly called "pagination". Because the user interface would normally show a "page" of a predefined number of heroes at a time.
And then you can interact with the user interface to get the next page, and so on. And then you can interact with the user interface to get the next page, and so on.
///
How do we get the next 3? How do we get the next 3?
<img class="shadow" alt="table with next rows selected, from 4 to 6" src="/img/tutorial/offset-and-limit/limit2.svg"> <img class="shadow" alt="table with next rows selected, from 4 to 6" src="/img/tutorial/offset-and-limit/limit2.svg">

View File

@ -30,9 +30,12 @@ The `team` table looks like this:
</tr> </tr>
</table> </table>
!!! tip /// tip
Notice that it doesn't have any foreign key to other tables. Notice that it doesn't have any foreign key to other tables.
///
And the `hero` table looks like this: And the `hero` table looks like this:
<table> <table>
@ -106,7 +109,8 @@ Specifically, the new link table `heroteamlink` would be:
</tr> </tr>
</table> </table>
!!! info /// info
Other names used for this **link table** are: Other names used for this **link table** are:
* association table * association table
@ -120,6 +124,8 @@ Specifically, the new link table `heroteamlink` would be:
I'm using the term "link table" because it's short, doesn't collide with other terms already used (e.g. "relationship"), it's easy to remember how to write it, etc. I'm using the term "link table" because it's short, doesn't collide with other terms already used (e.g. "relationship"), it's easy to remember how to write it, etc.
///
## Link Primary Key ## Link Primary Key
Cool, we have a link table with **just two columns**. But remember that SQL databases [require each row to have a **primary key**](../../databases.md#identifications-primary-key){.internal-link target=_blank} that **uniquely identifies** the row in that table? Cool, we have a link table with **just two columns**. But remember that SQL databases [require each row to have a **primary key**](../../databases.md#identifications-primary-key){.internal-link target=_blank} that **uniquely identifies** the row in that table?

View File

@ -18,9 +18,12 @@ A row in the table `heroteamlink` points to **one** particular hero, but a singl
And also, the same row in the table `heroteamlink` points to **one** team, but a single team can be connected to **many** hero-team links, so it's also **one-to-many**. And also, the same row in the table `heroteamlink` points to **one** team, but a single team can be connected to **many** hero-team links, so it's also **one-to-many**.
!!! tip /// tip
The previous many-to-many relationship was also just two one-to-many relationships combined, but now it's going to be much more explicit. The previous many-to-many relationship was also just two one-to-many relationships combined, but now it's going to be much more explicit.
///
## Update Link Model ## Update Link Model
Let's update the `HeroTeamLink` model. Let's update the `HeroTeamLink` model.
@ -51,11 +54,14 @@ The new **relationship attributes** have their own `back_populates` pointing to
* `team`: has `back_populates="hero_links"`, because in the `Team` model, the attribute will contain the links to the **team's heroes**. * `team`: has `back_populates="hero_links"`, because in the `Team` model, the attribute will contain the links to the **team's heroes**.
* `hero`: has `back_populates="team_links"`, because in the `Hero` model, the attribute will contain the links to the **hero's teams**. * `hero`: has `back_populates="team_links"`, because in the `Hero` model, the attribute will contain the links to the **hero's teams**.
!!! info /// info
In SQLAlchemy this is called an Association Object or Association Model. In SQLAlchemy this is called an Association Object or Association Model.
I'm calling it **Link Model** just because that's easier to write avoiding typos. But you are also free to call it however you want. 😉 I'm calling it **Link Model** just because that's easier to write avoiding typos. But you are also free to call it however you want. 😉
///
## Update Team Model ## Update Team Model
Now let's update the `Team` model. Now let's update the `Team` model.

View File

@ -82,11 +82,14 @@ We can use the same **relationship attributes** to include `hero_spider_boy` in
</details> </details>
!!! tip /// tip
Because we are accessing an attribute in the models right after we commit, with `hero_spider_boy.teams` and `team_z_force.heroes`, the data is refreshed automatically. Because we are accessing an attribute in the models right after we commit, with `hero_spider_boy.teams` and `team_z_force.heroes`, the data is refreshed automatically.
So we don't have to call `session.refresh()`. So we don't have to call `session.refresh()`.
///
We then commit the change, refresh, and print the updated **Spider-Boy**'s heroes to confirm. We then commit the change, refresh, and print the updated **Spider-Boy**'s heroes to confirm.
Notice that we only `add` **Z-Force** to the session, then we commit. Notice that we only `add` **Z-Force** to the session, then we commit.

View File

@ -71,9 +71,12 @@ This will return the first object in the `results` (if there was any).
That way, we don't have to deal with an iterable or a list. That way, we don't have to deal with an iterable or a list.
!!! tip /// tip
Notice that `.first()` is a method of the `results` object, not of the `select()` statement. Notice that `.first()` is a method of the `results` object, not of the `select()` statement.
///
Although this query would find two rows, by using `.first()` we get only the first row. Although this query would find two rows, by using `.first()` we get only the first row.
If we run it in the command line it would output: If we run it in the command line it would output:

View File

@ -58,9 +58,12 @@ As you already know how this works, I won't separate that in a select `statement
</details> </details>
!!! tip /// tip
When writing your own code, this is probably the style you will use most often, as it's shorter, more convenient, and you still get all the power of autocompletion and inline errors. When writing your own code, this is probably the style you will use most often, as it's shorter, more convenient, and you still get all the power of autocompletion and inline errors.
///
## Print the Data ## Print the Data
Now, let's print the current **Spider-Boy**, the current **Preventers** team, and particularly, the current **Preventers** list of heroes: Now, let's print the current **Spider-Boy**, the current **Preventers** team, and particularly, the current **Preventers** list of heroes:
@ -127,9 +130,12 @@ The first important thing is, we *haven't committed* the hero yet, so accessing
But in our code, in this exact point in time, we already said that **Spider-Boy** is no longer part of the **Preventers**. 🔥 But in our code, in this exact point in time, we already said that **Spider-Boy** is no longer part of the **Preventers**. 🔥
!!! tip /// tip
We could revert that later by not committing the **session**, but that's not what we are interested in here. We could revert that later by not committing the **session**, but that's not what we are interested in here.
///
Here, at this point in the code, in memory, the code expects **Preventers** to *not include* **Spider-Boy**. Here, at this point in the code, in memory, the code expects **Preventers** to *not include* **Spider-Boy**.
The output of printing `hero_spider_boy` without team is: The output of printing `hero_spider_boy` without team is:
@ -247,11 +253,14 @@ And we can keep the rest of the code the same:
</details> </details>
!!! tip /// tip
This is the same section where we updated `hero_spider_boy.team` to `None` but we *haven't committed* that change yet. This is the same section where we updated `hero_spider_boy.team` to `None` but we *haven't committed* that change yet.
The same section that caused a problem before. The same section that caused a problem before.
///
## Review the Result ## Review the Result
This time, **SQLModel** (actually SQLAlchemy) will be able to notice the change, and **automatically update the list of heroes** in the team, even before we commit. This time, **SQLModel** (actually SQLAlchemy) will be able to notice the change, and **automatically update the list of heroes** in the team, even before we commit.
@ -336,9 +345,12 @@ So, the string `"heroes"` refers to the attribute `heroes` in the class `Team`.
</details> </details>
!!! tip /// tip
Each **relationship attribute** points to the other one, in the other model, using `back_populates`. Each **relationship attribute** points to the other one, in the other model, using `back_populates`.
///
Although it's simple code, it can be confusing to think about 😵, because the same line has concepts related to both models in multiple places: Although it's simple code, it can be confusing to think about 😵, because the same line has concepts related to both models in multiple places:
* Just by being in the **current** model, the line has something to do with the current model. * Just by being in the **current** model, the line has something to do with the current model.

View File

@ -123,11 +123,14 @@ And in the `Team` class, the `heroes` attribute is annotated as a list of `Hero`
**SQLModel** (actually SQLAlchemy) is smart enough to know that the relationship is established by the `team_id`, as that's the foreign key that points from the `hero` table to the `team` table, so we don't have to specify that explicitly here. **SQLModel** (actually SQLAlchemy) is smart enough to know that the relationship is established by the `team_id`, as that's the foreign key that points from the `hero` table to the `team` table, so we don't have to specify that explicitly here.
!!! tip /// tip
There's a couple of things we'll check again in some of the next chapters, about the `List["Hero"]` and the `back_populates`. There's a couple of things we'll check again in some of the next chapters, about the `List["Hero"]` and the `back_populates`.
But for now, let's first see how to use these relationship attributes. But for now, let's first see how to use these relationship attributes.
///
## Next Steps ## Next Steps
Now let's see some real examples of how to use these new **relationship attributes** in the next chapters. ✨ Now let's see some real examples of how to use these new **relationship attributes** in the next chapters. ✨

View File

@ -6,9 +6,12 @@ And then we read the data together with `select()` and using `.where()` or `.joi
Now we will see how to use **Relationship Attributes**, an extra feature of **SQLModel** (and SQLAlchemy) to work with the data in the database in way much more familiar way, and closer to normal Python code. Now we will see how to use **Relationship Attributes**, an extra feature of **SQLModel** (and SQLAlchemy) to work with the data in the database in way much more familiar way, and closer to normal Python code.
!!! info /// info
When I say "**relationship**" I mean the standard dictionary term, of data related to other data. When I say "**relationship**" I mean the standard dictionary term, of data related to other data.
I'm not using the term "**relation**" that is the technical, academical, SQL term for a single table. I'm not using the term "**relation**" that is the technical, academical, SQL term for a single table.
///
And using those **relationship attributes** is where a tool like **SQLModel** really shines. ✨ And using those **relationship attributes** is where a tool like **SQLModel** really shines. ✨

View File

@ -77,11 +77,14 @@ So, the highlighted block above, has the same results as the block below:
</details> </details>
!!! tip /// tip
The automatic data fetching will work as long as the starting object (in this case the `Hero`) is associated with an **open** session. The automatic data fetching will work as long as the starting object (in this case the `Hero`) is associated with an **open** session.
For example, here, **inside** a `with` block with a `Session` object. For example, here, **inside** a `with` block with a `Session` object.
///
## Get a List of Relationship Objects ## Get a List of Relationship Objects
And the same way, when we are working on the **many** side of the **one-to-many** relationship, we can get a list of of the related objects just by accessing the relationship attribute: And the same way, when we are working on the **many** side of the **one-to-many** relationship, we can get a list of of the related objects just by accessing the relationship attribute:

View File

@ -29,5 +29,8 @@ And of course, **SQLModel** can also understand it in the string correctly. ✨
That is actually part of Python, it's the current official solution to handle it. That is actually part of Python, it's the current official solution to handle it.
!!! info /// info
There's a lot of work going on in Python itself to make that simpler and more intuitive, and find ways to make it possible to not wrap the class in a string. There's a lot of work going on in Python itself to make that simpler and more intuitive, and find ways to make it possible to not wrap the class in a string.
///

View File

@ -79,13 +79,16 @@ You can try that out in **DB Browser for SQLite**:
<img class="shadow" src="/img/tutorial/select/image01.png"> <img class="shadow" src="/img/tutorial/select/image01.png">
!!! warning /// warning
Here we are getting all the rows. Here we are getting all the rows.
If you have thousands of rows, that could be expensive to compute for the database. If you have thousands of rows, that could be expensive to compute for the database.
You would normally want to filter the rows to receive only the ones you want. But we'll learn about that later in the next chapter. You would normally want to filter the rows to receive only the ones you want. But we'll learn about that later in the next chapter.
///
### A SQL Shortcut ### 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 we 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 `*`:
@ -240,11 +243,14 @@ We pass the class model `Hero` to the `select()` function. And that tells it tha
And notice that in the `select()` function we don't explicitly specify the `FROM` part. It is already obvious to **SQLModel** (actually to SQLAlchemy) that we want to select `FROM` the table `hero`, because that's the one associated with the `Hero` class model. And notice that in the `select()` function we don't explicitly specify the `FROM` part. It is already obvious to **SQLModel** (actually to SQLAlchemy) that we want to select `FROM` the table `hero`, because that's the one associated with the `Hero` class model.
!!! tip /// tip
The value of the `statement` returned by `select()` is a special object that allows us to do other things. The value of the `statement` returned by `select()` is a special object that allows us to do other things.
I'll tell you about that in the next chapters. I'll tell you about that in the next chapters.
///
## Execute the Statement ## Execute the Statement
Now that we have the `select` statement, we can execute it with the **session**: Now that we have the `select` statement, we can execute it with the **session**:
@ -360,9 +366,12 @@ Let's review the code up to this point:
{!./docs_src/tutorial/select/annotations/en/tutorial002.md!} {!./docs_src/tutorial/select/annotations/en/tutorial002.md!}
!!! tip /// tip
Check out the number bubbles to see what is done by each line of code. Check out the number bubbles to see what is done by each line of code.
///
Here it starts to become more evident why we should have a single **engine** for the whole application, but different **sessions** for each group of operations. Here it starts to become more evident why we should have a single **engine** for the whole application, but different **sessions** for each group of operations.
This new session we created uses the *same* **engine**, but it's a new and independent **session**. This new session we created uses the *same* **engine**, but it's a new and independent **session**.
@ -373,11 +382,14 @@ And the second section reading data from the database could be in another functi
So, both sections could be in **different places** and would need their own sessions. So, both sections could be in **different places** and would need their own sessions.
!!! info /// info
To be fair, in this example all that code could actually share the same **session**, there's actually no need to have two here. To be fair, in this example all that code could actually share the same **session**, there's actually no need to have two here.
But it allows me to show you how they could be separated and to reinforce the idea that you should have **one engine** per application, and **multiple sessions**, one per each group of operations. But it allows me to show you how they could be separated and to reinforce the idea that you should have **one engine** per application, and **multiple sessions**, one per each group of operations.
///
## Get a List of `Hero` Objects ## Get a List of `Hero` Objects
Up to now we are using the `results` to iterate over them. Up to now we are using the `results` to iterate over them.
@ -415,9 +427,12 @@ After printing it, we would see something like:
] ]
``` ```
!!! info /// info
It would actually look more compact, I'm formatting it a bit for you to see that it is actually a list with all the data. It would actually look more compact, I'm formatting it a bit for you to see that it is actually a list with all the data.
///
## Compact Version ## Compact Version
I have been creating several variables to be able to explain to you what each thing is doing. I have been creating several variables to be able to explain to you what each thing is doing.
@ -461,9 +476,12 @@ SQLAchemy also has it's own `select`, and SQLModel's `select` uses SQLAlchemy's
But SQLModel's version does a lot of **tricks** with type annotations to make sure you get the best **editor support** possible, no matter if you use **VS Code**, **PyCharm**, or something else. ✨ But SQLModel's version does a lot of **tricks** with type annotations to make sure you get the best **editor support** possible, no matter if you use **VS Code**, **PyCharm**, or something else. ✨
!!! info /// info
There was a lot of work and research, with different versions of the internal code, to improve this as much as possible. 🤓 There was a lot of work and research, with different versions of the internal code, to improve this as much as possible. 🤓
///
### SQLModel's `session.exec` ### SQLModel's `session.exec`
📢 This is one to pay special attention to. 📢 This is one to pay special attention to.
@ -492,11 +510,14 @@ On top of that, **SQLModel**'s `session.exec()` also does some tricks to reduce
But SQLModel's `Session` still has access to `session.execute()` too. But SQLModel's `Session` still has access to `session.execute()` too.
!!! tip /// tip
Your editor will give you autocompletion for both `session.exec()` and `session.execute()`. Your editor will give you autocompletion for both `session.exec()` and `session.execute()`.
📢 Remember to **always use `session.exec()`** to get the best editor support and developer experience. 📢 Remember to **always use `session.exec()`** to get the best editor support and developer experience.
///
### Caveats of **SQLModel** Flavor ### Caveats of **SQLModel** Flavor
SQLModel is designed to have the best **developer experience** in a narrow set of **very common use cases**. ✨ SQLModel is designed to have the best **developer experience** in a narrow set of **very common use cases**. ✨

View File

@ -41,13 +41,16 @@ And the second part, with the `WHERE`, defines to which rows it should apply tha
In this case, as we only have one hero with the name `"Spider-Boy"`, it will only apply the update in that row. In this case, as we only have one hero with the name `"Spider-Boy"`, it will only apply the update in that row.
!!! info /// info
Notice that in the `UPDATE` the single equals sign (`=`) means **assignment**, setting a column to some value. Notice that in the `UPDATE` the single equals sign (`=`) means **assignment**, setting a column to some value.
And in the `WHERE` the same single equals sign (`=`) is used for **comparison** between two values, to find rows that match. And in the `WHERE` the same single equals sign (`=`) is used for **comparison** between two values, to find rows that match.
This is in contrast to Python and most programming languages, where a single equals sign (`=`) is used for assignment, and two equal signs (`==`) are used for comparisons. This is in contrast to Python and most programming languages, where a single equals sign (`=`) is used for assignment, and two equal signs (`==`) are used for comparisons.
///
You can try that in **DB Browser for SQLite**: You can try that in **DB Browser for SQLite**:
<img class="shadow" src="/img/tutorial/update/image01.png"> <img class="shadow" src="/img/tutorial/update/image01.png">
@ -69,7 +72,8 @@ After that update, the data in the table will look like this, with the new age f
</tr> </tr>
</table> </table>
!!! tip /// tip
It will probably be more common to find the row to update by `id`, for example: It will probably be more common to find the row to update by `id`, for example:
```SQL ```SQL
@ -80,6 +84,8 @@ After that update, the data in the table will look like this, with the new age f
But in the example above I used `name` to make it more intuitive. But in the example above I used `name` to make it more intuitive.
///
Now let's do the same update in code, with **SQLModel**. Now let's do the same update in code, with **SQLModel**.
To get the same results, delete the `database.db` file before running the examples. To get the same results, delete the `database.db` file before running the examples.
@ -143,9 +149,12 @@ Hero: name='Spider-Boy' secret_name='Pedro Parqueador' age=None id=2
</div> </div>
!!! tip /// tip
Notice that by this point, the hero still doesn't have an age. Notice that by this point, the hero still doesn't have an age.
///
## Set a Field Value ## Set a Field Value
Now that you have a `hero` object, you can simply set the value of the field (the attribute representing a column) that you want. Now that you have a `hero` object, you can simply set the value of the field (the attribute representing a column) that you want.
@ -333,9 +342,12 @@ Now let's review all that code:
{!./docs_src/tutorial/update/annotations/en/tutorial002.md!} {!./docs_src/tutorial/update/annotations/en/tutorial002.md!}
!!! tip /// tip
Check out the number bubbles to see what is done by each line of code. Check out the number bubbles to see what is done by each line of code.
///
## Multiple Updates ## Multiple Updates
The update process with **SQLModel** is more or less the same as with creating new objects, you add them to the session, and then commit them. The update process with **SQLModel** is more or less the same as with creating new objects, you add them to the session, and then commit them.
@ -361,9 +373,12 @@ This also means that you can update several fields (attributes, columns) at once
</details> </details>
!!! tip /// tip
Review what each line does by clicking each number bubble in the code. 👆 Review what each line does by clicking each number bubble in the code. 👆
///
## Recap ## Recap
Update **SQLModel** objects just as you would with other Python objects. 🐍 Update **SQLModel** objects just as you would with other Python objects. 🐍

View File

@ -81,11 +81,14 @@ Then the database will bring a table like this:
</tr> </tr>
</table> </table>
!!! tip /// tip
Even if the result is only one row, the database always returns a **table**. Even if the result is only one row, the database always returns a **table**.
In this case, a table with only one row. In this case, a table with only one row.
///
You can try that out in **DB Browser for SQLite**: You can try that out in **DB Browser for SQLite**:
<img class="shadow" src="/img/tutorial/where/image01.png"> <img class="shadow" src="/img/tutorial/where/image01.png">
@ -268,11 +271,14 @@ So, what's happening there?
In the example above we are using two equal signs (`==`). That's called the "**equality operator**". In the example above we are using two equal signs (`==`). That's called the "**equality operator**".
!!! tip /// tip
An **operator** is just a symbol that is put beside one value or in the middle of two values to do something with them. An **operator** is just a symbol that is put beside one value or in the middle of two values to do something with them.
`==` is called the **equality** operator because it checks if two things are **equal**. `==` is called the **equality** operator because it checks if two things are **equal**.
///
When writing Python, if you write something using this equality operator (`==`) like: When writing Python, if you write something using this equality operator (`==`) like:
```Python ```Python
@ -291,9 +297,12 @@ True
False False
``` ```
!!! tip /// tip
`<`, `>`, `==`, `>=`, `<=`, and `!=` are all **operators** used for **comparisons**. `<`, `>`, `==`, `>=`, `<=`, and `!=` are all **operators** used for **comparisons**.
///
But SQLAlchemy adds some magic to the columns/fields in a **model class** to make those Python comparisons have super powers. But SQLAlchemy adds some magic to the columns/fields in a **model class** to make those Python comparisons have super powers.
So, if you write something like: So, if you write something like:
@ -451,9 +460,12 @@ select(Hero).where(Hero.secret_name == "Pedro Parqueador")
I think that alone, having better editor support, autocompletion, and inline errors, is enough to make it worth having expressions instead of keyword arguments. ✨ I think that alone, having better editor support, autocompletion, and inline errors, is enough to make it worth having expressions instead of keyword arguments. ✨
!!! tip /// tip
**Expressions** also provide more features for other types of comparisons, shown down below. 👇 **Expressions** also provide more features for other types of comparisons, shown down below. 👇
///
## Exec the Statement ## Exec the Statement
Now that we know how `.where()` works, let's finish the code. Now that we know how `.where()` works, let's finish the code.
@ -502,13 +514,16 @@ secret_name='Dive Wilson' age=None id=1 name='Deadpond'
</div> </div>
!!! tip /// tip
The `results` object is an iterable to be used in a `for` loop. The `results` object is an iterable to be used in a `for` loop.
Even if we got only one row, we iterate over that `results` object. Just as if it was a list of one element. Even if we got only one row, we iterate over that `results` object. Just as if it was a list of one element.
We'll see other ways to get the data later. We'll see other ways to get the data later.
///
## Other Comparisons ## Other Comparisons
Here's another great advantage of these special **expressions** passed to `.where()`. Here's another great advantage of these special **expressions** passed to `.where()`.
@ -597,9 +612,12 @@ age=36 id=6 name='Dr. Weird' secret_name='Steve Weird'
age=93 id=7 name='Captain North America' secret_name='Esteban Rogelios' age=93 id=7 name='Captain North America' secret_name='Esteban Rogelios'
``` ```
!!! tip /// tip
Notice that it didn't select `Black Lion`, because the age is not *strictly* greater than `35`. Notice that it didn't select `Black Lion`, because the age is not *strictly* greater than `35`.
///
### More Than or Equal ### More Than or Equal
Let's do that again, but with `>=` to get the rows where a column is **more than or equal** to a value: Let's do that again, but with `>=` to get the rows where a column is **more than or equal** to a value:
@ -630,9 +648,12 @@ age=36 id=6 name='Dr. Weird' secret_name='Steve Weird'
age=93 id=7 name='Captain North America' secret_name='Esteban Rogelios' age=93 id=7 name='Captain North America' secret_name='Esteban Rogelios'
``` ```
!!! tip /// tip
This time we got `Black Lion` too because although the age is not *strictly* greater than `35`it is *equal* to `35`. This time we got `Black Lion` too because although the age is not *strictly* greater than `35`it is *equal* to `35`.
///
### Less Than ### Less Than
Similarly, we can use `<` to get the rows where a column is **less than** a value: Similarly, we can use `<` to get the rows where a column is **less than** a value:
@ -660,9 +681,12 @@ And we get the younger one with an age in the database:
age=32 id=4 name='Tarantula' secret_name='Natalia Roman-on' age=32 id=4 name='Tarantula' secret_name='Natalia Roman-on'
``` ```
!!! tip /// tip
We could imagine that **Spider-Boy** is even **younger**. But because we don't know the age, it is `NULL` in the database (`None` in Python), it doesn't match any of these age comparisons with numbers. We could imagine that **Spider-Boy** is even **younger**. But because we don't know the age, it is `NULL` in the database (`None` in Python), it doesn't match any of these age comparisons with numbers.
///
### Less Than or Equal ### Less Than or Equal
Finally, we can use `<=` to get the rows where a column is **less than or equal** to a value: Finally, we can use `<=` to get the rows where a column is **less than or equal** to a value:
@ -691,9 +715,12 @@ age=32 id=4 name='Tarantula' secret_name='Natalia Roman-on'
age=35 id=5 name='Black Lion' secret_name='Trevor Challa' age=35 id=5 name='Black Lion' secret_name='Trevor Challa'
``` ```
!!! tip /// tip
We get `Black Lion` here too because although the age is not *strictly* less than `35` it is *equal* to `35`. We get `Black Lion` here too because although the age is not *strictly* less than `35` it is *equal* to `35`.
///
### Benefits of Expressions ### Benefits of Expressions
Here's a good moment to see that being able to use these pure Python expressions instead of keyword arguments can help a lot. ✨ Here's a good moment to see that being able to use these pure Python expressions instead of keyword arguments can help a lot. ✨
@ -925,11 +952,14 @@ col(Hero.age) > 35
And with that the editor knows this code is actually fine, because this is a special **SQLModel** column. And with that the editor knows this code is actually fine, because this is a special **SQLModel** column.
!!! tip /// tip
That `col()` will come handy later, giving autocompletion to several other things we can do with these special **class attributes** for columns. That `col()` will come handy later, giving autocompletion to several other things we can do with these special **class attributes** for columns.
But we'll get there later. But we'll get there later.
///
## Recap ## Recap
You can use `.where()` with powerful expressions using **SQLModel** columns (the special class attributes) to filter the rows that you want. 🚀 You can use `.where()` with powerful expressions using **SQLModel** columns (the special class attributes) to filter the rows that you want. 🚀

View File

@ -21,6 +21,13 @@ theme:
- search.suggest - search.suggest
- search.highlight - search.highlight
- content.tabs.link - content.tabs.link
- navigation.indexes
- content.tooltips
- navigation.path
- content.code.annotate
- content.code.copy
- content.code.select
# - navigation.tabs
icon: icon:
repo: fontawesome/brands/github-alt repo: fontawesome/brands/github-alt
logo: img/icon-white.svg logo: img/icon-white.svg
@ -91,20 +98,28 @@ nav:
- release-notes.md - release-notes.md
markdown_extensions: markdown_extensions:
- markdown.extensions.attr_list
- markdown.extensions.tables
- markdown.extensions.md_in_html
- toc: - toc:
permalink: true permalink: true
- markdown.extensions.codehilite:
guess_lang: false
- admonition
- codehilite
- extra
- pymdownx.superfences: - pymdownx.superfences:
custom_fences: custom_fences:
- name: mermaid - name: mermaid
class: mermaid class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format '' format: !!python/name:pymdownx.superfences.fence_code_format ''
- pymdownx.tabbed: - pymdownx.betterem
alternate_style: true - pymdownx.highlight
- pymdownx.blocks.details
- pymdownx.blocks.admonition:
types:
- note
- info
- tip
- warning
- danger
- pymdownx.blocks.tab:
alternate_style: True
- mdx_include - mdx_include
extra: extra: