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
Metadata
Release files for basic-csv-writer 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| basic_csv_writer-0.1.1.tar.gz | 11.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| basic_csv_writer-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 22.2 kB
Release files / basic_csv_writer-0.1.1.tar.gz
| Download URL | basic_csv_writer-0.1.1.tar.gz |
|---|---|
| Size | 11.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6d3e2b02cf7c05b04d45b66dd932552e2ac49b17a18208b8256a1640768605f2
|
|
BLAKE2b-256 checksum How to use checksums |
c65fa6af928d45097c369b2ec70c25b47306a2a2d032fdaf96e246cda27ab6de
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / basic_csv_writer-0.1.1-py3-none-any.whl
| Download URL | basic_csv_writer-0.1.1-py3-none-any.whl |
|---|---|
| Size | 10.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
58374864916e01c47969e6cf60da8dfce834898ade2f82c1e90605a35eb6a98c
|
|
BLAKE2b-256 checksum How to use checksums |
9d7cdd9909f34db9c98b56bc8170f829c5b64ce1a258c6b12d9486a3140cf9af
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Security for basic-csv-writer 0.1.1
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.