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.1.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.1-py3-none-any.whl (10.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: basic_csv_writer-0.1.1.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.1.tar.gz
Algorithm Hash digest
SHA256 6d3e2b02cf7c05b04d45b66dd932552e2ac49b17a18208b8256a1640768605f2
MD5 a29dce9a975596d6887e102f9af87b19
BLAKE2b-256 c65fa6af928d45097c369b2ec70c25b47306a2a2d032fdaf96e246cda27ab6de

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for basic_csv_writer-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 58374864916e01c47969e6cf60da8dfce834898ade2f82c1e90605a35eb6a98c
MD5 27406ad4a841d6bd9d9333ea4287b742
BLAKE2b-256 9d7cdd9909f34db9c98b56bc8170f829c5b64ce1a258c6b12d9486a3140cf9af

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