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 iswrite(file, fieldnames, mapping)— call it positionally aswrite(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) -> intWrite to an open text file (open it withnewline=""). Returns the number of data rows written. Extra keyword args are forwarded tocsv.DictWriter(delimiter,dialect,quoting, ...).write_path(path, fieldnames, mapping, *, encoding="utf-8", ...) -> intOpenpath(withnewline=""), write, and close.writes(fieldnames, mapping, ...) -> strReturn 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6d3e2b02cf7c05b04d45b66dd932552e2ac49b17a18208b8256a1640768605f2
|
|
| MD5 |
a29dce9a975596d6887e102f9af87b19
|
|
| BLAKE2b-256 |
c65fa6af928d45097c369b2ec70c25b47306a2a2d032fdaf96e246cda27ab6de
|
File details
Details for the file basic_csv_writer-0.1.1-py3-none-any.whl.
File metadata
- Download URL: basic_csv_writer-0.1.1-py3-none-any.whl
- Upload date:
- Size: 10.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58374864916e01c47969e6cf60da8dfce834898ade2f82c1e90605a35eb6a98c
|
|
| MD5 |
27406ad4a841d6bd9d9333ea4287b742
|
|
| BLAKE2b-256 |
9d7cdd9909f34db9c98b56bc8170f829c5b64ce1a258c6b12d9486a3140cf9af
|