Python client for the Airtable API providing comprehensive database operations, ORM functionality, enterprise features, and testing utilities
—
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.
Base Model class for creating ORM-style table representations with field definitions and metadata configuration.
class Model:
def __init__(self, **field_values):
"""
Initialize model instance with field values.
Parameters:
- field_values: Keyword arguments mapping field names to values
"""
def save(self) -> object:
"""
Save model instance to Airtable (create or update).
Returns:
SaveResult object with operation details
"""
def delete(self) -> bool:
"""
Delete record from Airtable.
Returns:
True if deletion was successful
"""
def to_record(self) -> dict:
"""
Convert model instance to Airtable record dict.
Returns:
Dict with 'id', 'createdTime', and 'fields' keys
"""
@classmethod
def from_record(cls, record: dict) -> 'Model':
"""
Create model instance from Airtable record.
Parameters:
- record: Record dict from Airtable API
Returns:
Model instance populated with record data
"""
@classmethod
def from_records(cls, records: list[dict]) -> list['Model']:
"""
Create multiple model instances from record list.
Parameters:
- records: List of record dicts
Returns:
List of model instances
"""
@classmethod
def all(cls, **options) -> list['Model']:
"""
Retrieve all records as model instances.
Parameters:
- options: Same as Table.all() options
Returns:
List of model instances
"""
@classmethod
def first(cls, **options) -> Optional['Model']:
"""
Get first matching record as model instance.
Parameters:
- options: Same as Table.first() options
Returns:
Model instance or None
"""
@classmethod
def batch_save(cls, instances: list['Model']) -> list[object]:
"""
Save multiple model instances efficiently.
Parameters:
- instances: List of model instances to save
Returns:
List of SaveResult objects
"""
class SaveResult:
"""Result object returned from save operations."""
@property
def created(self) -> bool:
"""True if record was created, False if updated."""
@property
def record(self) -> dict:
"""The saved record dict."""Comprehensive field type system with automatic validation, conversion, and type safety for different Airtable field types.
# Text fields
class TextField:
def __init__(self, field_name: str):
"""Single line text field."""
class RichTextField:
def __init__(self, field_name: str):
"""Rich text field with formatting."""
class EmailField:
def __init__(self, field_name: str):
"""Email address field with validation."""
class UrlField:
def __init__(self, field_name: str):
"""URL field with validation."""
class PhoneNumberField:
def __init__(self, field_name: str):
"""Phone number field."""
# Numeric fields
class NumberField:
def __init__(self, field_name: str):
"""Number field (integer or decimal)."""
class CurrencyField:
def __init__(self, field_name: str):
"""Currency field with formatting."""
class PercentField:
def __init__(self, field_name: str):
"""Percentage field."""
class DurationField:
def __init__(self, field_name: str):
"""Duration field (time intervals)."""
class RatingField:
def __init__(self, field_name: str):
"""Rating field (star ratings)."""
# Boolean and selection fields
class CheckboxField:
def __init__(self, field_name: str):
"""Checkbox field (boolean values)."""
class SelectField:
def __init__(self, field_name: str):
"""Single select field (dropdown)."""
class MultipleSelectField:
def __init__(self, field_name: str):
"""Multiple select field (multiple choices)."""
# Date and time fields
class DateField:
def __init__(self, field_name: str):
"""Date field (date only)."""
class DatetimeField:
def __init__(self, field_name: str):
"""Datetime field (date and time)."""
class CreatedTimeField:
def __init__(self, field_name: str):
"""Auto-populated creation time (read-only)."""
class LastModifiedTimeField:
def __init__(self, field_name: str):
"""Auto-populated modification time (read-only)."""
# File and attachment fields
class AttachmentField:
def __init__(self, field_name: str):
"""File attachment field."""
# Relationship fields
class LinkField:
def __init__(self, field_name: str, linked_model: type = None):
"""
Link to another table field.
Parameters:
- field_name: Name of link field
- linked_model: Model class for linked table (optional)
"""
# Computed fields (read-only)
class FormulaField:
def __init__(self, field_name: str):
"""Formula field (computed, read-only)."""
class LookupField:
def __init__(self, field_name: str):
"""Lookup field (from linked records, read-only)."""
class RollupField:
def __init__(self, field_name: str):
"""Rollup field (aggregates from linked records, read-only)."""
class CountField:
def __init__(self, field_name: str):
"""Count field (counts linked records, read-only)."""
# System fields
class CreatedByField:
def __init__(self, field_name: str):
"""User who created record (read-only)."""
class LastModifiedByField:
def __init__(self, field_name: str):
"""User who last modified record (read-only)."""
class CollaboratorField:
def __init__(self, field_name: str):
"""Single collaborator field."""
class MultipleCollaboratorsField:
def __init__(self, field_name: str):
"""Multiple collaborators field."""
# Specialized fields
class AutoNumberField:
def __init__(self, field_name: str):
"""Auto-incrementing number field (read-only)."""
class BarcodeField:
def __init__(self, field_name: str):
"""Barcode field."""
class ButtonField:
def __init__(self, field_name: str):
"""Button field (triggers actions)."""from pyairtable.orm import Model, fields
class Contact(Model):
# Required Meta class with table configuration
class Meta:
base_id = 'app1234567890abcde'
table_name = 'Contacts'
api_key = 'your_access_token'
# Field definitions
name = fields.TextField('Name')
email = fields.EmailField('Email')
phone = fields.PhoneNumberField('Phone')
company = fields.TextField('Company')
active = fields.CheckboxField('Active')
rating = fields.RatingField('Rating')
notes = fields.RichTextField('Notes')
created = fields.CreatedTimeField('Created')
# Create and save records
contact = Contact(
name='John Doe',
email='john@example.com',
company='Acme Corp',
active=True,
rating=5
)
# Save to Airtable (creates new record)
result = contact.save()
print(f"Created record: {result.record['id']}")
# Update and save again
contact.phone = '+1-555-0123'
contact.notes = 'Updated contact info'
result = contact.save() # Updates existing record
print(f"Updated: {result.created}") # False for updatefrom datetime import date, datetime
from pyairtable.orm import Model, fields
class Employee(Model):
class Meta:
base_id = 'app1234567890abcde'
table_name = 'Employees'
api_key = 'your_access_token'
timeout = (5, 30) # Connection and read timeouts
typecast = True # Enable automatic type conversion
# Personal info
first_name = fields.TextField('First Name')
last_name = fields.TextField('Last Name')
email = fields.EmailField('Email')
phone = fields.PhoneNumberField('Phone')
# Employment details
employee_id = fields.AutoNumberField('Employee ID')
department = fields.SelectField('Department')
position = fields.TextField('Position')
salary = fields.CurrencyField('Salary')
start_date = fields.DateField('Start Date')
# Status and metrics
active = fields.CheckboxField('Active')
performance_rating = fields.RatingField('Performance')
skills = fields.MultipleSelectField('Skills')
# Relationships
manager = fields.LinkField('Manager', linked_model='Employee')
projects = fields.LinkField('Projects') # Links to Project model
# Computed fields (read-only)
years_employed = fields.FormulaField('Years Employed')
total_projects = fields.CountField('Total Projects')
# System fields
created_time = fields.CreatedTimeField('Created')
modified_time = fields.LastModifiedTimeField('Last Modified')
created_by = fields.CreatedByField('Created By')
# Create employee with related data
employee = Employee(
first_name='Jane',
last_name='Smith',
email='jane.smith@company.com',
department='Engineering',
position='Senior Developer',
salary=120000,
start_date=date(2023, 1, 15),
active=True,
skills=['Python', 'JavaScript', 'React']
)
result = employee.save()# Get all employees
all_employees = Employee.all()
# Filter employees
active_employees = Employee.all(
formula="AND({Active} = TRUE(), {Department} = 'Engineering')"
)
# Get first matching employee
first_engineer = Employee.first(
formula="{Department} = 'Engineering'",
sort=[{'field': 'Start Date', 'direction': 'asc'}]
)
# Pagination
recent_employees = Employee.all(
sort=[{'field': 'Created', 'direction': 'desc'}],
max_records=50
)
# Convert existing records to models
from pyairtable import Api
api = Api('your_token')
table = api.table('base_id', 'Employees')
records = table.all()
# Convert to model instances
employees = Employee.from_records(records)
for emp in employees:
print(f"{emp.first_name} {emp.last_name} - {emp.department}")# Create multiple employees
new_employees = [
Employee(
first_name='Alice',
last_name='Johnson',
email='alice@company.com',
department='Marketing',
active=True
),
Employee(
first_name='Bob',
last_name='Wilson',
email='bob@company.com',
department='Sales',
active=True
)
]
# Batch save for efficiency
results = Employee.batch_save(new_employees)
print(f"Created {len(results)} employees")
# Update multiple employees
for emp in Employee.all(formula="{Department} = 'Engineering'"):
emp.performance_rating = 5
# Save all changes
updated_employees = [emp for emp in Employee.all() if emp.performance_rating == 5]
Employee.batch_save(updated_employees)class Project(Model):
class Meta:
base_id = 'app1234567890abcde'
table_name = 'Projects'
api_key = 'your_access_token'
name = fields.TextField('Name')
description = fields.RichTextField('Description')
status = fields.SelectField('Status')
start_date = fields.DateField('Start Date')
end_date = fields.DateField('End Date')
# Link to employees
team_members = fields.LinkField('Team Members', linked_model=Employee)
project_manager = fields.LinkField('Project Manager', linked_model=Employee)
# Computed fields
team_size = fields.CountField('Team Size')
total_salary_cost = fields.RollupField('Total Salary Cost')
# Create project with team members
project = Project(
name='New Product Launch',
description='Launch our new mobile app',
status='In Progress',
start_date=date(2024, 1, 1)
)
# Save project first
project.save()
# Add team members (assumes employees already exist)
engineer1 = Employee.first(formula="{Email} = 'jane.smith@company.com'")
engineer2 = Employee.first(formula="{Email} = 'alice@company.com'")
if engineer1 and engineer2:
project.team_members = [engineer1.id, engineer2.id] # Use record IDs
project.project_manager = engineer1.id
project.save()from pyairtable.exceptions import PyAirtableError
try:
# Invalid email will raise validation error
contact = Contact(
name='Test User',
email='invalid-email-format', # Invalid email
active=True
)
contact.save()
except PyAirtableError as e:
print(f"Validation error: {e}")
# Type conversion with typecast
employee = Employee(
first_name='John',
last_name='Doe',
salary='85000', # String will be converted to number
start_date='2023-06-15', # String will be converted to date
active='true' # String will be converted to boolean
)
# Save with automatic type conversion
result = employee.save()class Employee(Model):
# ... field definitions ...
@property
def full_name(self) -> str:
"""Get employee's full name."""
return f"{self.first_name} {self.last_name}"
@property
def is_senior(self) -> bool:
"""Check if employee is in senior position."""
senior_titles = ['Senior', 'Lead', 'Principal', 'Manager', 'Director']
return any(title in self.position for title in senior_titles)
def give_raise(self, amount: float) -> None:
"""Give employee a salary raise."""
self.salary += amount
def transfer_department(self, new_department: str) -> None:
"""Transfer employee to new department."""
old_dept = self.department
self.department = new_department
self.notes = f"Transferred from {old_dept} to {new_department}"
@classmethod
def get_by_email(cls, email: str) -> Optional['Employee']:
"""Find employee by email address."""
return cls.first(formula=f"{{Email}} = '{email}'")
@classmethod
def active_employees(cls) -> list['Employee']:
"""Get all active employees."""
return cls.all(formula="{Active} = TRUE()")
# Usage
employee = Employee.get_by_email('jane.smith@company.com')
if employee:
print(f"Found: {employee.full_name}")
if employee.is_senior:
employee.give_raise(5000)
employee.save()Install with Tessl CLI
npx tessl i tessl/pypi-pyairtable