A Python library for managing Airtable data using Pydantic objects
Project description
🚀 Pydantic Airtable
The most intuitive way to integrate Pydantic models with Airtable
✨ Clean, Intuitive Airtable Integration
Transform your Pydantic models into fully-functional Airtable integrations with just 8 lines of code:
from pydantic_airtable import airtable_model, configure_from_env
from pydantic import BaseModel
configure_from_env() # Auto-loads from .env
@airtable_model(table_name="Users")
class User(BaseModel):
name: str # Auto-detects as SINGLE_LINE_TEXT
email: str # Auto-detects as EMAIL
age: Optional[int] = None # Auto-detects as NUMBER
is_active: bool = True # Auto-detects as CHECKBOX
# That's it! Now you have full CRUD operations
user = User.create(name="Alice", email="alice@example.com", age=28)
users = User.all()
alice = User.find_by(name="Alice")
🌟 Key Features
| Feature | Description | Example |
|---|---|---|
| Type Detection | Auto-detects field types from naming patterns | email: str → EMAIL field |
| Zero Config | Works with just environment variables | AIRTABLE_ACCESS_TOKEN=pat_... |
| Table Creation | Creates tables from model definitions | User.create_table() |
| Intuitive CRUD | Simple, predictable methods | User.create(), User.all(), user.save() |
| Advanced Filtering | Clean query syntax | User.find_by(is_active=True) |
| Batch Operations | Efficient bulk operations | User.bulk_create([...]) |
📦 Installation
pip install pydantic-airtable
🚀 Quick Start
1. Setup (One Time)
Create a .env file:
AIRTABLE_ACCESS_TOKEN=pat_your_personal_access_token
AIRTABLE_BASE_ID=app_your_base_id
Get your credentials:
- Personal Access Token: Airtable Developer Hub
- Base ID: Found in your Airtable base URL
2. Define Your Model
from pydantic_airtable import airtable_model, configure_from_env
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
# One-line configuration
configure_from_env()
@airtable_model(table_name="Users")
class User(BaseModel):
# Field type detection
name: str # → SINGLE_LINE_TEXT
email: str # → EMAIL (detected from field name)
phone: str # → PHONE (detected from field name)
website: str # → URL (detected from field name)
bio: str # → LONG_TEXT (detected from field name)
age: Optional[int] = None # → NUMBER
is_active: bool = True # → CHECKBOX
created_at: datetime # → DATETIME
3. Use It!
# Create table automatically
User.create_table()
# Create records
alice = User.create(
name="Alice Johnson",
email="alice@example.com",
phone="555-0123",
age=28
)
bob = User.create(
name="Bob Smith",
email="bob@example.com",
website="https://bobsmith.dev"
)
# Query records
all_users = User.all()
active_users = User.find_by(is_active=True)
alice = User.first(name="Alice Johnson")
# Update records
alice.age = 29
alice.save()
# Batch operations
users_data = [
{"name": "Charlie", "email": "charlie@example.com"},
{"name": "Diana", "email": "diana@example.com"},
]
User.bulk_create(users_data)
🧠 Field Type Detection
The system automatically detects Airtable field types:
| Python Code | Detected Type | Reason |
|---|---|---|
email: str |
Field name contains "email" | |
phone: str |
PHONE | Field name contains "phone" |
website: str |
URL | Field name contains "website" |
description: str |
LONG_TEXT | Field name suggests long text |
price: float |
CURRENCY | Field name suggests money |
completion_rate: float |
PERCENT | Field name suggests percentage |
is_active: bool |
CHECKBOX | Boolean type |
created_at: datetime |
DATETIME | DateTime type |
Priority: Enum |
SELECT | Enum type |
tags: List[str] |
MULTI_SELECT | List type |
⚙️ Advanced Usage
Override Auto-Detection
from pydantic_airtable import airtable_field, AirtableFieldType
@airtable_model(table_name="Projects")
class Project(BaseModel):
name: str # Auto-detected as SINGLE_LINE_TEXT
# Override detection with explicit configuration
status: str = airtable_field(
field_type=AirtableFieldType.SELECT,
choices=["Planning", "In Progress", "Done"]
)
# Custom field name in Airtable
description: str = airtable_field(
field_name="Project Description",
field_type=AirtableFieldType.LONG_TEXT
)
Multiple Configurations
# Per-model configuration
user_config = AirtableConfig(
access_token="pat_user_token",
base_id="app_user_base"
)
@airtable_model(config=user_config, table_name="Users")
class User(BaseModel):
name: str
email: str
# Or inline configuration
@airtable_model(
table_name="Projects",
access_token="pat_project_token",
base_id="app_project_base"
)
class Project(BaseModel):
name: str
description: str
Table Management
# Create table from model
result = User.create_table()
print(f"Created table: {result['id']}")
# Sync model changes to existing table
sync_result = User.sync_table(
create_missing_fields=True,
update_field_types=False
)
print(f"Added {len(sync_result['fields_created'])} new fields")
# Check if table exists
try:
users = User.all()
print("Table exists and accessible")
except Exception:
print("Table needs to be created")
User.create_table()
📚 Complete Examples
Simple Task Manager
from pydantic_airtable import airtable_model, configure_from_env
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
from enum import Enum
configure_from_env()
class Priority(str, Enum):
LOW = "Low"
MEDIUM = "Medium"
HIGH = "High"
@airtable_model(table_name="Tasks")
class Task(BaseModel):
title: str
description: Optional[str] = None
priority: Priority = Priority.MEDIUM # → SELECT field
completed: bool = False # → CHECKBOX field
due_date: Optional[datetime] = None # → DATETIME field
# Usage
Task.create_table()
task = Task.create(
title="Implement user authentication",
description="Add JWT-based auth system",
priority=Priority.HIGH,
due_date=datetime(2024, 12, 31)
)
# Find high priority incomplete tasks
urgent_tasks = Task.find_by(priority=Priority.HIGH, completed=False)
Customer Relationship Management
@airtable_model(table_name="Customers")
class Customer(BaseModel):
# Contact info (auto detection)
name: str # → SINGLE_LINE_TEXT
email: str # → EMAIL
phone: str # → PHONE
website: Optional[str] = None # → URL
# Business info
company: Optional[str] = None # → SINGLE_LINE_TEXT
title: Optional[str] = None # → SINGLE_LINE_TEXT
# Relationship tracking
status: str = "Lead" # → SINGLE_LINE_TEXT
notes: Optional[str] = None # → LONG_TEXT (detected)
# Financials
deal_value: Optional[float] = None # → CURRENCY (if named 'price', 'cost', etc.)
# Usage
Customer.create_table()
customer = Customer.create(
name="Jane Doe",
email="jane@example.com",
phone="555-0199",
website="https://example.com",
company="Example Corp",
title="CTO",
deal_value=50000.00
)
# Find all customers with websites
web_customers = Customer.find_by(website__not_empty=True)
🎛️ Configuration Options
Environment Variables
# Required
AIRTABLE_ACCESS_TOKEN=pat_your_token # Personal Access Token
AIRTABLE_BASE_ID=app_your_base # Base ID
# Optional
AIRTABLE_TABLE_NAME=DefaultTable # Default table name
Programmatic Configuration
from pydantic_airtable import AirtableConfig, set_global_config
# Method 1: From environment
configure_from_env()
# Method 2: Explicit configuration
config = AirtableConfig(
access_token="pat_your_token",
base_id="app_your_base",
table_name="DefaultTable" # optional
)
set_global_config(config)
# Method 3: Per-model configuration
@airtable_model(config=config, table_name="SpecificTable")
class MyModel(BaseModel):
pass
🧪 Testing
import pytest
from pydantic_airtable import configure_from_env
# Setup test configuration
@pytest.fixture(autouse=True)
def setup_airtable():
configure_from_env(
access_token="pat_test_token",
base_id="app_test_base"
)
def test_user_creation():
user = User.create(name="Test User", email="test@example.com")
assert user.name == "Test User"
assert user.id is not None
# Cleanup
user.delete()
🔧 Development Setup
# Clone repository
git clone https://github.com/pydantic-airtable/pydantic-airtable.git
cd pydantic-airtable
# Install development dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Run examples
cd examples/simple_usage
python simple_usage.py
📖 API Reference
Model Decorator
@airtable_model(
table_name: str, # Airtable table name
config: AirtableConfig = None, # Configuration object
access_token: str = None, # Direct token specification
base_id: str = None # Direct base ID specification
)
Model Methods
# Class methods
User.create(**data) -> User # Create new record
User.get(record_id: str) -> User # Get by ID
User.all(**filters) -> List[User] # Get all records
User.find_by(**filters) -> List[User] # Find by field values
User.first(**filters) -> Optional[User] # Get first match
User.bulk_create(data_list) -> List[User] # Create multiple records
User.create_table() -> dict # Create table from model
User.sync_table(**options) -> dict # Sync model to existing table
# Instance methods
user.save() -> User # Save changes
user.delete() -> dict # Delete record
Field Utilities
from pydantic_airtable import airtable_field, AirtableFieldType
field = airtable_field(
field_type: AirtableFieldType = None, # Override auto-detection
field_name: str = None, # Custom Airtable field name
read_only: bool = False, # Read-only field
choices: List[str] = None, # For SELECT/MULTI_SELECT
**pydantic_field_kwargs # Standard Pydantic Field options
)
❓ Troubleshooting
Common Issues
Issue: ConfigurationError: Airtable Personal Access Token is required
# Solution: Set environment variables or configure explicitly
configure_from_env() # Loads from .env file
# OR
configure_from_env(access_token="pat_...", base_id="app_...")
Issue: APIError: Table 'Users' not found
# Solution: Create the table first
User.create_table()
Issue: Field type not detected correctly
# Solution: Override with explicit type
from pydantic_airtable import airtable_field, AirtableFieldType
class User(BaseModel):
description: str = airtable_field(
field_type=AirtableFieldType.LONG_TEXT
)
Performance Tips
- Use
bulk_create()for multiple records - Cache model instances when possible
- Use
find_by()instead of filteringall()results - Set up proper indexes in Airtable for frequently queried fields
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
📖 Documentation
Full documentation is available at pydantic-airtable.readthedocs.io
🔗 Links
Made with ❤️ for the Python community
Transform your Airtable integration from complex to simple with just 8 lines of code! 🚀
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pydantic_airtable-1.0.1.tar.gz.
File metadata
- Download URL: pydantic_airtable-1.0.1.tar.gz
- Upload date:
- Size: 33.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
81ff202dfd0830fc6b7df4e76b11edc6ef0119cf2cbec005e4be368bea48ffd6
|
|
| MD5 |
6387aaf8cc5992c98e7b1312c4d74d83
|
|
| BLAKE2b-256 |
4840fbadc8e27670dc64384f47652af88aecfcd668dae68377120e3e4c6f4c70
|
Provenance
The following attestation bundles were made for pydantic_airtable-1.0.1.tar.gz:
Publisher:
publish.yml on grishick/pydantic-airtable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pydantic_airtable-1.0.1.tar.gz -
Subject digest:
81ff202dfd0830fc6b7df4e76b11edc6ef0119cf2cbec005e4be368bea48ffd6 - Sigstore transparency entry: 798834735
- Sigstore integration time:
-
Permalink:
grishick/pydantic-airtable@bc947f3b50bfd3b1ea06c16f1743b123b3063fa2 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/grishick
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bc947f3b50bfd3b1ea06c16f1743b123b3063fa2 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pydantic_airtable-1.0.1-py3-none-any.whl.
File metadata
- Download URL: pydantic_airtable-1.0.1-py3-none-any.whl
- Upload date:
- Size: 33.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ce70042159ce34449a65b2882e568e792f17208d57298211066aa0ff32dfca95
|
|
| MD5 |
dc07fd7f7fad0bab7cd23ba2745a1158
|
|
| BLAKE2b-256 |
f4cdf568efcb34c2516da263e7b90b2a1e00c7cf115f07edc2c06d5063dacc4c
|
Provenance
The following attestation bundles were made for pydantic_airtable-1.0.1-py3-none-any.whl:
Publisher:
publish.yml on grishick/pydantic-airtable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pydantic_airtable-1.0.1-py3-none-any.whl -
Subject digest:
ce70042159ce34449a65b2882e568e792f17208d57298211066aa0ff32dfca95 - Sigstore transparency entry: 798834738
- Sigstore integration time:
-
Permalink:
grishick/pydantic-airtable@bc947f3b50bfd3b1ea06c16f1743b123b3063fa2 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/grishick
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bc947f3b50bfd3b1ea06c16f1743b123b3063fa2 -
Trigger Event:
release
-
Statement type: