When you use `Decimal` you can specify the number of digits and decimal places to support in the `Field()` function. They will be validated by Pydantic (for example when using FastAPI) and the same information will also be used for the database columns.
-/// info
+/// note
For the database, **SQLModel** will use <a href="https://docs.sqlalchemy.org/en/20/core/type_basics.html#sqlalchemy.types.DECIMAL" class="external-link" target="_blank">SQLAlchemy's `DECIMAL` type</a>.
UUIDs can be particularly useful as an alternative to auto-incrementing integers for **primary keys**.
-/// info
+/// note
Official support for UUIDs was added in SQLModel version `0.0.20`.
# Intro to Databases
-/// info
+/// note
Are you a seasoned developer and already know everything about databases? 🤓
I'll tell you more about SQL, SQLModel, how to use them, and how they are related in the next sections.
-/// info | Technical Details
+/// note | 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.
But we never deleted the `hero` table. 🎉
-/// info
+/// note
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.
## SQL Table Names
-/// info | Technical Background
+/// note | Technical Background
This is a bit of boring background for SQL purists. Feel free to skip this section. 😉
* Then **comment** saying that you did that, that's how I will know you really checked it.
-/// info
+/// note
Unfortunately, I can't simply trust PRs that just have several approvals.
<img alt="table relationships" src="/img/tutorial/relationships/select/relationships2.drawio.svg">
-/// info
+/// note
We will later update **Spider-Boy** to add him to the **Preventers** team too, but not yet.
If you had a custom table name, you would use that custom table name.
-/// info
+/// note
You can learn about setting a custom table name for a model in the Advanced User Guide.
WHERE hero.team_id = team.id
```
-/// info
+/// note
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.
And in this `for` loop we assign them to the variable `hero` and the variable `team`.
-/// info
+/// note
There was a lot of research, design, and work behind **SQLModel** to make this provide the best possible developer experience.
We use the config `table=True` to tell **SQLModel** that this is a **table model**, it represents a table.
-/// info
+/// note
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**.
</div>
-/// info
+/// note
I simplified the output above a bit to make it easier to read.
...will **not** be executed.
-/// info
+/// note
For more information, check <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">the official Python docs</a>.
Let's use the same **offset** and **limit** we learned about in the previous tutorial chapters for the API.
-/// info
+/// note
In many cases, this is also called **pagination**.
This way, a client can decide to take fewer heroes if they want, but not more.
-/// info
+/// note
If you need to refresh how query parameters and their validation work, check out the docs in FastAPI:
We want to get the hero based on the `id`, so we will use a **path parameter** `hero_id`.
-/// info
+/// note
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>.
For example, client generators, that can automatically create the code necessary to talk to your API in many languages.
-/// info
+/// note
If you are curious about the standards, FastAPI generates OpenAPI, that internally uses JSON Schema.
And we also need to disable it because in **FastAPI** each request could be handled by multiple interacting threads.
-/// info
+/// note
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>.
## Create Heroes *Path Operation*
-/// info
+/// note
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>.
{* ./docs_src/tutorial/fastapi/simple_hero_api/tutorial001_py310.py ln[23:37] hl[31:32] *}
-/// info
+/// note
If you need a refresher on some of those concepts, checkout the FastAPI documentation:
</div>
-/// info
+/// note
The `fastapi` command uses <a href="https://www.uvicorn.org/" class="external-link" target="_blank">Uvicorn</a> underneath.
We are using **pytest** to run the tests. And pytest also has a very similar concept to the **dependencies in FastAPI**.
-/// info
+/// note
In fact, pytest was one of the things that inspired the design of the dependencies in FastAPI.
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
+/// note
SQLModel (actually SQLAlchemy) will **automatically generate the index name** for you.
This makes the interactions with the database more efficient (plus some extra benefits).
-/// info | Technical Details
+/// note | Technical Details
The session will create a new transaction and execute all the SQL code in that transaction.
</tr>
</table>
-/// info
+/// note
Other names used for this **link table** are:
* `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**.
-/// info
+/// note
In SQLAlchemy this is called an Association Object or Association Model.
Let's see how to configure that with **SQLModel**.
-/// info
+/// note
This feature, including `cascade_delete`, `ondelete`, and `passive_deletes`, is available since SQLModel version `0.0.21`.
{* ./docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial003_py310.py ln[30:33] hl[33] *}
-/// info
+/// note
You can learn more about SQLite, foreign keys, and this SQL command on the <a href="https://docs.sqlalchemy.org/en/20/dialects/sqlite.html#foreign-key-support" class="external-link" target="_blank">SQLAlchemy docs</a>.
Now we will see how to use **Relationship Attributes**, an extra feature of **SQLModel** (and SQLAlchemy), to work with the data in the database in a much more familiar way, and closer to normal Python code.
-/// info
+/// note
When I say "**relationship**" I mean the standard dictionary term, of data related to other data.
That is actually part of Python, it's the current official solution to handle it.
-/// info
+/// note
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.
So, both sections could be in **different places** and would need their own sessions.
-/// info
+/// note
To be fair, in this example all that code could actually share the same **session**, there's actually no need to have two here.
]
```
-/// info
+/// note
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.
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
+/// note
There was a lot of work and research, with different versions of the internal code, to improve this as much as possible. 🤓
In this case, as we only have one hero with the name `"Spider-Boy"`, it will only apply the update in that row.
-/// info
+/// note
Notice that in the `UPDATE` the single equals sign (`=`) means **assignment**, setting a column to some value.
When you work in Python projects you probably should use a **virtual environment** (or a similar mechanism) to isolate the packages you install for each project.
-/// info
+/// note
If you already know about virtual environments, how to create them and use them, you might want to skip this section. 🤓
///
-/// info
+/// note
This page will teach you how to use **virtual environments** and how they work.
33. Print the `hero_1`.
- /// info
+ /// note
Even if the `hero_1` wasn't fresh, this would **not** trigger a `refresh` making the **session** use the **engine** to fetch data from the database because it is not accessing an attribute.
34. Print the `hero_2`.
- /// info
+ /// note
Even if the `hero_2` wasn't fresh, this would **not** trigger a `refresh` making the **session** use the **engine** to fetch data from the database because it is not accessing an attribute.
35. Print the `hero_3`.
- /// info
+ /// note
Even if the `hero_3` wasn't fresh, this would **not** trigger a `refresh` making the **session** use the **engine** to fetch data from the database because it is not accessing an attribute.
We tell it that with the `poolclass=StaticPool` parameter.
- /// info
+ /// note
You can read more details in the <a href="https://docs.sqlalchemy.org/en/14/dialects/sqlite.html#using-a-memory-database-in-multiple-threads" class="external-link" target="_blank">SQLAlchemy documentation about Using a Memory Database in Multiple Threads</a>
# pymdownx blocks
pymdownx.blocks.admonition:
- types:
- - note
- - attention
- - caution
- - danger
- - error
- - tip
- - hint
- - warning
- # Custom types
- - info
pymdownx.blocks.details:
pymdownx.blocks.tab:
alternate_style: True