or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

attachments.mdcli.mdcomments.mdcore-api.mdenterprise.mdformulas.mdindex.mdorm.mdrecord-operations.mdtesting.mdwebhooks.md

orm.mddocs/

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

```