Skip to content

Commit 854314c

Browse files
committed
docs(advanced/sa_column): add clean outputs; show encrypted ciphertext with sa_column; tidy examples and messages
1 parent 00b594f commit 854314c

File tree

3 files changed

+83
-14
lines changed

3 files changed

+83
-14
lines changed

docs/advanced/sa-column.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,24 @@ After running, you should see "Database and tables created." and a `database_enc
6161
If you change the encryption key between runs, delete `database_encrypted.db` first so existing ciphertext doesn’t fail to decrypt with the new key.
6262
///
6363

64+
### Output
65+
66+
```
67+
Adding Hero 1: Ted Lasso
68+
Adding Hero 2: Roy Kent
69+
Adding Hero 3: Keeley Jones
70+
Inserted 3 heroes.
71+
72+
Selecting by name: Ted Lasso
73+
Hero 1: id=1 name='Ted Lasso' secret_name='Coach' age=None
74+
Hero 1 secret_name (decrypted in Python): Coach
75+
Hero 1 secret_name (stored in DB, encrypted): omSF3WBuflYmqx2+Dz6PgQ==
76+
77+
Selecting by name: Roy Kent
78+
Hero 2: id=2 name='Roy Kent' secret_name='Roy' age=None
79+
Hero 2 secret_name (decrypted in Python): Roy
80+
```
81+
6482
## Use case: enforcing uniqueness
6583

6684
- Single‑column unique: You can express this using `Field(unique=True)` in SQLModel or directly on the SQLAlchemy `Column(...)` when using `sa_column` for full control (e.g., to set a specific SQL type or name).
@@ -91,6 +109,24 @@ python app.py
91109

92110
After running, you should see the selected rows printed, with a database created at `database_unique.db`. Attempting to insert a duplicate `email` (single‑column unique) or a duplicate pair of `(name, secret_name)` (composite unique) would raise an integrity error.
93111

112+
### Output
113+
114+
```
115+
Adding Hero 1: Ted Lasso ([email protected])
116+
Adding Hero 2: Roy Kent ([email protected])
117+
Adding Hero 3: Keeley Jones ([email protected])
118+
Inserted 3 heroes.
119+
120+
Attempting to insert a duplicate (name, secret_name) ...
121+
Composite unique constraint enforced: UNIQUE constraint failed: hero.name, hero.secret_name
122+
123+
Selecting by email (unique column):
124+
Hero 1: name='Ted Lasso' id=1 age=None secret_name='Coach' email='[email protected]'
125+
126+
Selecting by composite key (name, secret_name):
127+
Hero 2: name='Roy Kent' id=2 age=None secret_name='Roy' email='[email protected]'
128+
```
129+
94130
## Important considerations
95131

96132
- **Prefer** built‑in `Field()` parameters (like `unique=True`, `index=True`, `default=...`) when they are sufficient.

docs_src/advanced/sa_column/tutorial001.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,13 @@ class Hero(SQLModel, table=True):
3030

3131
sqlite_file_name = "database_encrypted.db"
3232
sqlite_url = f"sqlite:///{sqlite_file_name}"
33-
engine = create_engine(sqlite_url, echo=True)
33+
engine = create_engine(sqlite_url)
3434

3535

3636
def create_db_and_tables() -> None:
37+
# Reset DB for demo so decryption key changes don't break runs
38+
if os.path.exists(sqlite_file_name):
39+
os.remove(sqlite_file_name)
3740
SQLModel.metadata.create_all(engine)
3841

3942

@@ -43,19 +46,32 @@ def create_heroes() -> None:
4346
hero_3 = Hero(name="Keeley Jones", secret_name="Keeley", age=29)
4447

4548
with Session(engine) as session:
49+
print("Adding Hero 1: Ted Lasso")
50+
print("Adding Hero 2: Roy Kent")
51+
print("Adding Hero 3: Keeley Jones")
4652
session.add(hero_1)
4753
session.add(hero_2)
4854
session.add(hero_3)
4955
session.commit()
56+
print("Inserted 3 heroes.\n")
5057

5158

5259
def select_heroes() -> None:
5360
with Session(engine) as session:
61+
print("Selecting by name: Ted Lasso")
5462
statement = select(Hero).where(Hero.name == "Ted Lasso")
5563
hero_1 = session.exec(statement).one()
5664
print("Hero 1:", hero_1)
5765
print("Hero 1 secret_name (decrypted in Python):", hero_1.secret_name)
58-
66+
# Read the raw encrypted value directly from the DB (bypassing type decryption)
67+
with engine.connect() as conn:
68+
raw_encrypted = conn.exec_driver_sql(
69+
"SELECT secret_name FROM hero WHERE name = ?",
70+
("Ted Lasso",),
71+
).scalar_one()
72+
print("Hero 1 secret_name (stored in DB, encrypted):", raw_encrypted)
73+
74+
print("\nSelecting by name: Roy Kent")
5975
statement = select(Hero).where(Hero.name == "Roy Kent")
6076
hero_2 = session.exec(statement).one()
6177
print("Hero 2:", hero_2)

docs_src/advanced/sa_column/tutorial002.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,69 @@
1+
import os
12
from typing import Optional
23

34
from sqlalchemy import Column, String, UniqueConstraint
5+
from sqlalchemy.exc import IntegrityError
46
from sqlmodel import Field, Session, SQLModel, create_engine, select
57

68

79
class Hero(SQLModel, table=True):
810
id: Optional[int] = Field(default=None, primary_key=True)
9-
# Single-column unique using sa_column for full control (e.g., explicit SQL type and nullability)
11+
# Single-column unique via sa_column
1012
email: str = Field(sa_column=Column(String(255), unique=True, nullable=False))
1113
name: str
1214
secret_name: str
1315
age: Optional[int] = None
1416

15-
# Composite (multi-column) unique constraint using the idiomatic SQLAlchemy approach
17+
# Composite unique constraint
1618
__table_args__ = (
1719
UniqueConstraint("name", "secret_name", name="uq_hero_name_secret"),
1820
)
1921

2022

2123
sqlite_file_name = "database_unique.db"
2224
sqlite_url = f"sqlite:///{sqlite_file_name}"
23-
engine = create_engine(sqlite_url, echo=True)
25+
engine = create_engine(sqlite_url)
2426

2527

2628
def create_db_and_tables() -> None:
29+
# Reset DB for demo
30+
if os.path.exists(sqlite_file_name):
31+
os.remove(sqlite_file_name)
2732
SQLModel.metadata.create_all(engine)
2833

2934

3035
def create_heroes() -> None:
31-
hero_1 = Hero(email="[email protected]", name="Ted Lasso", secret_name="Coach")
32-
hero_2 = Hero(email="[email protected]", name="Roy Kent", secret_name="Roy")
33-
hero_3 = Hero(
34-
email="[email protected]", name="Keeley Jones", secret_name="Keeley"
35-
)
36-
3736
with Session(engine) as session:
38-
session.add(hero_1)
39-
session.add(hero_2)
40-
session.add(hero_3)
37+
hero_1 = Hero(email="[email protected]", name="Ted Lasso", secret_name="Coach")
38+
hero_2 = Hero(email="[email protected]", name="Roy Kent", secret_name="Roy")
39+
hero_3 = Hero(email="[email protected]", name="Keeley Jones", secret_name="Keeley")
40+
41+
print("Adding Hero 1: Ted Lasso ([email protected])")
42+
print("Adding Hero 2: Roy Kent ([email protected])")
43+
print("Adding Hero 3: Keeley Jones ([email protected])")
44+
session.add_all([hero_1, hero_2, hero_3])
4145
session.commit()
46+
print("Inserted 3 heroes.\n")
47+
48+
# Duplicate (name, secret_name) should fail (different email)
49+
hero_4 = Hero(email="[email protected]", name="Roy Kent", secret_name="Roy")
50+
try:
51+
print("Attempting to insert a duplicate (name, secret_name) ...")
52+
session.add(hero_4)
53+
session.commit()
54+
except IntegrityError as e:
55+
session.rollback()
56+
print("Composite unique constraint enforced:", str(e.orig))
4257

4358

4459
def select_heroes() -> None:
4560
with Session(engine) as session:
61+
print("\nSelecting by email (unique column):")
4662
statement = select(Hero).where(Hero.email == "[email protected]")
4763
hero_1 = session.exec(statement).one()
4864
print("Hero 1:", hero_1)
4965

66+
print("\nSelecting by composite key (name, secret_name):")
5067
statement = select(Hero).where(
5168
(Hero.name == "Roy Kent") & (Hero.secret_name == "Roy")
5269
)

0 commit comments

Comments
 (0)