📝 Add docs

This commit is contained in:
Sebastián Ramírez
2021-08-24 15:02:48 +02:00
parent 29017c60a7
commit 6d1d86ab85
51 changed files with 12260 additions and 0 deletions

View File

@@ -0,0 +1,374 @@
# Relationship back_populates
Now you know how to use the **relationship attributes** to manipulate connected data in the database! 🎉
Let's now take a small step back and review how we defined those `Relationship()` attributes again, let's clarify that `back_populates` argument. 🤓
## Relationship with `back_populates`
So, what is that `back_populates` argument in each `Relationship()`?
The value is a string with the name of the attribute in the **other** model class.
<img src="/img/tutorial/relationships/attributes/back-populates.svg">
That tells **SQLModel** that if something changes in this model, it should change that attribute in the other model, and it will work even before committing with the session (that would force a refresh of the data).
Let's understand that better with an example.
## An Incomplete Relationship
Let's see how that works by writing an **incomplete** version first, without `back_populates`:
```Python hl_lines="11 21"
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py[ln:1-21]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py!}
```
</details>
## Read Data Objects
Now, we will get the **Spider-Boy** hero and, *independently*, the **Preventers** team using two `select`s.
As you already know how this works, I won't separate that in a select `statement`, `results`, etc. Let's use the shorter form in a single call:
```Python hl_lines="5-7 9-11"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py[ln:105-113]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py!}
```
</details>
!!! 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.
## Print the Data
Now, let's print the current **Spider-Boy**, the current **Preventers** team, and particularly, the current **Preventers** list of heroes:
```Python hl_lines="13-15"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py[ln:105-117]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py!}
```
</details>
Up to this point, it's all good. 😊
In particular, the result of printing `preventers_team.heroes` is:
``` hl_lines="3"
Preventers Team Heroes: [
Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2),
Hero(name='Spider-Boy', age=None, id=3, secret_name='Pedro Parqueador', team_id=2),
Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2),
Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2),
Hero(name='Captain North America', age=93, id=8, secret_name='Esteban Rogelios', team_id=2)
]
```
Notice that we have **Spider-Boy** there.
## Update Objects Before Committing
Now let's update **Spider-Boy**, removing him from the team by setting `hero_spider_boy.team = None` and then let's print this object again:
```Python hl_lines="8 12"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py[ln:105-106]!}
# Code here omitted 👈
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py[ln:119-123]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py!}
```
</details>
The first important thing is, we *haven't commited* the hero yet, so accessing the list of heroes would not trigger an automatic refresh.
But in our code, in this exact point in time, we already said that **Spider-Boy** is no longer part of the **Preventers**. 🔥
!!! tip
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**.
The output of printing `hero_spider_boy` without team is:
```
Spider-Boy without team: name='Spider-Boy' age=None id=3 secret_name='Pedro Parqueador' team_id=2 team=None
```
Cool, the team is set to `None`, the `team_id` attribute still has the team ID until we save it. But that's okay as we are now working mainly with the **relationship attributes** and the objects. ✅
But now, what happens when we print the `preventers_team.heroes`?
``` hl_lines="3"
Preventers Team Heroes again: [
Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2),
Hero(name='Spider-Boy', age=None, id=3, secret_name='Pedro Parqueador', team_id=2, team=None),
Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2),
Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2),
Hero(name='Captain North America', age=93, id=8, secret_name='Esteban Rogelios', team_id=2)
]
```
Oh, no! 😱 **Spider-Boy** is still listed there!
## Commit and Print
Now, if we commit it and print again:
```Python hl_lines="8-9 15"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py[ln:105-106]!}
# Code here omitted 👈
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py[ln:125-132]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py!}
```
</details>
When we access `preventers_team.heroes` after the `commit`, that triggers a refresh, so we get the latest list, without **Spider-Boy**, so that's fine again:
```
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age, hero.team_id AS hero_team_id
FROM hero
WHERE ? = hero.team_id
2021-08-13 11:15:24,658 INFO sqlalchemy.engine.Engine [cached since 0.1924s ago] (2,)
Preventers Team Heroes after commit: [
Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2),
Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2),
Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2),
Hero(name='Captain North America', age=93, id=8, secret_name='Esteban Rogelios', team_id=2)
]
```
There's no **Spider-Boy** after committing, so that's good. 😊
But we still have that inconsistency in that previous point above.
If we use the objects before committing, we could end up having errors. 😔
Let's fix that. 🤓
## Fix It Using `back_populates`
That's what `back_populates` is for. ✨
Let's add it back:
```Python hl_lines="11 21"
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py[ln:1-21]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py!}
```
</details>
And we can keep the rest of the code the same:
```Python hl_lines="8 12"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py[ln:105-106]!}
# Code here omitted 👈
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py[ln:119-123]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py!}
```
</details>
!!! tip
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.
## 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.
That second print would output:
```
Preventers Team Heroes again: [
Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2),
Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2),
Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2),
Hero(name='Captain North America', age=93, id=8, secret_name='Esteban Rogelios', team_id=2)
]
```
Notice that now **Spider-Boy** is not there, we fixed it with `back_populates`! 🎉
## The Value of `back_populates`
Now that you know why `back_populates` is there, let's review the exact value again.
It's quite simple code, it's just a string, but it might be confusing to think exactly *what* string should go there:
```Python hl_lines="11 21"
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py[ln:1-21]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py!}
```
</details>
The string in `back_populates` is the name of the attribute *in the other* model, that will reference *the current* model.
<img src="/img/tutorial/relationships/attributes/back-populates.svg">
So, in the class `Team`, we have an attribute `heroes` and we declare it with `Relationship(back_populates="team")`.
```Python hl_lines="8"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py[ln:6-11]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py!}
```
</details>
The string in `back_populates="team"` refers to the attribute `team` in the class `Hero` (the other class).
And, in the class `Hero`, we declare an attribute `team`, and we declare it with `Relationship(back_populates="heroes")`.
So, the string `"heroes"` refers to the attribute `heroes` in the class `Team`.
```Python hl_lines="10"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py[ln:14-21]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py!}
```
</details>
!!! tip
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:
* Just by being in the **current** model, the line has something to do with the current model.
* The name of the attribute is about the **other** model.
* The type annotation is about the **other** model.
* And the `back_populates` refers to an attribute in the **other** model, that points to the **current** model.
## A Mental Trick to Remember `back_populates`
A mental trick you can use to remember is that the string in `back_populates` is always about the current model class you are editing. 🤓
So, if you are in the class `Hero`, the value of `back_populates` for any relationship attribute connecting to **any** other table (to any other model, it could be `Team`, `Weapon`, `Powers`, etc) will still always refer to this same class.
So, `back_populates` would most probably be something like `"hero"` or `"heroes"`.
<img src="/img/tutorial/relationships/attributes/back-populates2.svg">
```Python hl_lines="3 10 13 15"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial003.py[ln:29-41]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/back_populates/tutorial003.py!}
```
</details>

View File

@@ -0,0 +1,168 @@
# Create and Update Relationships
Let's see now how to create data with relationships using these new **relationship attributes**. ✨
## Create Instances with Fields
Let's check the old code we used to create some heroes and teams:
```Python hl_lines="9 12 18 24"
# Code above omitted 👆
{!./docs_src/tutorial/connect/insert/tutorial001.py[ln:31-60]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/insert/tutorial001.py!}
```
</details>
There are several things to **notice** here.
First, we **create** some `Team` instance objects. We want to use the IDs of these teams when creating the `Hero` instances, in the `team_id` field.
But model instances **don't have an ID** generated by the database until we `add` and `commit` them to the **session**. Before that, they are just `None`, and we want to use the actual IDs.
So, we have to `add` them and `commit` the session first, before we start creating the `Hero` instances, to be able to **use their IDs**.
Then, we use those IDs when creating the `Hero` instances. We `add` the new heroes to the session, and then we `commit` them.
So, we are **committing twice**. And we have to remember to `add` some things first, and then `commit`, and do all that **in the right order**, otherwise we could end up using a `team.id` that is currently `None` because it hasn't been saved.
This is the first area where these **relationship attributes** can help. 🤓
## Create Instances with Relationship Attributes
Now let's do all that, but this time using the new, shiny `Relationship` attributes:
```Python hl_lines="9 12 18"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py[ln:34-57]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py!}
```
</details>
Now we can create the `Team` instances and pass them directly to the new `team` argument when creating the `Hero` instances, as `team=team_preventers` instead of `team_id=team_preventers.id`.
And thanks to SQLAlchemy and how it works underneath, these teams don't even have to have an ID yet, but because we are assigning the whole object to each hero, those teams **will be automatically created** in the database, the automatic ID will be generated, and will be set in the `team_id` column for each of the corresponding hero rows.
In fact, now we don't even have to put the teams explicitly in the session with `session.add(team)`, because these `Team` instances are **already associated** with heroes that **we do** `add` to the session.
SQLAlchemy knows that it also has to include those teams in the next commit to be able to save the heroes correctly.
And then, as you can see, we only have to do one `commit()`.
## Assign a Relationship
The same way we could assign an integer with a `team.id` to a `hero.team_id`, we can also assign the `Team` instance to the `hero.team`:
```Python hl_lines="8"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001.py[ln:34-35]!}
# Previous code here omitted 👈
{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001.py[ln:59-63]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001.py!}
```
</details>
## Create a Team with Heroes
Before, we created some `Team` instances and passed them in the `team=` argument when creating `Hero` instances.
We could also create the `Hero` instances first, and then pass them in the `heroes=` argument that takes a list, when creating a `Team` instance:
```Python hl_lines="13 15-16"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001.py[ln:34-35]!}
# Previous code here omitted 👈
{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001.py[ln:65-75]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001.py!}
```
</details>
Here we create two heroes first, **Black Lion** and **Princess Sure-E**, and then we pass them in the `heroes` argument.
Notice that, the same as before, we only have to `add` the `Team` instance to the session, and because the heroes are connected to it, they will be automatically saved too when we `commit`.
## Include Relationship Objects in the Many Side
We said before that this is a **many-to-one** relationship, because there can be **many** heroes that belong to **one** team.
We can also connect data with these relationship attributes on the **many** side.
As the attribute `team.heroes` behaves like a list, we can simply append to it.
Let's create some more heroes and add them to the `team_preventers.heroes` list attribute:
```Python hl_lines="14-18"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001.py[ln:34-35]!}
# Previous code here omitted 👈
{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001.py[ln:77-93]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001.py!}
```
</details>
The attribute `team_preventers.heroes` behaves like a list. But it's a special type of list, because when we modify it adding heroes to it, **SQLModel** (actually SQLAlchemy) **keeps track of the necessary changes** to be done in the database.
Then we `add()` the team to the session and `commit()` it.
And in the same way as before, we don't even have to `add()` the independent heroes to the session, because they are **connected to the team**.
## Recap
We can use common Python objects and attributes to create and update data connections with these **relationship attributes**. 😎
Next we'll see how to use these relationship attributes to read connected data. 🤝

View File

@@ -0,0 +1,135 @@
# Define Relationships Attributes
Now we are finally in one of the most exciting parts of **SQLModel**.
Relationship Attributes. ✨
We currently have a `team` table:
<table>
<tr>
<th>id</th><th>name</th><th>headquarters</th>
</tr>
<tr>
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
</tr>
<tr>
<td>2</td><td>Z-Force</td><td>Sister Margarets Bar</td>
</tr>
</table>
And a `hero` table:
<table>
<tr>
<th>id</th><th>name</th><th>secret_name</th><th>age</th><th>team_id</th>
</tr>
<tr>
<td>1</td><td>Deadpond</td><td>Dive Wilson</td><td>null</td><td>2</td>
</tr>
<tr>
<td>2</td><td>Rusty-Man</td><td>Tommy Sharp</td><td>48</td><td>1</td>
</tr>
<tr>
<td>3</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td><td>1</td>
</tr>
</table>
Now that you know how these tables work underneath and how the model classes represent them, it's time to add a little convenience that will make many operations in code simpler.
## Declare Relationship Attributes
Up to now, we have only used the `team_id` column to connect the tables when querying with `select()`:
```Python hl_lines="18"
{!./docs_src/tutorial/connect/insert/tutorial001.py[ln:1-18]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/insert/tutorial001.py!}
```
</details>
This is a **plain field** like all the others, all representing a **column in the table**.
But now let's add a couple of new special attributes to these model classes, let's add `Relationship` attributes.
First, import `Relationship` from `sqlmodel`:
```Python hl_lines="3"
{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py[ln:1-3]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py!}
```
</details>
Next, use that `Relationship` to declare a new attribute in the model classes:
```Python hl_lines="11 21"
{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py[ln:1-21]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py!}
```
</details>
## What Are These Relationship Attributes
This new attributes are not the same as fields, they **don't represent a column** directly in the database, and their value is not a singular value like an integer. Their value is the actual **entire object** that is related.
So, in the case of a `Hero` instance, if you call `hero.team`, you will get the entire `Team` instance object that this hero belongs to. ✨
For example, you could check if a `hero` belongs to any `team` (if `.team` is not `None`) and then print the team's `name`:
```Python
if hero.team:
print(hero.team.name)
```
## Optional Relationship Attributes
Notice that in the `Hero` class, the type annotation for `team` is `Optional[Team]`.
This means that this attribute could be `None`, or it could be a full `Team` object.
This is because the related **`team_id` could also be `None`** (or `NULL` in the database).
If it was required for a `Hero` instance to belong to a `Team`, then the `team_id` would be `int` instead of `Optional[int]`.
And the `team` attribute would be a `Team` instead of `Optional[Team]`.
## Relationship Attributes With Lists
And in the `Team` class, the `heroes` attribute is annotated as a list of `Hero` objects, because that's what it will have.
**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
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.
## Next Steps
Now let's see some real examples of how to use these new **relationship attributes** in the next chapters. ✨

View File

@@ -0,0 +1,14 @@
# Relationship Attributes - Intro
In the previous chapters we discussed how to manage databases with tables that have **relationships** by using fields (columns) with **foreign keys** pointing to other columns.
And then we read the data together with `select()` and using `.where()` or `.join()` to connect it.
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
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.
And using those **relationship attributes** is where a tool like **SQLModel** really shines. ✨

View File

@@ -0,0 +1,133 @@
# Read Relationships
Now that we know how to connect data using **relationship Attributes**, let's see how to get and read the objects from a relationship.
## Select a Hero
First, add a function `select_heroes()` where we get a hero to start working with, and add that function to the `main()` function:
```Python hl_lines="3-7 14"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001.py[ln:96-100]!}
# Previous code here omitted 👈
{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001.py[ln:110-113]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001.py!}
```
</details>
## Select the Related Team - Old Way
Now that we have a hero, we can get the team this hero belongs to.
With what we have learned **up to now**, we could use a `select()` statement, then execute it with `session.exec()`, and then get the `.first()` result, for example:
```Python hl_lines="9-12"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001.py[ln:96-105]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001.py!}
```
</details>
## Get Relationship Team - New Way
But now that we have the **relationship attributes**, we can just access them, and **SQLModel** (actually SQLAlchemy) will go and fetch the correspoinding data from the database, and make it available in the attribute. ✨
So, the highlighted block above, has the same results as the block below:
```Python hl_lines="11"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001.py[ln:96-100]!}
# Code from the previous example omitted 👈
{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001.py[ln:107]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial001.py!}
```
</details>
!!! tip
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.
## 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:
```Python hl_lines="9"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002.py[ln:96-102]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002.py!}
```
</details>
That would print a list with all the heroes in the Preventers team:
<div class="termy">
```console
$ python app.py
// Automatically fetch the heroes
INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age, hero.team_id AS hero_team_id
FROM hero
WHERE ? = hero.team_id
INFO Engine [cached since 0.8774s ago] (2,)
// Print the list of Preventers
Preventers heroes: [
Hero(name='Rusty-Man', age=48, id=2, secret_name='Tommy Sharp', team_id=2),
Hero(name='Spider-Boy', age=None, id=3, secret_name='Pedro Parqueador', team_id=2),
Hero(name='Tarantula', age=32, id=6, secret_name='Natalia Roman-on', team_id=2),
Hero(name='Dr. Weird', age=36, id=7, secret_name='Steve Weird', team_id=2),
Hero(name='Captain North America', age=93, id=8, secret_name='Esteban Rogelios', team_id=2)
]
```
</div>
## Recap
With **relationship attributes** you can use the power of common Python objects to easily access related data from the database. 😎

View File

@@ -0,0 +1,51 @@
# Remove Relationships
Now let's say that **Spider-Boy** tells **Rusty-Man** something like:
> I don't feel so good Mr. Sharp
And then for some reason needs to leave the **Preventers** for some years. 😭
We can remove the relationship by setting it to `None`, the same as with the `team_id`, it also works with the new relationship attribute `.team`:
```Python hl_lines="9"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002.py[ln:105-116]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002.py!}
```
</details>
And of course, we should remember to add this `update_heroes()` function to `main()` so that it runs when we call this program from the command line:
```Python hl_lines="7"
# Code above omitted 👆
{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002.py[ln:119-123]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/read_relationships/tutorial002.py!}
```
</details>
## Recap
This chapter was too short for a recap, wasn't it? 🤔
Anyway, **relationship attributes** make it easy and intuitive to work with relationships stored in the database. 🎉

View File

@@ -0,0 +1,33 @@
## About the String in `List["Hero"]`
In the first Relationship attribute, we declare it with `List["Hero"]`, putting the `Hero` in quotes instead of just normally there:
```Python hl_lines="11"
{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py[ln:1-21]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py!}
```
</details>
What's that about? Can't we just write it normally as `List[Hero]`?
By that point, in that line in the code, the Python interpreter **doesn't know of any class `Hero`**, and if we put it just there, it would try to find it unsuccessfully, and then fail. 😭
But by putting it in quotes, in a string, the interpreter sees it as just a string with the text `"Hero"` inside.
But the editor and other tools can see that **the string is actually a type annotation inside**, and provide all the autocompletion, type checks, etc. 🎉
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.
!!! 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.