Codester demo · Scripts & Code → Python
CSV to Airtable sync with dry-run first: validation, field mapping, rejection report, upserts only on --commit.
The package includes a sample CSV and config so you can see the whole loop with no Airtable account: 2 valid rows and 2 intentional rejections. sample_customers.csv, as shipped:
| customer_id | name | monthly_spend | active | Dry-run verdict | |
|---|---|---|---|---|---|
| C-1001 | Acme Fitness | ops@acme.example | 1200.50 | true | Valid |
| C-1002 | Northwind Studio | owner@northwind.example | 775 | yes | Valid |
| C-1003 | (missing) | missing-name@example.com | 500 | true | Rejected — missing required value: name |
| C-1002 | Duplicate Studio | duplicate@example.com | 100 | false | Rejected — duplicate key in CSV: C-1002 |
{
"key_column": "customer_id",
"key_field": "External ID",
"field_mapping": {
"customer_id": "External ID",
"name": "Customer Name",
"email": "Email",
"monthly_spend": "Monthly Spend",
"active": "Active"
},
"required": ["customer_id", "name", "email"],
"types": {
"monthly_spend": "number",
"active": "boolean"
}
}Dry-run is the default: the whole file is validated first and nothing touches Airtable until --commit is added — and a commit refuses the entire file if even one row is rejected. Here is the actual dry run on the sample: the summary it prints, and the rejections.jsonl it writes, with a row-level reason for each rejected row.
$ python3 sync.py sample_customers.csv --config sample_config.json
{
"valid": 2,
"rejected": 2,
"committed": false
}
exit code: 0
--- rejections.jsonl (written by the dry run) ---
{"reason": "missing required value: name", "row": {"active": "true", "customer_id": "C-1003", "email": "missing-name@example.com", "monthly_spend": "500", "name": ""}, "row_number": 4}
{"reason": "duplicate key in CSV: C-1002", "row": {"active": "false", "customer_id": "C-1002", "email": "duplicate@example.com", "monthly_spend": "100", "name": "Duplicate Studio"}, "row_number": 5}Screenshots shipped inside the Codester package.
