Skip to main content

A tiny, safe CSV writer for flat key/value dicts (with CSV/formula-injection protection).

Project description

basic-csv-writer

A tiny, dependency-free CSV writer for the most common boring case: you have a flat {key: value} dict and you want a clean two-column CSV out of it — a header plus one row per item. Pure Python standard library (csv), packaged for PyPI.

The one thing it does beyond csv.DictWriter: it is safe by default. A naive CSV is a well-known injection vector — a cell whose text starts with =, +, - or @ is treated as a formula by Excel / Google Sheets / LibreOffice when the file is opened, so an attacker-controlled value like =cmd|'/c calc'!A1 can run code on whoever opens your "data" file. This library neutralises those cells automatically.

Install

pip install basic-csv-writer

Usage

import basic_csv_writer

fieldnames = ["username", "password"]
rows = {
    "jack": "password",
    "jill": "password",
    "mike": "123456",
}
rows["sam"] = "secretpassword"

with open("passwords.csv", "w", newline="") as fh:
    basic_csv_writer.write(fh, fieldnames, rows)

passwords.csv:

username,password
jack,password
jill,password
mike,123456
sam,secretpassword

Note on the original sketch. The first idea wrote write(file, fieldnames=fieldnames, simple_dict), which is not valid Python (a positional argument cannot follow a keyword argument). The real signature is write(file, fieldnames, mapping) — call it positionally as write(fh, fieldnames, rows).

Don't want to manage the file handle?

basic_csv_writer.write_path("passwords.csv", fieldnames, rows)

Just want the CSV as a string?

text = basic_csv_writer.writes(["k", "v"], {"a": 1, "b": 2})
# 'k,v\r\na,1\r\nb,2\r\n'

Safe by default: CSV / formula injection

CSV is a data format — nothing this library writes can execute on its own. The risk lives in the program that opens the file. By default, any cell that would be interpreted as a formula is prefixed with a single quote ('), the OWASP-recommended fix, so it renders as literal text:

basic_csv_writer.writes(["user", "pw"], {"eve": "=2+5"}, write_header=False)
# "eve,'=2+5\r\n"   -> opens as the text =2+5, never the number 7

You choose the policy via mode:

mode behaviour
SanitizeMode.ESCAPE prefix dangerous cells with ' (default, safe)
SanitizeMode.ERROR raise ValueError on the first dangerous cell
SanitizeMode.NONE write everything verbatim (opt-in, unsafe)
from basic_csv_writer import SanitizeMode

# Refuse to emit a risky file at all:
basic_csv_writer.writes(["k", "v"], data, mode=SanitizeMode.ERROR)

This also resolves the classic 123 (int) vs '123' (str) ambiguity from the original idea: whatever you pass in lands in the file as deterministic, literal text and can never be silently reinterpreted.

The dangerous-character set is overridable via dangerous_prefixes=(...).

Command line

# KEY=VALUE pairs
python -m basic_csv_writer --columns username,password jack=password mike=123456

# from a JSON object (file or stdin)
echo '{"a": "=2+5", "b": 3}' | python -m basic_csv_writer --columns k,v --json -

# write to a file; --strict refuses formula-injection values, --unsafe disables protection
python -m basic_csv_writer -o out.csv --columns k,v --strict a=1 b=2

(An installed console script basic-csv-writer is provided too.)

API

  • write(file, fieldnames, mapping, *, mode=SanitizeMode.ESCAPE, write_header=True, dangerous_prefixes=..., **dictwriter_kwargs) -> int Write to an open text file (open it with newline=""). Returns the number of data rows written. Extra keyword args are forwarded to csv.DictWriter (delimiter, dialect, quoting, ...).
  • write_path(path, fieldnames, mapping, *, encoding="utf-8", ...) -> int Open path (with newline=""), write, and close.
  • writes(fieldnames, mapping, ...) -> str Return the CSV as a string.
  • sanitize_value(value, mode=..., dangerous_prefixes=...) -> str, is_dangerous(value, dangerous_prefixes=...) -> bool — the sanitization primitives, exposed for reuse.

fieldnames must contain exactly two names (a key column and a value column). mapping must be a dict-like object.

Development

python -m venv .venv && . .venv/bin/activate
pip install -e ".[test]"
pytest -q

License

MIT © Michael Smolkin

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

basic_csv_writer-0.1.0.tar.gz (11.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

basic_csv_writer-0.1.0-py3-none-any.whl (10.9 kB view details)

Uploaded Python 3

File details

Details for the file basic_csv_writer-0.1.0.tar.gz.

File metadata

  • Download URL: basic_csv_writer-0.1.0.tar.gz
  • Upload date:
  • Size: 11.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for basic_csv_writer-0.1.0.tar.gz
Algorithm Hash digest
SHA256 064beba9ac4c733d287ef9d7b69f15a99530f0369b9d25942da53263e3a78ada
MD5 00500c58a4caba067e0f1a2eb8a7bef5
BLAKE2b-256 713c46f89c03af1d5fe18c4e3594fb7973a1effe23859960988c1503bf472fe0

See more details on using hashes here.

File details

Details for the file basic_csv_writer-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for basic_csv_writer-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9548cc18c878795c7243bc6ba1ff13d8af34f1fe3b46ba5b94cc84794e37f197
MD5 a966576e729b9200f10f5ef944062b95
BLAKE2b-256 fd660d50c1649de4be5863377802bc8ebf63dea2a2dbd6ca65dbef5620cd65fa

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page