0
# ORM System
1
2
Object-Relational Mapping functionality providing a Django-style model approach to Airtable tables. Includes field definitions, type validation, automatic conversions, and model-based operations for Python developers.
3
4
## Capabilities
5
6
### Model Definition
7
8
Base Model class for creating ORM-style table representations with field definitions and metadata configuration.
9
10
```python { .api }
11
class Model:
12
def __init__(self, **field_values):
13
"""
14
Initialize model instance with field values.
15
16
Parameters:
17
- field_values: Keyword arguments mapping field names to values
18
"""
19
20
def save(self) -> object:
21
"""
22
Save model instance to Airtable (create or update).
23
24
Returns:
25
SaveResult object with operation details
26
"""
27
28
def delete(self) -> bool:
29
"""
30
Delete record from Airtable.
31
32
Returns:
33
True if deletion was successful
34
"""
35
36
def to_record(self) -> dict:
37
"""
38
Convert model instance to Airtable record dict.
39
40
Returns:
41
Dict with 'id', 'createdTime', and 'fields' keys
42
"""
43
44
@classmethod
45
def from_record(cls, record: dict) -> 'Model':
46
"""
47
Create model instance from Airtable record.
48
49
Parameters:
50
- record: Record dict from Airtable API
51
52
Returns:
53
Model instance populated with record data
54
"""
55
56
@classmethod
57
def from_records(cls, records: list[dict]) -> list['Model']:
58
"""
59
Create multiple model instances from record list.
60
61
Parameters:
62
- records: List of record dicts
63
64
Returns:
65
List of model instances
66
"""
67
68
@classmethod
69
def all(cls, **options) -> list['Model']:
70
"""
71
Retrieve all records as model instances.
72
73
Parameters:
74
- options: Same as Table.all() options
75
76
Returns:
77
List of model instances
78
"""
79
80
@classmethod
81
def first(cls, **options) -> Optional['Model']:
82
"""
83
Get first matching record as model instance.
84
85
Parameters:
86
- options: Same as Table.first() options
87
88
Returns:
89
Model instance or None
90
"""
91
92
@classmethod
93
def batch_save(cls, instances: list['Model']) -> list[object]:
94
"""
95
Save multiple model instances efficiently.
96
97
Parameters:
98
- instances: List of model instances to save
99
100
Returns:
101
List of SaveResult objects
102
"""
103
104
class SaveResult:
105
"""Result object returned from save operations."""
106
107
@property
108
def created(self) -> bool:
109
"""True if record was created, False if updated."""
110
111
@property
112
def record(self) -> dict:
113
"""The saved record dict."""
114
```
115
116
### Field Types
117
118
Comprehensive field type system with automatic validation, conversion, and type safety for different Airtable field types.
119
120
```python { .api }
121
# Text fields
122
class TextField:
123
def __init__(self, field_name: str):
124
"""Single line text field."""
125
126
class RichTextField:
127
def __init__(self, field_name: str):
128
"""Rich text field with formatting."""
129
130
class EmailField:
131
def __init__(self, field_name: str):
132
"""Email address field with validation."""
133
134
class UrlField:
135
def __init__(self, field_name: str):
136
"""URL field with validation."""
137
138
class PhoneNumberField:
139
def __init__(self, field_name: str):
140
"""Phone number field."""
141
142
# Numeric fields
143
class NumberField:
144
def __init__(self, field_name: str):
145
"""Number field (integer or decimal)."""
146
147
class CurrencyField:
148
def __init__(self, field_name: str):
149
"""Currency field with formatting."""
150
151
class PercentField:
152
def __init__(self, field_name: str):
153
"""Percentage field."""
154
155
class DurationField:
156
def __init__(self, field_name: str):
157
"""Duration field (time intervals)."""
158
159
class RatingField:
160
def __init__(self, field_name: str):
161
"""Rating field (star ratings)."""
162
163
# Boolean and selection fields
164
class CheckboxField:
165
def __init__(self, field_name: str):
166
"""Checkbox field (boolean values)."""
167
168
class SelectField:
169
def __init__(self, field_name: str):
170
"""Single select field (dropdown)."""
171
172
class MultipleSelectField:
173
def __init__(self, field_name: str):
174
"""Multiple select field (multiple choices)."""
175
176
# Date and time fields
177
class DateField:
178
def __init__(self, field_name: str):
179
"""Date field (date only)."""
180
181
class DatetimeField:
182
def __init__(self, field_name: str):
183
"""Datetime field (date and time)."""
184
185
class CreatedTimeField:
186
def __init__(self, field_name: str):
187
"""Auto-populated creation time (read-only)."""
188
189
class LastModifiedTimeField:
190
def __init__(self, field_name: str):
191
"""Auto-populated modification time (read-only)."""
192
193
# File and attachment fields
194
class AttachmentField:
195
def __init__(self, field_name: str):
196
"""File attachment field."""
197
198
# Relationship fields
199
class LinkField:
200
def __init__(self, field_name: str, linked_model: type = None):
201
"""
202
Link to another table field.
203
204
Parameters:
205
- field_name: Name of link field
206
- linked_model: Model class for linked table (optional)
207
"""
208
209
# Computed fields (read-only)
210
class FormulaField:
211
def __init__(self, field_name: str):
212
"""Formula field (computed, read-only)."""
213
214
class LookupField:
215
def __init__(self, field_name: str):
216
"""Lookup field (from linked records, read-only)."""
217
218
class RollupField:
219
def __init__(self, field_name: str):
220
"""Rollup field (aggregates from linked records, read-only)."""
221
222
class CountField:
223
def __init__(self, field_name: str):
224
"""Count field (counts linked records, read-only)."""
225
226
# System fields
227
class CreatedByField:
228
def __init__(self, field_name: str):
229
"""User who created record (read-only)."""
230
231
class LastModifiedByField:
232
def __init__(self, field_name: str):
233
"""User who last modified record (read-only)."""
234
235
class CollaboratorField:
236
def __init__(self, field_name: str):
237
"""Single collaborator field."""
238
239
class MultipleCollaboratorsField:
240
def __init__(self, field_name: str):
241
"""Multiple collaborators field."""
242
243
# Specialized fields
244
class AutoNumberField:
245
def __init__(self, field_name: str):
246
"""Auto-incrementing number field (read-only)."""
247
248
class BarcodeField:
249
def __init__(self, field_name: str):
250
"""Barcode field."""
251
252
class ButtonField:
253
def __init__(self, field_name: str):
254
"""Button field (triggers actions)."""
255
```
256
257
### Usage Examples
258
259
#### Basic Model Definition
260
261
```python
262
from pyairtable.orm import Model, fields
263
264
class Contact(Model):
265
# Required Meta class with table configuration
266
class Meta:
267
base_id = 'app1234567890abcde'
268
table_name = 'Contacts'
269
api_key = 'your_access_token'
270
271
# Field definitions
272
name = fields.TextField('Name')
273
email = fields.EmailField('Email')
274
phone = fields.PhoneNumberField('Phone')
275
company = fields.TextField('Company')
276
active = fields.CheckboxField('Active')
277
rating = fields.RatingField('Rating')
278
notes = fields.RichTextField('Notes')
279
created = fields.CreatedTimeField('Created')
280
281
# Create and save records
282
contact = Contact(
283
name='John Doe',
284
email='john@example.com',
285
company='Acme Corp',
286
active=True,
287
rating=5
288
)
289
290
# Save to Airtable (creates new record)
291
result = contact.save()
292
print(f"Created record: {result.record['id']}")
293
294
# Update and save again
295
contact.phone = '+1-555-0123'
296
contact.notes = 'Updated contact info'
297
result = contact.save() # Updates existing record
298
print(f"Updated: {result.created}") # False for update
299
```
300
301
#### Advanced Model Features
302
303
```python
304
from datetime import date, datetime
305
from pyairtable.orm import Model, fields
306
307
class Employee(Model):
308
class Meta:
309
base_id = 'app1234567890abcde'
310
table_name = 'Employees'
311
api_key = 'your_access_token'
312
timeout = (5, 30) # Connection and read timeouts
313
typecast = True # Enable automatic type conversion
314
315
# Personal info
316
first_name = fields.TextField('First Name')
317
last_name = fields.TextField('Last Name')
318
email = fields.EmailField('Email')
319
phone = fields.PhoneNumberField('Phone')
320
321
# Employment details
322
employee_id = fields.AutoNumberField('Employee ID')
323
department = fields.SelectField('Department')
324
position = fields.TextField('Position')
325
salary = fields.CurrencyField('Salary')
326
start_date = fields.DateField('Start Date')
327
328
# Status and metrics
329
active = fields.CheckboxField('Active')
330
performance_rating = fields.RatingField('Performance')
331
skills = fields.MultipleSelectField('Skills')
332
333
# Relationships
334
manager = fields.LinkField('Manager', linked_model='Employee')
335
projects = fields.LinkField('Projects') # Links to Project model
336
337
# Computed fields (read-only)
338
years_employed = fields.FormulaField('Years Employed')
339
total_projects = fields.CountField('Total Projects')
340
341
# System fields
342
created_time = fields.CreatedTimeField('Created')
343
modified_time = fields.LastModifiedTimeField('Last Modified')
344
created_by = fields.CreatedByField('Created By')
345
346
# Create employee with related data
347
employee = Employee(
348
first_name='Jane',
349
last_name='Smith',
350
email='jane.smith@company.com',
351
department='Engineering',
352
position='Senior Developer',
353
salary=120000,
354
start_date=date(2023, 1, 15),
355
active=True,
356
skills=['Python', 'JavaScript', 'React']
357
)
358
359
result = employee.save()
360
```
361
362
#### Querying and Retrieval
363
364
```python
365
# Get all employees
366
all_employees = Employee.all()
367
368
# Filter employees
369
active_employees = Employee.all(
370
formula="AND({Active} = TRUE(), {Department} = 'Engineering')"
371
)
372
373
# Get first matching employee
374
first_engineer = Employee.first(
375
formula="{Department} = 'Engineering'",
376
sort=[{'field': 'Start Date', 'direction': 'asc'}]
377
)
378
379
# Pagination
380
recent_employees = Employee.all(
381
sort=[{'field': 'Created', 'direction': 'desc'}],
382
max_records=50
383
)
384
385
# Convert existing records to models
386
from pyairtable import Api
387
388
api = Api('your_token')
389
table = api.table('base_id', 'Employees')
390
records = table.all()
391
392
# Convert to model instances
393
employees = Employee.from_records(records)
394
for emp in employees:
395
print(f"{emp.first_name} {emp.last_name} - {emp.department}")
396
```
397
398
#### Batch Operations
399
400
```python
401
# Create multiple employees
402
new_employees = [
403
Employee(
404
first_name='Alice',
405
last_name='Johnson',
406
email='alice@company.com',
407
department='Marketing',
408
active=True
409
),
410
Employee(
411
first_name='Bob',
412
last_name='Wilson',
413
email='bob@company.com',
414
department='Sales',
415
active=True
416
)
417
]
418
419
# Batch save for efficiency
420
results = Employee.batch_save(new_employees)
421
print(f"Created {len(results)} employees")
422
423
# Update multiple employees
424
for emp in Employee.all(formula="{Department} = 'Engineering'"):
425
emp.performance_rating = 5
426
427
# Save all changes
428
updated_employees = [emp for emp in Employee.all() if emp.performance_rating == 5]
429
Employee.batch_save(updated_employees)
430
```
431
432
#### Working with Relationships
433
434
```python
435
class Project(Model):
436
class Meta:
437
base_id = 'app1234567890abcde'
438
table_name = 'Projects'
439
api_key = 'your_access_token'
440
441
name = fields.TextField('Name')
442
description = fields.RichTextField('Description')
443
status = fields.SelectField('Status')
444
start_date = fields.DateField('Start Date')
445
end_date = fields.DateField('End Date')
446
447
# Link to employees
448
team_members = fields.LinkField('Team Members', linked_model=Employee)
449
project_manager = fields.LinkField('Project Manager', linked_model=Employee)
450
451
# Computed fields
452
team_size = fields.CountField('Team Size')
453
total_salary_cost = fields.RollupField('Total Salary Cost')
454
455
# Create project with team members
456
project = Project(
457
name='New Product Launch',
458
description='Launch our new mobile app',
459
status='In Progress',
460
start_date=date(2024, 1, 1)
461
)
462
463
# Save project first
464
project.save()
465
466
# Add team members (assumes employees already exist)
467
engineer1 = Employee.first(formula="{Email} = 'jane.smith@company.com'")
468
engineer2 = Employee.first(formula="{Email} = 'alice@company.com'")
469
470
if engineer1 and engineer2:
471
project.team_members = [engineer1.id, engineer2.id] # Use record IDs
472
project.project_manager = engineer1.id
473
project.save()
474
```
475
476
#### Field Validation and Error Handling
477
478
```python
479
from pyairtable.exceptions import PyAirtableError
480
481
try:
482
# Invalid email will raise validation error
483
contact = Contact(
484
name='Test User',
485
email='invalid-email-format', # Invalid email
486
active=True
487
)
488
contact.save()
489
490
except PyAirtableError as e:
491
print(f"Validation error: {e}")
492
493
# Type conversion with typecast
494
employee = Employee(
495
first_name='John',
496
last_name='Doe',
497
salary='85000', # String will be converted to number
498
start_date='2023-06-15', # String will be converted to date
499
active='true' # String will be converted to boolean
500
)
501
502
# Save with automatic type conversion
503
result = employee.save()
504
```
505
506
#### Custom Model Methods
507
508
```python
509
class Employee(Model):
510
# ... field definitions ...
511
512
@property
513
def full_name(self) -> str:
514
"""Get employee's full name."""
515
return f"{self.first_name} {self.last_name}"
516
517
@property
518
def is_senior(self) -> bool:
519
"""Check if employee is in senior position."""
520
senior_titles = ['Senior', 'Lead', 'Principal', 'Manager', 'Director']
521
return any(title in self.position for title in senior_titles)
522
523
def give_raise(self, amount: float) -> None:
524
"""Give employee a salary raise."""
525
self.salary += amount
526
527
def transfer_department(self, new_department: str) -> None:
528
"""Transfer employee to new department."""
529
old_dept = self.department
530
self.department = new_department
531
self.notes = f"Transferred from {old_dept} to {new_department}"
532
533
@classmethod
534
def get_by_email(cls, email: str) -> Optional['Employee']:
535
"""Find employee by email address."""
536
return cls.first(formula=f"{{Email}} = '{email}'")
537
538
@classmethod
539
def active_employees(cls) -> list['Employee']:
540
"""Get all active employees."""
541
return cls.all(formula="{Active} = TRUE()")
542
543
# Usage
544
employee = Employee.get_by_email('jane.smith@company.com')
545
if employee:
546
print(f"Found: {employee.full_name}")
547
if employee.is_senior:
548
employee.give_raise(5000)
549
employee.save()
550
```