CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pyairtable

Python client for the Airtable API providing comprehensive database operations, ORM functionality, enterprise features, and testing utilities

Pending
Overview
Eval results
Files

testing.mddocs/

Testing Utilities

Mock Airtable APIs and helper functions for unit testing applications that use pyAirtable. Provides fake data generation, API simulation, and testing infrastructure without making real API calls.

Capabilities

Mock Airtable API

Context manager that intercepts pyAirtable API calls and provides controllable mock responses for testing.

class MockAirtable:
    def __init__(self, passthrough: bool = False):
        """
        Initialize mock Airtable instance.
        
        Parameters:
        - passthrough: If True, unmocked methods make real requests
        """
    
    def __enter__(self) -> 'MockAirtable':
        """Enter context manager and start mocking."""
    
    def __exit__(self, *exc_info) -> None:
        """Exit context manager and stop mocking."""
    
    def add_records(self, base_id: str, table_name: str, records: list[dict]) -> list[dict]:
        """
        Add mock records to table.
        
        Parameters:
        - base_id: Base ID for records
        - table_name: Table name for records  
        - records: List of record dicts or field dicts
        
        Returns:
        List of normalized record dicts with IDs
        """
    
    def set_records(self, base_id: str, table_name: str, records: list[dict]) -> None:
        """
        Replace all mock records for table.
        
        Parameters:
        - base_id: Base ID
        - table_name: Table name
        - records: List of record dicts to set
        """
    
    def clear(self) -> None:
        """Clear all mock records."""
    
    def set_passthrough(self, allowed: bool):
        """Context manager to temporarily enable/disable passthrough."""
    
    def enable_passthrough(self):
        """Enable passthrough for real API calls."""
    
    def disable_passthrough(self):
        """Disable passthrough (mock only)."""

def fake_record(fields: Optional[dict] = None, id: Optional[str] = None, **other_fields) -> dict:
    """
    Generate fake record dict with proper structure.
    
    Parameters:
    - fields: Field values dict
    - id: Record ID (auto-generated if not provided)
    - other_fields: Additional field values as kwargs
    
    Returns:
    Complete record dict with id, createdTime, and fields
    """

def fake_user(value=None) -> dict:
    """
    Generate fake user/collaborator dict.
    
    Parameters:
    - value: Name/identifier for user (auto-generated if not provided)
    
    Returns:
    User dict with id, email, and name
    """

def fake_attachment(url: str = "", filename: str = "") -> dict:
    """
    Generate fake attachment dict.
    
    Parameters:
    - url: Attachment URL (auto-generated if not provided)
    - filename: Filename (derived from URL if not provided)
    
    Returns:
    Attachment dict with id, url, filename, size, and type
    """

def fake_id(type: str = "rec", value=None) -> str:
    """
    Generate fake Airtable-style ID.
    
    Parameters:
    - type: ID prefix (rec, app, tbl, etc.)
    - value: Custom value to embed in ID
    
    Returns:
    Airtable-formatted ID string
    """

Usage Examples

Basic Mock Setup

from pyairtable import Api
from pyairtable.testing import MockAirtable, fake_record

def test_record_operations():
    # Mock all pyAirtable API calls
    with MockAirtable() as mock:
        # Add test records
        mock.add_records('app123', 'Contacts', [
            {'Name': 'John Doe', 'Email': 'john@example.com'},
            {'Name': 'Jane Smith', 'Email': 'jane@example.com'}
        ])
        
        # Use normal pyAirtable code - no real API calls made
        api = Api('fake_token')
        table = api.table('app123', 'Contacts')
        
        # Get all records (returns mock data)
        records = table.all()
        assert len(records) == 2
        assert records[0]['fields']['Name'] == 'John Doe'
        
        # Create record (adds to mock data)
        new_record = table.create({'Name': 'Bob Wilson', 'Email': 'bob@example.com'})
        assert new_record['id'].startswith('rec')
        
        # Verify creation
        all_records = table.all()
        assert len(all_records) == 3

Pytest Integration

import pytest
from pyairtable.testing import MockAirtable

@pytest.fixture(autouse=True)
def mock_airtable():
    """Auto-use fixture that mocks Airtable for all tests."""
    with MockAirtable() as mock:
        yield mock

def test_my_function(mock_airtable):
    # Add test data
    mock_airtable.add_records('base_id', 'table_name', [
        {'Status': 'Active', 'Count': 5},
        {'Status': 'Inactive', 'Count': 2}
    ])
    
    # Test your function that uses pyAirtable
    result = my_function_that_uses_airtable()
    assert result == expected_value

Advanced Testing Scenarios

from pyairtable.testing import MockAirtable, fake_record, fake_user, fake_attachment

def test_complex_data_structures():
    with MockAirtable() as mock:
        # Create records with various field types
        test_records = [
            fake_record({
                'Name': 'Project Alpha',
                'Status': 'In Progress',
                'Collaborators': [fake_user('Alice'), fake_user('Bob')],
                'Attachments': [
                    fake_attachment('https://example.com/doc.pdf', 'project_spec.pdf'),
                    fake_attachment('https://example.com/img.png', 'mockup.png')
                ],
                'Priority': 5,
                'Active': True
            }, id='rec001'),
            
            fake_record({
                'Name': 'Project Beta', 
                'Status': 'Complete',
                'Collaborators': [fake_user('Charlie')],
                'Priority': 3,
                'Active': False
            }, id='rec002')
        ]
        
        mock.set_records('app123', 'Projects', test_records)
        
        # Test filtering and retrieval
        api = Api('test_token')
        table = api.table('app123', 'Projects')
        
        active_projects = table.all(formula="{Active} = TRUE()")
        assert len(active_projects) == 1
        assert active_projects[0]['fields']['Name'] == 'Project Alpha'

Testing Error Conditions

import requests
from pyairtable.testing import MockAirtable

def test_api_error_handling():
    with MockAirtable(passthrough=False) as mock:
        # Add limited test data
        mock.add_records('app123', 'table1', [{'Name': 'Test'}])
        
        api = Api('test_token')
        table = api.table('app123', 'table1')
        
        # This works - record exists in mock
        record = table.get('rec000000000000001')
        assert record['fields']['Name'] == 'Test'
        
        # This raises KeyError - record not in mock data
        with pytest.raises(KeyError):
            table.get('rec999999999999999')

def test_with_real_api_fallback():
    """Test that combines mocking with real API calls."""
    with MockAirtable() as mock:
        # Enable passthrough for schema calls
        with mock.enable_passthrough():
            # This makes a real API call (if needed)
            api = Api(os.environ['AIRTABLE_API_KEY'])
            base = api.base('real_base_id')
            schema = base.schema()  # Real API call
        
        # Back to mocking for data operations
        mock.add_records('real_base_id', 'real_table', [
            {'Field1': 'Mock Data'}
        ])
        
        table = base.table('real_table')
        records = table.all()  # Uses mock data
        assert records[0]['fields']['Field1'] == 'Mock Data'

Batch Operation Testing

def test_batch_operations():
    with MockAirtable() as mock:
        # Test batch create
        api = Api('test_token')
        table = api.table('app123', 'Contacts')
        
        batch_data = [
            {'Name': f'User {i}', 'Email': f'user{i}@example.com'}
            for i in range(1, 6)
        ]
        
        created = table.batch_create(batch_data)
        assert len(created) == 5
        
        # Verify all records exist
        all_records = table.all()
        assert len(all_records) == 5
        
        # Test batch update
        updates = [
            {'id': created[0]['id'], 'fields': {'Status': 'VIP'}},
            {'id': created[1]['id'], 'fields': {'Status': 'Regular'}}
        ]
        
        updated = table.batch_update(updates)
        assert len(updated) == 2
        assert updated[0]['fields']['Status'] == 'VIP'
        
        # Test batch upsert
        upsert_data = [
            {'Name': 'User 1', 'Email': 'user1@example.com', 'Status': 'Premium'},  # Update
            {'Name': 'User 6', 'Email': 'user6@example.com', 'Status': 'New'}       # Create
        ]
        
        result = table.batch_upsert(upsert_data, key_fields=['Name'])
        assert len(result['updatedRecords']) == 1
        assert len(result['createdRecords']) == 1

ORM Testing

from pyairtable.orm import Model, fields
from pyairtable.testing import MockAirtable, fake_meta

class TestContact(Model):
    # Use fake_meta for testing
    Meta = fake_meta(
        base_id='app123',
        table_name='Contacts',
        api_key='test_token'
    )
    
    name = fields.TextField('Name')
    email = fields.EmailField('Email')
    active = fields.CheckboxField('Active')

def test_orm_with_mock():
    with MockAirtable() as mock:
        # Create and save model instance
        contact = TestContact(
            name='John Doe',
            email='john@example.com',
            active=True
        )
        
        result = contact.save()
        assert result.created is True
        assert contact.name == 'John Doe'
        
        # Test retrieval
        all_contacts = TestContact.all()
        assert len(all_contacts) == 1
        assert all_contacts[0].name == 'John Doe'
        
        # Test update
        contact.active = False
        result = contact.save()
        assert result.created is False  # Update, not create

Install with Tessl CLI

npx tessl i tessl/pypi-pyairtable

docs

attachments.md

cli.md

comments.md

core-api.md

enterprise.md

formulas.md

index.md

orm.md

record-operations.md

testing.md

webhooks.md

tile.json