or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

field-conversion.mdindex.mdrelationship-fields.mdschemas.md

relationship-fields.mddocs/

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

```