Skip to content

Commit d329a50

Browse files
committed
Merge remote-tracking branch 'origin/main' into ami/pydantic-jsonb
2 parents b28f242 + 14c2fc6 commit d329a50

File tree

81 files changed

+14727
-2836
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+14727
-2836
lines changed

docs/advanced/index.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
# Advanced User Guide
22

3-
The **Advanced User Guide** is gradually growing, you can already read about some advanced topics.
3+
The **Advanced User Guide** covers advanced topics and features of SQLModel.
44

5-
At some point it will include:
5+
Current topics include:
66

7-
* How to use `async` and `await` with the async session.
8-
* How to run migrations.
9-
* How to combine **SQLModel** models with SQLAlchemy.
7+
* [Working with Decimal Fields](decimal.md) - How to handle decimal numbers in SQLModel
8+
* [Working with UUID Fields](uuid.md) - How to use UUID fields in your models
9+
* [Storing Pydantic Models in JSONB Columns](pydantic-jsonb.md) - How to store and work with Pydantic models in JSONB columns
10+
11+
Coming soon:
12+
13+
* How to use `async` and `await` with the async session
14+
* How to run migrations
15+
* How to combine **SQLModel** models with SQLAlchemy
1016
* ...and more. 🤓

docs/advanced/pydantic-jsonb.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Storing Pydantic Models in JSONB Columns
2+
3+
You can store Pydantic models (and lists or dicts of them) in JSON or JSONB database columns using the `PydanticJSONB` utility.
4+
5+
This is especially useful when:
6+
7+
- You want to persist flexible, nested data structures in your models.
8+
- You prefer to avoid separate relational tables for structured fields like metadata, config, or address.
9+
- You want automatic serialization and deserialization using Pydantic.
10+
11+
## Usage
12+
13+
You can use it with SQLModel like this:
14+
15+
```python
16+
from typing import Optional
17+
from pydantic import BaseModel
18+
from sqlmodel import SQLModel, Field, Column
19+
from sqlmodel.sql.sqltypes import PydanticJSONB
20+
21+
class Address(BaseModel):
22+
street: str
23+
city: str
24+
25+
class User(SQLModel, table=True):
26+
id: Optional[int] = Field(default=None, primary_key=True)
27+
name: str
28+
address: Address = Field(sa_column=Column(PydanticJSONB(Address)))
29+
```
30+
31+
This will store the `address` field as a `JSONB` column in PostgreSQL and automatically serialize/deserialize to and from the `Address` Pydantic model.
32+
33+
If you're using a list or dict of models, `PydanticJSONB` supports that too:
34+
35+
```python
36+
Field(sa_column=Column(PydanticJSONB(List[SomeModel])))
37+
Field(sa_column=Column(PydanticJSONB(Dict[str, SomeModel])))
38+
```
39+
40+
## Requirements
41+
42+
* PostgreSQL (for full `JSONB` support).
43+
* Pydantic v2.
44+
* SQLAlchemy 2.x.
45+
46+
## Limitations
47+
48+
### Nested Model Updates
49+
50+
Currently, updating attributes inside a nested Pydantic model doesn't automatically trigger a database update. This is similar to how plain dictionaries work in SQLAlchemy. For example:
51+
52+
```python
53+
# This won't trigger a database update
54+
row = select(...) # some MyTable row
55+
row.data.x = 1
56+
db.add(row) # no effect, change isn't detected
57+
```
58+
59+
To update nested model attributes, you need to reassign the entire model:
60+
61+
```python
62+
# Workaround: Create a new instance and reassign
63+
updated = ExtraData(**row.data.model_dump())
64+
updated.x = 1
65+
row.data = updated
66+
db.add(row)
67+
```
68+
69+
This limitation will be addressed in a future update using `MutableDict` to enable change tracking for nested fields. The `MutableDict` implementation will emit change events when the contents of the dictionary are altered, including when values are added or removed.
70+
71+
## Notes
72+
73+
* Falls back to `JSON` if `JSONB` is not available.
74+
* Only tested with PostgreSQL at the moment.

docs/databases.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ There are many databases of many types.
6868

6969
A database could be a single file called `heroes.db`, managed with code in a very efficient way. An example would be SQLite, more about that in a bit.
7070

71-
![database as a single file](img/databases/single-file.svg)
71+
![database as a single file](img/databases/single-file.drawio.svg)
7272

7373
### A server database
7474

@@ -80,11 +80,11 @@ In this case, your code would talk to this server application instead of reading
8080

8181
The database could be located in a different server/machine:
8282

83-
![database in an external server](img/databases/external-server.svg)
83+
![database in an external server](img/databases/external-server.drawio.svg)
8484

8585
Or the database could be located in the same server/machine:
8686

87-
![database in the same server](img/databases/same-server.svg)
87+
![database in the same server](img/databases/same-server.drawio.svg)
8888

8989
The most important aspect of these types of databases is that **your code doesn't read or modify** the files containing the data directly.
9090

@@ -98,7 +98,7 @@ In some cases, the database could even be a group of server applications running
9898

9999
In this case, your code would talk to one or more of these server applications running on different machines.
100100

101-
![distributed database in multiple servers](img/databases/multiple-servers.svg)
101+
![distributed database in multiple servers](img/databases/multiple-servers.drawio.svg)
102102

103103
Most of the databases that work as server applications also support multiple servers in one way or another.
104104

@@ -257,7 +257,7 @@ For example, the table for the teams has the ID `1` for the team `Preventers` an
257257

258258
As these **primary key** IDs can uniquely identify each row on the table for teams, we can now go to the table for heroes and refer to those IDs in the table for teams.
259259

260-
![table relationships](img/databases/relationships.svg)
260+
![table relationships](img/databases/relationships.drawio.svg)
261261

262262
So, in the table for heroes, we use the `team_id` column to define a relationship to the *foreign* table for teams. Each value in the `team_id` column on the table with heroes will be the same value as the `id` column of one row in the table with teams.
263263

docs/db-to-code.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ For example this **Relation** or table:
279279

280280
* **Mapper**: this comes from Math, when there's something that can convert from some set of things to another, that's called a "**mapping function**". That's where the **Mapper** comes from.
281281

282-
![Squares to Triangles Mapper](img/db-to-code/mapper.svg)
282+
![Squares to Triangles Mapper](img/db-to-code/mapper.drawio.svg)
283283

284284
We could also write a **mapping function** in Python that converts from the *set of lowercase letters* to the *set of uppercase letters*, like this:
285285

docs/img/databases/external-server.drawio

Lines changed: 0 additions & 93 deletions
This file was deleted.

docs/img/databases/external-server.drawio.svg

Lines changed: 778 additions & 0 deletions
Loading

docs/img/databases/external-server.svg

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)