Post-processing scripts
A CSV export can go through a Python script you write before it becomes the file to download. Use it when the file needs a shape that choosing columns cannot give: capitalized names, a note next to certain values, tags split into separate columns.
The script runs isolated, without network, with time and memory limits. The result is the responsibility of whoever writes it.
Where to write it
- In the export dialog: turn on Post-process the export below the columns. The switch is available only with the CSV format.
- In an export model, with the same switch. Picking the model in the export dialog loads the script along with the columns. If you edit it or turn it off, the dropdown switches to Custom model.
Turning the switch off excludes the script without deleting its text.
The export keeps the script it started with, visible in the detail in Settings > Exports.
How it works
- Riseact generates the CSV.
- The script reads
/work/input.csvand writes/work/output.csv. output.csvbecomes the export file; the original is not kept.- The record count shown is the number of rows in
output.csv, header excluded.
If the script fails, the export fails.
The input file
- UTF-8 with BOM, comma separated, header on the first row. Open it with
encoding="utf-8-sig"andnewline="". - The headers are the column keys (
email,tags,donation amount), described on each export's page. - Multiple values, such as tags, are comma separated in the same cell.
The output file
- Write it with
encoding="utf-8-sig"so Excel reads accented letters. - Riseact does not validate it: you download whatever the script writes.
- If you change column names or values, the file may no longer be re-importable.
Environment and limits
| Interpreter | Python 3.14, standard library |
| Files | /work, with the export input.csv |
| Network | None |
| Memory | 128 MB, about 40 of which used by the interpreter |
| Time | 20 minutes |
| Disk | Three times the input file, at least 10 MB |
| Output | print is lost; stderr, traceback included, becomes the error message |
Work row by row: a large export does not fit in 128 MB, so no list(reader). If
you need two passes, open the file twice.
The script time adds to the extraction time. As a reference, splitting tags into columns over one million rows takes less than a minute.
Errors
- Unhandled exception or
sys.exitother than 0: the export fails and the detail shows the end of stderr, usually the traceback with the line number in/work/script.py. - Time or disk exhausted: the script is stopped and the detail says which limit was exceeded.
output.csvmissing: the export fails.
The outcome email reports the failure but not the error, because a traceback can contain personal data.
Test the script on a few records, with a manual selection or a narrow filter, before running it on the whole archive.
Example: capital first letter
In the supporters export, first name, last name and city with a capital initial. The
rest of the value does not change: de luca becomes De luca.
import csv
COLUMNS = ["first name", "last name", "city"]
def capitalize(value):
return value[:1].upper() + value[1:]
with open("/work/input.csv", encoding="utf-8-sig", newline="") as f, open(
"/work/output.csv", "w", encoding="utf-8-sig", newline=""
) as out:
reader = csv.DictReader(f)
writer = csv.DictWriter(out, fieldnames=reader.fieldnames)
writer.writeheader()
for row in reader:
for column in COLUMNS:
row[column] = capitalize(row[column])
writer.writerow(row)
In the donations and payments exports the same columns are called
supporter first name, supporter last name and supporter city.
Example: note on certain values
In the donations export, appends (recurring) to the frequency of monthly and
yearly donations.
import csv
COLUMN = "donation frequency"
NOTES = {
"MONTHLY": " (recurring)",
"ANNUALLY": " (recurring)",
}
with open("/work/input.csv", encoding="utf-8-sig", newline="") as f, open(
"/work/output.csv", "w", encoding="utf-8-sig", newline=""
) as out:
reader = csv.DictReader(f)
writer = csv.DictWriter(out, fieldnames=reader.fieldnames)
writer.writeheader()
for row in reader:
row[COLUMN] += NOTES.get(row[COLUMN], "")
writer.writerow(row)
From the API
The supporterExportCreate, donationExportCreate and paymentExportCreate
mutations accept the script in the script argument, only with format: CSV.
exportModelCreate and exportModelUpdate save it in the model's script field.
The export exposes the script in the script field; if the script fails, the error
code is SCRIPT_ERROR with the message in errorMessage.