Skip to main content
Loss of provenance

A previous release had provenance metadata, but this release does not. View details.

modify-csv

Apply a user-provided Python function to every cell in one CSV data row, every cell in one column, or one cell. The CSV's first record is treated as its header and is never modified. Row and column indexes are zero-based; columns can also be addressed by their exact header text.

Install

Requires Python 3.11 or newer. modify-csv is a command-line tool, so the simplest way to install it is in its own environment with pipx or uv:

pipx install modify-csv
# or
uv tool install modify-csv

To install it into the current Python environment instead:

python -m pip install modify-csv

Quick start

Given people.csv:

name,email
Ada Lovelace, ADA@EXAMPLE.COM
Grace Hopper,Grace@Example.com

Generate a script template:

modify-csv template normalize.py

Edit normalize.py so eval returns the cleaned-up value:

from typing import Any


def eval(content: Any, row: int, col: int) -> Any:
    return content.strip().lower()

Apply it to the email column:

modify-csv apply people.csv clean.csv --script-file normalize.py --column email

clean.csv now contains:

name,email
Ada Lovelace,ada@example.com
Grace Hopper,grace@example.com

Script contract

The supplied script must define exactly this callable shape:

from typing import Any


def eval(content: Any, row: int, col: int) -> Any:
    return content

Any is provided by Python's standard-library typing module. The function name, parameter names, and three-argument shape are enforced.

content is the current CSV field (initially a string), while row and col are zero-based numeric indexes into the data records and columns. The script is ordinary trusted Python and executes with the same permissions as the command.

  • Return value: whatever eval returns is written with Python's CSV writer. None becomes an empty cell and any other value is written as str(value). When targets are chained, content is whatever the previous target returned, so it may not be a string.
  • Script lifetime: the script runs once, then eval is called for each cell. Top-level variables keep their values between calls, which is useful for lookup tables or counters. Code under if __name__ == "__main__": does not run.
  • Errors: if eval raises an exception, the run stops, nothing is written, and the command exits with status 1.
  • The name eval: defining eval hides Python's built-in eval() inside your script. Use builtins.eval if you need the built-in.

Usage

modify-csv has two commands, apply and template. Each has its own help page with only the options that belong to it:

modify-csv --help             # the commands
modify-csv apply --help       # targets, script options and the script contract
modify-csv template --help

Apply a script

Give the input and output files, exactly one of --script or --script-file, and at least one target:

modify-csv apply INPUT OUTPUT (--script CODE | --script-file PATH) TARGET... [--force]
Target Transforms
--row INDEX every cell in one data row
--column COLUMN every data cell in one column
--cell ROW COLUMN one cell

Rows and columns are numbered from zero; data row 0 is the first record after the header. A column is an index or the column's exact header text; a value that is a whole number is read as an index. Existing output files are protected; pass --force to replace one.

Apply a script file to a column selected by header:

modify-csv apply input.csv output.csv --script-file normalize.py --column email

Apply inline code to data row 2:

modify-csv apply input.csv output.csv \
  --script 'def eval(content, row, col): return content.strip()' \
  --row 2

Apply inline code to the cell at data row 0 and column 1:

modify-csv apply input.csv output.csv \
  --script 'def eval(content, row, col): return int(content) * 2' \
  --cell 0 1

Inline scripts or script files

--script takes the whole script as a single argument, which suits one-line functions. In POSIX shells, wrap it in single quotes so characters such as $ and " reach Python unchanged. For anything longer, such as imports, helper functions or state shared between cells, write a file (modify-csv template gives you a starting point) and pass it with --script-file.

Chain targets

Targets can be repeated and mixed. The script runs over them in the order they are given:

modify-csv apply input.csv output.csv --script-file normalize.py \
  --column email --column name --row 0 --cell 3 phone
  • A cell covered by several targets is transformed once per target, and each time it receives the result of the previous transformation. In the example above, the email and name cells of data row 0 are transformed twice.
  • Every target is checked against the CSV before any cell changes. If one is out of range or names a missing header, nothing is written.

Use from Python

transform_csv does the same work as apply. Each target is a tuple: ("row", INDEX, None), ("column", COLUMN, None) or ("cell", ROW, COLUMN), where a column is an index or exact header text. Pass one target or a sequence of them, and any callable that takes (content, row, col):

from pathlib import Path
from typing import Any

from modify_csv import Target, transform_csv


def normalize(content: Any, row: int, col: int) -> Any:
    return content.strip()


targets: list[Target] = [
    ("column", "email", None),
    ("row", 0, None),
    ("cell", 3, "phone"),
]
transform_csv(Path("input.csv"), Path("output.csv"), targets, normalize)

The name and signature checks apply only to scripts loaded by the command line. Pass overwrite=True to replace an existing output file. Problems are raised as modify_csv.errors.ModifyCsvError.

Generate a script template

modify-csv template transform.py

Use --force to replace an existing template.

Behaviour and limits

  • Encoding: input is read as UTF-8, with or without a byte-order mark (BOM). Output is always UTF-8 without a BOM, so a BOM in the input is not kept. Some versions of Excel need the BOM to recognise UTF-8.
  • Format: only comma-delimited files with double-quote quoting are read. Other delimiters are not detected; a semicolon-separated file, for example, is read as a single column. Output uses Python's CSV defaults, quoting fields only where needed and ending lines with \r\n, so the output can differ from the input byte for byte even where no cell changed.
  • Shape: every data row must have as many fields as the header. Otherwise the command reports the first mismatched row and writes nothing.
  • Columns by name: header text must match exactly, including case and spaces. A header name that appears more than once is reported as ambiguous; select that column by index. A header that is a whole number, such as 2024, can only be selected by index, because whole numbers are read as indexes.
  • Output: the result is written to a temporary file in the output folder and then moved into place, so a failed run never leaves a partial file. Missing output folders are created. To edit a file in place, give the same path as INPUT and OUTPUT and pass --force.
  • Memory: the whole input file is loaded into memory.
  • Exit status: 0 on success, 1 when the input, script or output has a problem (reported as modify-csv: error: ...), and 2 for command-line usage errors.

Breaking changes

Releases that change the command line or Python API in incompatible ways are listed, with migration steps, in BREAKING_CHANGES.md.

Contributing

Development setup, tests and conventions are in CONTRIBUTING.md.

License

Licensed under the GNU Affero General Public License v3 (AGPL-3.0-only).

Metadata

Release files for modify-csv 2026.10.9

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Loss of provenance

These files do not have provenance metadata, despite files in a prior release including this data.

We recommend further investigation before installing these files. View details.

Source distribution (sdist)

Source distribution for modify-csv 2026.10.9
File Size Uploaded Provenance
modify_csv-2026.10.9.tar.gz 23.4 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for modify-csv 2026.10.9
File Interpreter ABI Platform
modify_csv-2026.10.9-py3-none-any.whl Python 3 none any Details

Total release size: 45.9 kB

Release files / modify_csv-2026.10.9.tar.gz

Loss of provenance

This file belongs to a release without provenance metadata, despite a prior release including this data.

We recommend further investigation before installing this file. View details.

Download URL modify_csv-2026.10.9.tar.gz
Size 23.4 kB
Tags Source
SHA-256 checksum
How to use checksums
416a38ad2af7a86d300b6bb7d68c752b82c5b6b77799332c5da49437701e2451
BLAKE2b-256 checksum
How to use checksums
fee9e4271f8373f9997692a557f0a5863115c6bedffeccc17dfff6c8b3efa574
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / modify_csv-2026.10.9-py3-none-any.whl

Loss of provenance

This file belongs to a release without provenance metadata, despite a prior release including this data.

We recommend further investigation before installing this file. View details.

Download URL modify_csv-2026.10.9-py3-none-any.whl
Size 22.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
484f8fa47f8140dcb95e032e2ac5d3e797dbea1979ecaba99bb8034dc1e089e3
BLAKE2b-256 checksum
How to use checksums
2897ac85e414d02a5776d689b1d577a28e3964a3d50277f6e60186d46ed809bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Security for modify-csv 2026.10.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Loss of provenance

A previous release of these files had provenance metadata, but this release does not.

While this could reflect a routine change by the project maintainers, it may also indicate that the release files have been compromised.

Release comparison

  • Removed 2026.10.4 2 files With provenance
  • Added 2026.10.9 2 files Without provenance

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page