0
# Field Conversion
1
2
Comprehensive system for converting SQLAlchemy columns, properties, and models into appropriate marshmallow fields, with support for all standard SQLAlchemy types and database-specific extensions.
3
4
**Note:** Type annotations using `Literal` require `from typing import Literal` (Python 3.8+) or `from typing_extensions import Literal` (earlier versions).
5
6
## Capabilities
7
8
### ModelConverter Class
9
10
The core conversion class that translates SQLAlchemy constructs to marshmallow fields with comprehensive type mapping and customization options.
11
12
```python { .api }
13
class ModelConverter:
14
"""Converts SQLAlchemy models into marshmallow fields.
15
16
Provides methods for converting entire models, individual properties,
17
and columns to appropriate marshmallow field types.
18
"""
19
20
def __init__(self, schema_cls: type[ma.Schema] | None = None): ...
21
22
def fields_for_model(
23
self,
24
model: type[DeclarativeMeta],
25
*,
26
include_fk: bool = False,
27
include_relationships: bool = False,
28
fields: Iterable[str] | None = None,
29
exclude: Iterable[str] | None = None,
30
base_fields: dict | None = None,
31
dict_cls: type[dict] = dict,
32
) -> dict[str, fields.Field]:
33
"""Generate dict of field_name: Field pairs for the given model.
34
35
Parameters:
36
- model: SQLAlchemy model class
37
- include_fk: Whether to include foreign key fields
38
- include_relationships: Whether to include relationship fields
39
- fields: Only include these field names (whitelist)
40
- exclude: Exclude these field names (blacklist)
41
- base_fields: Existing fields to merge with
42
- dict_cls: Dict class to use for result
43
44
Returns:
45
Dictionary mapping field names to marshmallow Field instances
46
"""
47
48
def fields_for_table(
49
self,
50
table: sa.Table,
51
*,
52
include_fk: bool = False,
53
fields: Iterable[str] | None = None,
54
exclude: Iterable[str] | None = None,
55
base_fields: dict | None = None,
56
dict_cls: type[dict] = dict,
57
) -> dict[str, fields.Field]:
58
"""Generate dict of field_name: Field pairs for the given table.
59
60
Parameters:
61
- table: SQLAlchemy Table instance
62
- include_fk: Whether to include foreign key fields
63
- fields: Only include these field names (whitelist)
64
- exclude: Exclude these field names (blacklist)
65
- base_fields: Existing fields to merge with
66
- dict_cls: Dict class to use for result
67
68
Returns:
69
Dictionary mapping field names to marshmallow Field instances
70
"""
71
72
@overload
73
def property2field(
74
self,
75
prop: MapperProperty,
76
*,
77
instance: Literal[True] = ...,
78
field_class: type[fields.Field] | None = ...,
79
**kwargs,
80
) -> fields.Field: ...
81
82
@overload
83
def property2field(
84
self,
85
prop: MapperProperty,
86
*,
87
instance: Literal[False] = ...,
88
field_class: type[fields.Field] | None = ...,
89
**kwargs,
90
) -> type[fields.Field]: ...
91
92
def property2field(
93
self,
94
prop: MapperProperty,
95
*,
96
instance: bool = True,
97
field_class: type[fields.Field] | None = None,
98
**kwargs,
99
) -> fields.Field | type[fields.Field]:
100
"""Convert SQLAlchemy Property to marshmallow field.
101
102
Parameters:
103
- prop: SQLAlchemy Property (column property or relationship)
104
- instance: If True, return Field instance; if False, return Field class
105
- field_class: Override field class to use
106
- **kwargs: Additional field constructor arguments
107
108
Returns:
109
Field instance or class depending on instance parameter
110
"""
111
112
@overload
113
def column2field(
114
self,
115
column: sa.Column,
116
*,
117
instance: Literal[True] = ...,
118
**kwargs
119
) -> fields.Field: ...
120
121
@overload
122
def column2field(
123
self,
124
column: sa.Column,
125
*,
126
instance: Literal[False] = ...,
127
**kwargs
128
) -> type[fields.Field]: ...
129
130
def column2field(
131
self,
132
column: sa.Column,
133
*,
134
instance: bool = True,
135
**kwargs
136
) -> fields.Field | type[fields.Field]:
137
"""Convert SQLAlchemy Column to marshmallow field.
138
139
Parameters:
140
- column: SQLAlchemy Column instance
141
- instance: If True, return Field instance; if False, return Field class
142
- **kwargs: Additional field constructor arguments
143
144
Returns:
145
Field instance or class depending on instance parameter
146
"""
147
148
@overload
149
def field_for(
150
self,
151
model: type[DeclarativeMeta],
152
property_name: str,
153
*,
154
instance: Literal[True] = ...,
155
field_class: type[fields.Field] | None = ...,
156
**kwargs,
157
) -> fields.Field: ...
158
159
@overload
160
def field_for(
161
self,
162
model: type[DeclarativeMeta],
163
property_name: str,
164
*,
165
instance: Literal[False] = ...,
166
field_class: type[fields.Field] | None = None,
167
**kwargs,
168
) -> type[fields.Field]: ...
169
170
def field_for(
171
self,
172
model: type[DeclarativeMeta],
173
property_name: str,
174
*,
175
instance: bool = True,
176
field_class: type[fields.Field] | None = None,
177
**kwargs,
178
) -> fields.Field | type[fields.Field]:
179
"""Convert model property to marshmallow field by name.
180
181
Parameters:
182
- model: SQLAlchemy model class
183
- property_name: Name of property to convert
184
- instance: If True, return Field instance; if False, return Field class
185
- field_class: Override field class to use
186
- **kwargs: Additional field constructor arguments
187
188
Returns:
189
Field instance or class depending on instance parameter
190
"""
191
```
192
193
### Conversion Functions
194
195
Module-level convenience functions that use a default ModelConverter instance for quick field conversion.
196
197
```python { .api }
198
def fields_for_model(
199
model: type[DeclarativeMeta],
200
*,
201
include_fk: bool = False,
202
include_relationships: bool = False,
203
fields: Iterable[str] | None = None,
204
exclude: Iterable[str] | None = None,
205
base_fields: dict | None = None,
206
dict_cls: type[dict] = dict,
207
) -> dict[str, fields.Field]:
208
"""Generate fields dict from SQLAlchemy model (convenience function).
209
210
Uses default ModelConverter instance.
211
"""
212
213
@overload
214
def property2field(
215
prop: MapperProperty,
216
*,
217
instance: Literal[True] = ...,
218
field_class: type[fields.Field] | None = ...,
219
**kwargs,
220
) -> fields.Field: ...
221
222
@overload
223
def property2field(
224
prop: MapperProperty,
225
*,
226
instance: Literal[False] = ...,
227
field_class: type[fields.Field] | None = ...,
228
**kwargs,
229
) -> type[fields.Field]: ...
230
231
def property2field(
232
prop: MapperProperty,
233
*,
234
instance: bool = True,
235
field_class: type[fields.Field] | None = None,
236
**kwargs,
237
) -> fields.Field | type[fields.Field]:
238
"""Convert SQLAlchemy property to field (convenience function).
239
240
Uses default ModelConverter instance.
241
"""
242
243
@overload
244
def column2field(
245
column: sa.Column,
246
*,
247
instance: Literal[True] = ...,
248
**kwargs
249
) -> fields.Field: ...
250
251
@overload
252
def column2field(
253
column: sa.Column,
254
*,
255
instance: Literal[False] = ...,
256
**kwargs
257
) -> type[fields.Field]: ...
258
259
def column2field(
260
column: sa.Column,
261
*,
262
instance: bool = True,
263
**kwargs
264
) -> fields.Field | type[fields.Field]:
265
"""Convert SQLAlchemy column to field (convenience function).
266
267
Uses default ModelConverter instance.
268
"""
269
270
@overload
271
def field_for(
272
model: type[DeclarativeMeta],
273
property_name: str,
274
*,
275
instance: Literal[True] = ...,
276
field_class: type[fields.Field] | None = ...,
277
**kwargs,
278
) -> fields.Field: ...
279
280
@overload
281
def field_for(
282
model: type[DeclarativeMeta],
283
property_name: str,
284
*,
285
instance: Literal[False] = ...,
286
field_class: type[fields.Field] | None = None,
287
**kwargs,
288
) -> type[fields.Field]: ...
289
290
def field_for(
291
model: type[DeclarativeMeta],
292
property_name: str,
293
*,
294
instance: bool = True,
295
field_class: type[fields.Field] | None = None,
296
**kwargs,
297
) -> fields.Field | type[fields.Field]:
298
"""Get field for model property by name (convenience function).
299
300
Uses default ModelConverter instance.
301
"""
302
```
303
304
### Usage Examples
305
306
#### Converting Entire Models
307
308
```python
309
from marshmallow_sqlalchemy import fields_for_model, ModelConverter
310
from mymodels import User, Post
311
312
# Using convenience function
313
user_fields = fields_for_model(User)
314
# Returns: {"id": Integer(), "name": String(), "email": String(), ...}
315
316
# Include relationships and foreign keys
317
post_fields = fields_for_model(
318
Post,
319
include_fk=True,
320
include_relationships=True
321
)
322
323
# Exclude specific fields
324
user_fields_filtered = fields_for_model(
325
User,
326
exclude=["password_hash", "last_login"]
327
)
328
329
# Only include specific fields
330
user_fields_minimal = fields_for_model(
331
User,
332
fields=["id", "name", "email"]
333
)
334
```
335
336
#### Converting Individual Properties
337
338
```python
339
from marshmallow_sqlalchemy import field_for, property2field
340
from mymodels import User
341
342
# Get field for specific property
343
name_field = field_for(User, "name")
344
email_field = field_for(User, "email", dump_only=True)
345
346
# Get field class instead of instance
347
NameFieldClass = field_for(User, "name", instance=False)
348
349
# Convert property directly
350
user_mapper = sa.inspect(User)
351
name_prop = user_mapper.attrs["name"]
352
name_field = property2field(name_prop)
353
```
354
355
#### Converting Columns
356
357
```python
358
from marshmallow_sqlalchemy import column2field
359
from mymodels import User
360
361
# Convert table column
362
user_table = User.__table__
363
name_column = user_table.columns["name"]
364
name_field = column2field(name_column)
365
366
# Add custom validation
367
email_field = column2field(
368
user_table.columns["email"],
369
validate=[validate.Email(), validate.Length(max=255)]
370
)
371
```
372
373
#### Custom Converter
374
375
```python
376
from marshmallow_sqlalchemy import ModelConverter
377
from marshmallow import fields
378
379
class CustomConverter(ModelConverter):
380
"""Custom converter with additional type mappings."""
381
382
SQLA_TYPE_MAPPING = {
383
**ModelConverter.SQLA_TYPE_MAPPING,
384
MyCustomType: fields.String, # Map custom type to String field
385
}
386
387
def _get_field_class_for_data_type(self, data_type):
388
# Custom logic for field type selection
389
if isinstance(data_type, MySpecialType):
390
return fields.Raw
391
return super()._get_field_class_for_data_type(data_type)
392
393
# Use custom converter
394
converter = CustomConverter()
395
fields_dict = converter.fields_for_model(MyModel)
396
397
# Or use with schema
398
class MySchema(SQLAlchemyAutoSchema):
399
class Meta:
400
model = MyModel
401
model_converter = CustomConverter
402
```
403
404
### Type Mappings
405
406
ModelConverter includes comprehensive mappings for SQLAlchemy types:
407
408
#### Standard SQLAlchemy Types
409
410
- `sa.String` → `fields.String`
411
- `sa.Integer` → `fields.Integer`
412
- `sa.Float` → `fields.Float`
413
- `sa.Boolean` → `fields.Boolean`
414
- `sa.DateTime` → `fields.DateTime`
415
- `sa.Date` → `fields.Date`
416
- `sa.Time` → `fields.Time`
417
- `sa.Numeric` → `fields.Decimal`
418
- `sa.Text` → `fields.String`
419
- `sa.JSON` → `fields.Raw`
420
- `sa.Enum` → `fields.Enum` (if enum_class) or `fields.Raw`
421
- `sa.PickleType` → `fields.Raw`
422
423
#### PostgreSQL-Specific Types
424
425
- `postgresql.UUID` → `fields.UUID`
426
- `postgresql.JSONB` → `fields.Raw`
427
- `postgresql.ARRAY` → `fields.List`
428
- `postgresql.HSTORE` → `fields.Raw`
429
- `postgresql.INET` → `fields.String`
430
- `postgresql.CIDR` → `fields.String`
431
- `postgresql.MACADDR` → `fields.String`
432
- `postgresql.MONEY` → `fields.Decimal`
433
434
#### MySQL-Specific Types
435
436
- `mysql.BIT` → `fields.Integer`
437
- `mysql.YEAR` → `fields.Integer`
438
- `mysql.SET` → `fields.List`
439
- `mysql.ENUM` → `fields.Field`
440
441
#### MSSQL-Specific Types
442
443
- `mssql.BIT` → `fields.Integer`
444
- `mssql.UNIQUEIDENTIFIER` → `fields.UUID`
445
446
### Field Configuration
447
448
The converter automatically configures fields based on column properties:
449
450
```python
451
# Column with nullable=False becomes required field
452
name = sa.Column(sa.String, nullable=False)
453
# → fields.String(required=True)
454
455
# Column with default value becomes non-required
456
status = sa.Column(sa.String, default="active")
457
# → fields.String(required=False)
458
459
# Column with length constraint gets Length validator
460
username = sa.Column(sa.String(50))
461
# → fields.String(validate=[validate.Length(max=50)])
462
463
# Enum column gets OneOf validator
464
priority = sa.Column(sa.Enum("low", "medium", "high"))
465
# → fields.String(validate=[validate.OneOf(["low", "medium", "high"])])
466
467
# Foreign key columns are excluded by default (unless include_fk=True)
468
user_id = sa.Column(sa.Integer, sa.ForeignKey("users.id"))
469
# → Excluded unless include_fk=True
470
471
# Relationship properties become Related fields
472
posts = relationship("Post", back_populates="author")
473
# → Related() field (unless include_relationships=False)
474
```