0
# Relationship Fields
1
2
Specialized marshmallow fields for handling SQLAlchemy relationships with automatic serialization and deserialization of related model instances, supporting both single relationships and collections.
3
4
## Capabilities
5
6
### Related Field
7
8
Field for representing SQLAlchemy relationships, handling serialization of related model instances and deserialization back to instances with session-aware loading.
9
10
```python { .api }
11
class Related(fields.Field):
12
"""Field for SQLAlchemy relationships.
13
14
Handles serialization/deserialization of related model instances.
15
Must be attached to a Schema with a SQLAlchemy model.
16
"""
17
18
def __init__(
19
self,
20
columns: list[str] | str | None = None,
21
column: str | None = None, # Deprecated, use columns
22
**kwargs,
23
):
24
"""Initialize Related field.
25
26
Parameters:
27
- columns: Column names on related model for serialization.
28
If None, uses primary key(s) of related model.
29
- column: (Deprecated) Single column name, use columns instead.
30
- **kwargs: Additional field arguments
31
"""
32
33
@property
34
def model(self) -> type[DeclarativeMeta] | None:
35
"""The SQLAlchemy model of the parent schema."""
36
37
@property
38
def related_model(self) -> type[DeclarativeMeta]:
39
"""The SQLAlchemy model of the related object."""
40
41
@property
42
def related_keys(self) -> list[MapperProperty]:
43
"""Properties used for serialization/deserialization."""
44
45
@property
46
def session(self) -> Session:
47
"""SQLAlchemy session from parent schema."""
48
49
@property
50
def transient(self) -> bool:
51
"""Whether parent schema loads transient instances."""
52
```
53
54
### RelatedList Field
55
56
List field that extends marshmallow's List field with special handling for collections of related objects in SQLAlchemy relationships.
57
58
```python { .api }
59
class RelatedList(fields.List):
60
"""List field for one-to-many and many-to-many relationships.
61
62
Extends marshmallow List field with proper handling for
63
SQLAlchemy relationship collections.
64
"""
65
66
def get_value(self, obj, attr, accessor=None):
67
"""Get value with special handling for relationship collections."""
68
```
69
70
### Nested Field
71
72
Nested field that automatically inherits session and transient state from the parent schema for proper relationship handling.
73
74
```python { .api }
75
class Nested(fields.Nested):
76
"""Nested field that inherits session from parent schema.
77
78
Ensures nested schemas have access to the same SQLAlchemy
79
session for proper relationship loading.
80
"""
81
82
def _deserialize(self, *args, **kwargs):
83
"""Deserialize with session inheritance."""
84
```
85
86
### Utility Functions
87
88
```python { .api }
89
def get_primary_keys(model: type[DeclarativeMeta]) -> list[MapperProperty]:
90
"""Get primary key properties for a SQLAlchemy model.
91
92
Parameters:
93
- model: SQLAlchemy model class
94
95
Returns:
96
List of primary key mapper properties
97
"""
98
99
def ensure_list(value: Any) -> list:
100
"""Ensure value is a list.
101
102
Converts iterables to lists, wraps non-iterables in lists.
103
"""
104
```
105
106
## Usage Examples
107
108
### Basic Related Field
109
110
```python
111
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema, Related
112
from mymodels import Author, Book
113
114
class BookSchema(SQLAlchemyAutoSchema):
115
class Meta:
116
model = Book
117
load_instance = True
118
include_relationships = True
119
120
# Related field automatically created for relationships
121
# when include_relationships=True
122
author = Related() # Uses primary key of Author model
123
124
class AuthorSchema(SQLAlchemyAutoSchema):
125
class Meta:
126
model = Author
127
load_instance = True
128
129
# Manual Related field with custom columns
130
books = Related(columns=["id", "title"])
131
```
132
133
### Related Field Serialization
134
135
```python
136
# Serialization - converts model instances to dictionaries
137
book = session.get(Book, 1)
138
schema = BookSchema()
139
serialized = schema.dump(book)
140
# Result: {
141
# "id": 1,
142
# "title": "Python Guide",
143
# "author": {"id": 5, "name": "John Doe"} # Related field serialized
144
# }
145
146
# For multiple columns, returns dict
147
author_with_email = Related(columns=["id", "email"])
148
# Serializes to: {"id": 5, "email": "john@example.com"}
149
150
# For single column (primary key), returns scalar value
151
author_id_only = Related(columns=["id"])
152
# Serializes to: 5
153
```
154
155
### Related Field Deserialization
156
157
```python
158
# Deserialization - converts dictionaries back to model instances
159
book_data = {
160
"title": "New Book",
161
"author": {"id": 5} # Reference to existing author
162
}
163
164
schema = BookSchema()
165
book_instance = schema.load(book_data, session=session)
166
# Loads existing Author with id=5 from database
167
# Creates new Book instance with that author
168
169
# Scalar value deserialization (for single primary key)
170
book_data_scalar = {
171
"title": "Another Book",
172
"author": 5 # Direct primary key value
173
}
174
book_instance = schema.load(book_data_scalar, session=session)
175
```
176
177
### Transient Mode
178
179
```python
180
# Load related objects as new instances (not from database)
181
schema = BookSchema()
182
book_data = {
183
"title": "New Book",
184
"author": {"name": "Jane Doe", "email": "jane@example.com"}
185
}
186
187
# Transient mode - creates new Author instance
188
book_instance = schema.load(book_data, session=session, transient=True)
189
# book_instance.author is a new Author instance, not loaded from DB
190
```
191
192
### RelatedList for Collections
193
194
```python
195
from marshmallow_sqlalchemy import RelatedList
196
197
class AuthorSchema(SQLAlchemyAutoSchema):
198
class Meta:
199
model = Author
200
load_instance = True
201
202
# Automatically created when include_relationships=True
203
books = RelatedList(Related(columns=["id", "title"]))
204
205
# Serialization of collections
206
author = session.get(Author, 1)
207
schema = AuthorSchema()
208
serialized = schema.dump(author)
209
# Result: {
210
# "id": 1,
211
# "name": "John Doe",
212
# "books": [
213
# {"id": 1, "title": "First Book"},
214
# {"id": 2, "title": "Second Book"}
215
# ]
216
# }
217
218
# Deserialization of collections
219
author_data = {
220
"name": "New Author",
221
"books": [
222
{"id": 1}, # Load existing book
223
{"title": "Brand New Book"} # Create new book
224
]
225
}
226
author_instance = schema.load(author_data, session=session)
227
```
228
229
### Nested Schemas
230
231
```python
232
from marshmallow_sqlalchemy import Nested
233
234
class BookDetailSchema(SQLAlchemyAutoSchema):
235
class Meta:
236
model = Book
237
load_instance = True
238
239
class AuthorDetailSchema(SQLAlchemyAutoSchema):
240
class Meta:
241
model = Author
242
load_instance = True
243
244
# Use nested schema for detailed book information
245
books = Nested(BookDetailSchema, many=True)
246
247
# Session automatically inherited by nested schema
248
schema = AuthorDetailSchema()
249
author_data = {
250
"name": "Author Name",
251
"books": [
252
{"title": "Book Title", "isbn": "978-1234567890"}
253
]
254
}
255
author_instance = schema.load(author_data, session=session)
256
# Nested BookDetailSchema automatically gets the session
257
```
258
259
### Custom Column Specifications
260
261
```python
262
class BookSchema(SQLAlchemyAutoSchema):
263
class Meta:
264
model = Book
265
load_instance = True
266
267
# Use specific columns for serialization
268
author = Related(columns=["id", "name", "email"])
269
270
# Use single column (returns scalar)
271
category_id = Related(columns=["id"])
272
273
# Use all columns (default behavior)
274
publisher = Related() # Uses primary key columns
275
276
# Multiple primary key handling
277
class CompositeKeyModel(Base):
278
__tablename__ = "composite"
279
key1 = sa.Column(sa.Integer, primary_key=True)
280
key2 = sa.Column(sa.String, primary_key=True)
281
282
class ParentSchema(SQLAlchemyAutoSchema):
283
class Meta:
284
model = Parent
285
load_instance = True
286
287
# Automatically handles multiple primary keys
288
composite_ref = Related()
289
# Serializes to: {"key1": 1, "key2": "abc"}
290
# Deserializes from: {"key1": 1, "key2": "abc"}
291
```
292
293
### Error Handling
294
295
```python
296
# Related field validation errors
297
try:
298
schema = BookSchema()
299
result = schema.load({
300
"title": "Test Book",
301
"author": "invalid_value" # Should be dict or primary key
302
}, session=session)
303
except ValidationError as e:
304
# e.messages contains field-specific errors
305
print(e.messages["author"])
306
# ["Could not deserialize related value 'invalid_value'; expected a dictionary with keys ['id']"]
307
308
# Missing related object
309
try:
310
result = schema.load({
311
"title": "Test Book",
312
"author": {"id": 999} # Non-existent author
313
}, session=session)
314
# Creates new Author instance with id=999 if not found in DB
315
except ValidationError as e:
316
# Handle validation errors
317
pass
318
```
319
320
### Association Proxies
321
322
```python
323
# Related fields work with SQLAlchemy association proxies
324
class User(Base):
325
__tablename__ = "users"
326
id = sa.Column(sa.Integer, primary_key=True)
327
name = sa.Column(sa.String)
328
329
# Association proxy
330
role_names = association_proxy("user_roles", "role_name")
331
332
class UserSchema(SQLAlchemyAutoSchema):
333
class Meta:
334
model = User
335
load_instance = True
336
include_relationships = True
337
338
# Related field handles association proxy relationships
339
role_names = Related() # Automatically detected and handled
340
```