Skip to content

Commit 30fc336

Browse files
committed
add usage example
1 parent 3586ff4 commit 30fc336

File tree

2 files changed

+125
-7
lines changed

2 files changed

+125
-7
lines changed

README.md

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,76 @@ SQLAlchemy-Nested-Mutable
55
pip install sqlalchemy-nested-mutable
66
```
77

8-
An advanced SQLAlchemy column type factory that helps map complex Python types (e.g. List, Dict, Pydantic Model and their hybrids) to database types (e.g. ARRAY, JSONB),
9-
And keep track of mutations in deeply nested data structures so that SQLAlchemy can emit proper UPDATE statements.
8+
An advanced SQLAlchemy column type factory that helps map complex Python types (e.g. `List`, `Dict`, *Pydantic Model* and their hybrids) to database types (e.g. `ARRAY`, `JSONB`),
9+
And keep track of mutations in deeply nested data structures so that SQLAlchemy can emit proper *UPDATE* statements.
1010

11-
SQLAlchemy-Nested-Mutable is highly inspired by SQLAlchemy-JSON <sup>[[0]](https://github.com/edelooff/sqlalchemy-json)</sup><sup>[[1]](https://variable-scope.com/posts/mutation-tracking-in-nested-json-structures-using-sqlalchemy)</sup>. However, it does not limit the mapped Python types to dict-like objects.
11+
SQLAlchemy-Nested-Mutable is highly inspired by SQLAlchemy-JSON<sup>[[0]](https://github.com/edelooff/sqlalchemy-json)</sup><sup>[[1]](https://variable-scope.com/posts/mutation-tracking-in-nested-json-structures-using-sqlalchemy)</sup>.
12+
However, it does not limit the mapped Python type to be `Dict`.
1213

13-
Documentation is not ready yet. Please refer to these test files for usage:
14+
Simple Example:
1415

15-
* test_mutable_list.py
16-
* test_mutable_dict.py
17-
* test_mutable_pydantic_type.py
16+
> NOTE the example below is first updated in `examples/user-addresses.py` and then updated here.
17+
18+
```python
19+
from typing import Optional, List
20+
21+
import pydantic
22+
import sqlalchemy as sa
23+
from sqlalchemy.orm import Session, DeclarativeBase, Mapped, mapped_column
24+
from sqlalchemy_nested_mutable import MutablePydanticBaseModel
25+
26+
27+
class Base(DeclarativeBase):
28+
pass
29+
30+
31+
class Addresses(MutablePydanticBaseModel):
32+
"""A container for storing various addresses of users.
33+
34+
NOTE: for working with pydantic model, use a subclass of `MutablePydanticBaseModel` for column mapping.
35+
However, the nested models (e.g. `AddressItem` below) should be direct subclasses of `pydantic.BaseModel`.
36+
"""
37+
38+
class AddressItem(pydantic.BaseModel):
39+
street: str
40+
city: str
41+
area: Optional[str]
42+
43+
preferred: AddressItem
44+
work: Optional[AddressItem]
45+
home: Optional[AddressItem]
46+
others: List[AddressItem] = []
47+
48+
49+
class User(Base):
50+
__tablename__ = "user_account"
51+
52+
id: Mapped[int] = mapped_column(primary_key=True)
53+
name: Mapped[str] = mapped_column(sa.String(30))
54+
addresses: Mapped[Addresses] = mapped_column(Addresses.as_mutable(), nullable=True)
55+
56+
57+
engine = sa.create_engine("sqlite://")
58+
Base.metadata.create_all(engine)
59+
60+
with Session(engine) as s:
61+
s.add(u := User(name="foo", addresses={"preferred": {"street": "bar", "city": "baz"}}))
62+
assert isinstance(u.addresses, MutablePydanticBaseModel)
63+
s.commit()
64+
65+
u.addresses.preferred.street = "bar2"
66+
s.commit()
67+
assert u.addresses.preferred.street == "bar2"
68+
69+
u.addresses.others.append(Addresses.AddressItem.parse_obj({"street": "bar3", "city": "baz3"}))
70+
s.commit()
71+
assert isinstance(u.addresses.others[0], Addresses.AddressItem)
72+
73+
print(u.addresses.dict())
74+
```
75+
76+
For more usage, please refer to the following test files:
77+
78+
* tests/test_mutable_list.py
79+
* tests/test_mutable_dict.py
80+
* tests/test_mutable_pydantic_type.py

examples/user-addresses.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from typing import Optional, List
2+
3+
import pydantic
4+
import sqlalchemy as sa
5+
from sqlalchemy.orm import Session, DeclarativeBase, Mapped, mapped_column
6+
from sqlalchemy_nested_mutable import MutablePydanticBaseModel
7+
8+
9+
class Base(DeclarativeBase):
10+
pass
11+
12+
13+
class Addresses(MutablePydanticBaseModel):
14+
"""A container for storing various addresses of users.
15+
16+
NOTE: for working with pydantic model, use a subclass of `MutablePydanticBaseModel` for column mapping.
17+
However, the nested models (e.g. `AddressItem` below) should be direct subclasses of `pydantic.BaseModel`.
18+
"""
19+
20+
class AddressItem(pydantic.BaseModel):
21+
street: str
22+
city: str
23+
area: Optional[str]
24+
25+
preferred: AddressItem
26+
work: Optional[AddressItem]
27+
home: Optional[AddressItem]
28+
others: List[AddressItem] = []
29+
30+
31+
class User(Base):
32+
__tablename__ = "user_account"
33+
34+
id: Mapped[int] = mapped_column(primary_key=True)
35+
name: Mapped[str] = mapped_column(sa.String(30))
36+
addresses: Mapped[Addresses] = mapped_column(Addresses.as_mutable(), nullable=True)
37+
38+
39+
engine = sa.create_engine("sqlite://")
40+
Base.metadata.create_all(engine)
41+
42+
with Session(engine) as s:
43+
s.add(u := User(name="foo", addresses={"preferred": {"street": "bar", "city": "baz"}}))
44+
assert isinstance(u.addresses, MutablePydanticBaseModel)
45+
s.commit()
46+
47+
u.addresses.preferred.street = "bar2"
48+
s.commit()
49+
assert u.addresses.preferred.street == "bar2"
50+
51+
u.addresses.others.append(Addresses.AddressItem.parse_obj({"street": "bar3", "city": "baz3"}))
52+
s.commit()
53+
assert isinstance(u.addresses.others[0], Addresses.AddressItem)
54+
55+
print(u.addresses.dict())

0 commit comments

Comments
 (0)