csv
import csv
A complete, RFC 4180-compliant library for reading and writing Comma-Separated Values (CSV) data.
This module implements the format defined in RFC 4180: “Common Format and MIME Type for Comma-Separated Values (CSV) Files”: and extends it with the practical accommodations required by real-world CSV data, including configurable delimiters, optional quoting strategies, BOM detection, and lenient parsing of files that deviate from the strict spec.
Key CSV rules
- Records are separated by CRLF (
\r\n). The last record may omit the trailing line break. - Fields containing the delimiter, a double-quote, CR, or LF must be enclosed in double-quotes. - A double-quote inside a quoted field is escaped by doubling it (""). - An optional header record may appear as the first line. - All records must contain the same number of fields.
Extensions beyond RFC 4180
- Configurable delimiter: any single character (tab for
TSV, pipe, etc.) - Configurable quote character: defaults to
". - LF-only and CR-only line endings: accepted on input; CRLF on output. - BOM stripping: UTF-8 BOM (
\xEF\xBB\xBF) is silently removed. - Empty last line: a trailing newline after the last record is ignored. - Quoting strategies:QUOTE_MINIMAL(default),QUOTE_ALL,QUOTE_NONNUMERIC,QUOTE_NONE. - Lenient mode: tolerate unquoted fields containing the delimiter and other minor deviations common in real-world CSV files.
Quick start
Reading
import csv
# Parse a CSV string directly.
var table = csv.parse('name,age\r\nAlice,30\r\nBob,25')
echo table[0] # { name: 'Alice', age: '30' }
echo table[1] # { name: 'Bob', age: '25' }
# Stream a file row by row.
var reader = csv.Reader(file('data.csv'))
for row in reader {
echo row
}
reader.close()
Writing
import csv
# Encode a list of dicts to a CSV string.
var data = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
]
echo csv.stringify(data)
# name,age\r\nAlice,30\r\nBob,25\r\n
# Stream rows to a file.
var writer = csv.Writer(file('out.csv', 'w'))
writer.write_header(['name', 'age'])
writer.write_row(['Alice', 30])
writer.write_row(['Bob', 25])
writer.close()
The csv API
Every public name in csv, wherever it is declared. Each links to the
page that documents it.
| Name | Kind | Summary |
|---|---|---|
csv.CsvError | class | Error raised when a CSV parsing or encoding error is encountered. |
csv.Dialect | class | Encapsulates the formatting parameters that govern how CSV data is read or written. |
csv.QUOTE_ALL | constant | Quote every field unconditionally, regardless of content. |
csv.QUOTE_MINIMAL | constant | Quote only fields that contain the delimiter, the quote character, or a line break. |
csv.QUOTE_NONE | constant | Never quote fields. |
csv.QUOTE_NONNUMERIC | constant | Quote all non-numeric fields. |
csv.Reader | class | An incremental, row-by-row CSV reader that wraps a Zuri file object (or any object implementing .read()… |
csv.Writer | class | An incremental, row-by-row CSV writer that wraps a Zuri file object (or any object implementing .write()… |
csv.format_record | function | Encodes a single list of values as one CSV record and returns the string (including the line terminator). |
csv.parse | function | Parses a CSV string and returns all records as a list. |
csv.parse_record | function | Parses a single CSV record string and returns the fields as a list. |
csv.read_file | function | Reads a CSV file and returns all records as a list. |
csv.sniff_dialect | function | Attempts to detect the field delimiter used in sample and returns a Dialect configured with it. |
csv.stringify | function | Encodes a list of records into a CSV string and returns it. |
csv.write_file | function | Encodes a list of records and writes them to a CSV file, creating or overwriting the file at path. |
Constants
QUOTE_MINIMAL
csv.QUOTE_MINIMAL = 0
Quote only fields that contain the delimiter, the quote character, or a line break. This is the default and produces the most compact output. Compliant with RFC 4180 §2 rule 6.
QUOTE_ALL
csv.QUOTE_ALL = 1
Quote every field unconditionally, regardless of content. Produces the most portable output; useful when the consumer is known to require quoted fields.
QUOTE_NONNUMERIC
csv.QUOTE_NONNUMERIC = 2
Quote all non-numeric fields. Fields whose string representation is a valid integer or floating-point number are written unquoted.
QUOTE_NONE
csv.QUOTE_NONE = 3
Never quote fields. If a field contains the delimiter or a line break, a
CsvError is raised (in strict mode) or the character is written as-is
(in lenient mode). Use only when you are certain the data contains no
special characters.
Functions
sniff_dialect()
csv.sniff_dialect(sample, candidate_delimiters) -> Dialect
Attempts to detect the field delimiter used in sample and returns a
Dialect configured with it.
Each candidate delimiter is scored by how consistently it occurs across
the sample’s lines: a real delimiter tends to appear the same number of
times on every line, while one that just happens to show up in the data
(a comma inside a sentence, say) doesn’t. The best-scoring candidate
wins; if none of them appear consistently anywhere in sample, the
first candidate (, by default) is used.
This only detects the delimiter: not the quote character, whether a
header row is present, or any other Dialect field. Set those on the
returned Dialect yourself if they differ from the defaults.
Example,
%> var dialect = csv.sniff_dialect("a;b;c\n1;2;3\n4;5;6")
%> dialect.delimiter
';'
Parameters
sample(string) — Doesn’t need to be the whole file: a few representative lines are enough.candidate_delimiters(?list) — Default[',', '\t', ';', '|'].
Returns Dialect
parse()
csv.parse(text, dialect) -> list
Parses a CSV string and returns all records as a list.
When dialect.has_header is true (or has_header is passed as
true), the first record is treated as a header and every subsequent
record is returned as a dict keyed by column name. Otherwise, records
are returned as lists of strings.
This function is the simplest way to parse a complete CSV document that already resides in memory.
Examples
import csv
# No header: returns list of lists.
var rows = csv.parse('a,b,c\r\n1,2,3\r\n4,5,6')
echo rows[0] # ['a', 'b', 'c']
echo rows[1] # ['1', '2', '3']
# With header: returns list of dicts.
var d = csv.Dialect()
d.has_header = true
rows = csv.parse('name,age\r\nAlice,30\r\nBob,25', d)
echo rows[0] # { name: 'Alice', age: '30' }
echo rows[1] # { name: 'Bob', age: '25' }
# Tab-separated, no header.
var td = csv.Dialect()
td.delimiter = '\t'
rows = csv.parse('x\t1\ny\t2', td)
echo rows[0] # ['x', '1']
Parameters
text(string) — The CSV text to parse.dialect(?Dialect) — Optional dialect (defaults toDialect()).
Returns list — List of lists or dicts depending on has_header.
Raises CsvError On parse error in strict mode.
stringify()
csv.stringify(rows, dialect, keys) -> string
Encodes a list of records into a CSV string and returns it.
Each element of rows may be either a list (field values in order) or a
dict. When rows contains dicts, keys controls the column order; if
omitted, the keys of the first dict determine the order. A header row is
written automatically when rows contains dicts.
Examples
import csv
# From lists: no header.
echo csv.stringify([['Alice', 30], ['Bob', 25]])
# Alice,30\r\nBob,25\r\n
# From dicts: header inferred.
echo csv.stringify([
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
])
# name,age\r\nAlice,30\r\nBob,25\r\n
# Custom dialect.
var d = csv.Dialect()
d.quoting = csv.QUOTE_ALL
echo csv.stringify([['hello, world', 42]], d)
# "hello, world","42"\r\n
Parameters
rows(list) — List of lists or dicts to encode.dialect(?Dialect) — Optional dialect (defaults toDialect()).keys(?list) — Optional column key order whenrowsare dicts.
Returns string — The encoded CSV text.
Raises CsvError On encoding error.
parse_record()
csv.parse_record(line, dialect) -> list
Parses a single CSV record string and returns the fields as a list.
This is a lightweight utility for parsing a single line when you already have it isolated (e.g. from a line-buffered reader). No record separator detection is performed.
import csv
csv.parse_record('"hello, world",42,true')
# ['hello, world', '42', 'true']
csv.parse_record('"He said ""hi""",ok')
# ['He said "hi"', 'ok']
Parameters
line(string) — A single CSV record (with or without line ending).dialect(?Dialect) — Optional dialect.
Returns list — The parsed field values.
Raises CsvError On parse error.
format_record()
csv.format_record(row, dialect) -> string
Encodes a single list of values as one CSV record and returns the string (including the line terminator).
import csv
csv.format_record(['Alice', 'New York, NY', 'She said "hello"'])
# 'Alice,"New York, NY","She said ""hello"""\r\n'
Parameters
row(list) — Field values to encode.dialect(?Dialect) — Optional dialect.
Returns string — The encoded record with line terminator.
Raises CsvError On encoding error.
read_file()
csv.read_file(path, dialect) -> list
Reads a CSV file and returns all records as a list.
Convenience wrapper around Reader that opens, reads, and closes in one
call. Use Reader directly when you need streaming or finer control.
import csv
var d = csv.Dialect()
d.has_header = true
var rows = csv.read_file('employees.csv', d)
echo rows[0]['name']
Parameters
path(string) — Filesystem path to the CSV file.dialect(?Dialect) — Optional dialect.
Returns list — All records as lists or dicts.
Raises CsvError On parse error.
Raises Error On file I/O error.
write_file()
csv.write_file(path, rows, dialect, keys)
Encodes a list of records and writes them to a CSV file, creating or
overwriting the file at path.
Convenience wrapper around Writer. Use Writer directly when you need
streaming or finer control.
import csv
csv.write_file('out.csv', [
{ name: 'Alice', score: 98 },
{ name: 'Bob', score: 72 },
])
Parameters
path(string) — Filesystem path to write to.rows(list) — List of lists or dicts.dialect(?Dialect) — Optional dialect.keys(?list) — Optional column key order for dict rows.
Raises CsvError On encoding error.
Raises Error On file I/O error.
Classes
CsvError
class csv.CsvError < Error
Error raised when a CSV parsing or encoding error is encountered.
Fields
| Field | Type | Description |
|---|---|---|
line | ||
column |
Constructor
csv.CsvError(message, line, column)
Parameters
message(string) — Error description.line(number) — Line number (1-based).column(number) — Column number (1-based).
Dialect
class csv.Dialect
Encapsulates the formatting parameters that govern how CSV data is read
or written. Pass a Dialect instance to Reader or Writer to
override the defaults.
Example: tab-separated values (TSV)
import csv
var tsv = csv.Dialect()
tsv.delimiter = '\t'
var reader = csv.Reader(file, tsv)
Example: pipe-delimited, quote-all
var d = csv.Dialect()
d.delimiter = '|'
d.quoting = csv.QUOTE_ALL
var writer = csv.Writer(file, d)
Fields
| Field | Type | Description |
|---|---|---|
delimiter | string | The single-character field delimiter. |
quote_char | string | The character used to quote fields. |
line_terminator | string | The line terminator written between records. |
quoting | number | Quoting strategy. |
trim | bool | When true (default), leading and trailing whitespace is stripped from unquoted fields during reading. |
lenient | bool | When true, the parser accepts minor deviations from RFC 4180 such as a quote character that appears in the… |
has_header | bool | When true, reading expects the first record to be a header row and returns subsequent records as dicts… |
max_field_size | number | Maximum number of characters allowed in a single field. |
Dialect.validate()
csv.Dialect.validate()
Validates that all dialect fields hold consistent, legal values.
Raises CsvError If any field is invalid.
Reader
class csv.Reader
An incremental, row-by-row CSV reader that wraps a Zuri file object
(or any object implementing .read() and .close()).
Reader implements the iterator protocol so it can be used directly in
a for loop.
Example: reading a file with a header row
import csv
import io
var d = csv.Dialect()
d.has_header = true
var reader = csv.Reader(file('employees.csv'), d)
for row in reader {
echo row['name'] + ' earns ' + row['salary']
}
reader.close()
Example: tab-separated file, no header
import csv
import io
var d = csv.Dialect()
d.delimiter = '\t'
var reader = csv.Reader(file('data.tsv'), d)
for row in reader {
echo row # list: ['field1', 'field2', ...]
}
reader.close()
Notes
- The file is read all at once into memory. For very large
files (> a few hundred MB), consider streaming line by line using
read_record(). - Whendialect.has_headeristrue, the first record is consumed as the header and is accessible via theheadersproperty. - When
dialect.has_headeristrueand a data row has fewer fields than the header, the missing fields are set tonil. - Whendialect.has_headeristrueand a row has more fields than the header, the extra fields are silently discarded (lenient mode) or raiseCsvError(strict mode).
Fields
| Field | Type | Description |
|---|---|---|
dialect | Dialect | The dialect governing parsing behaviour. |
headers | list | The header row, as a list of strings, once the first record has been consumed. |
record_num | number | The 1-based index of the most recently returned record (not counting the header row). |
Constructor
csv.Reader(file, dialect)
Creates a Reader from a file object.
Parameters
file(file) — An open, readable Zuri file object.dialect(?Dialect) — Optional dialect (defaults toDialect()).
Raises CsvError If dialect validation fails.
Reader.read_record()
csv.Reader.read_record() -> list
Reads and returns the next record, or nil when all records have been
consumed.
When dialect.has_header is true, returns a dict keyed by header
name. Otherwise returns a list of field strings.
Returns list — | dict | nil
Raises CsvError On parse error in strict mode.
Reader.read_all()
csv.Reader.read_all() -> list
Reads all remaining records into a list and returns it.
Returns list — A list of lists (or dicts when dialect.has_header
is true).
Reader.close()
csv.Reader.close()
Closes the underlying file object. Always call close() when done, even
if an error occurred.
Writer
class csv.Writer
An incremental, row-by-row CSV writer that wraps a Zuri file object
(or any object implementing .write() and .close()).
Example: writing dicts (with header)
import csv
import io
var writer = csv.Writer(file('out.csv', 'w'))
writer.write_header(['id', 'name', 'score'])
writer.write_row([1, 'Alice', 98.5])
writer.write_row([2, 'Bob', 72.0])
writer.close()
Example: QUOTE_ALL with tab delimiter
import csv
import io
var d = csv.Dialect()
d.delimiter = '\t'
d.quoting = csv.QUOTE_ALL
var writer = csv.Writer(file('out.tsv', 'w'), d)
writer.write_row(['Alice', 'Engineer', 'New York'])
writer.close()
Notes
write_row()accepts a list of any values; each is converted to a string with.to_string()before encoding. -write_dict()accepts a dict and akeyslist that controls the field order and which keys are written. - The writer does not buffer: every call towrite_row()immediately writes to the underlying file.
Fields
| Field | Type | Description |
|---|---|---|
dialect | Dialect | The dialect governing encoding behaviour. |
record_num | number | The number of records written so far (not counting the header row). |
Constructor
csv.Writer(file, dialect)
Creates a Writer targeting a file object.
Parameters
file(file) — An open, writable Zuri file object.dialect(?Dialect) — Optional dialect (defaults toDialect()).
Raises CsvError If dialect validation fails.
Writer.write_row()
csv.Writer.write_row(row)
Encodes and writes a single record from a list of values.
Each element of row is converted to a string via .to_string() and
then encoded according to the dialect’s quoting strategy.
Parameters
row(list) — The field values for this record.
Raises CsvError On encoding error (e.g. QUOTE_NONE with special
chars).
Writer.write_header()
csv.Writer.write_header(headers)
Encodes and writes a header record from a list of column name strings.
Functionally identical to write_row() but does not increment
record_num, making it semantically clear that this is metadata.
Parameters
headers(list) — The column names.
Raises CsvError On encoding error.
Writer.write_dict()
csv.Writer.write_dict(row, keys)
Encodes and writes a single record from a dict, using keys to
determine field order. Missing keys produce empty fields.
writer.write_dict({ name: 'Alice', age: 30 }, ['name', 'age'])
Parameters
row(dict) — The record as a key-value dict.keys(list) — Ordered list of keys to extract fromrow.
Raises CsvError On encoding error.
Writer.write_dicts()
csv.Writer.write_dicts(rows, keys)
Writes a list of records from a list of dicts, preceded by a header row whose columns are derived from the keys of the first record.
If keys is provided it controls column order; otherwise the keys of
the first row are used in their natural iteration order.
writer.write_dicts([
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
])
Parameters
rows(list) — List of dicts, each representing one record.keys(?list) — Optional ordered key list. Inferred from the first row when omitted.
Raises CsvError On encoding error.
Writer.close()
csv.Writer.close()
Closes the underlying file object. Always call close() when done, even
if an error occurred.
2026, Richard Ore and The Zuri Contributors