Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

NameKindSummary
csv.CsvErrorclassError raised when a CSV parsing or encoding error is encountered.
csv.DialectclassEncapsulates the formatting parameters that govern how CSV data is read or written.
csv.QUOTE_ALLconstantQuote every field unconditionally, regardless of content.
csv.QUOTE_MINIMALconstantQuote only fields that contain the delimiter, the quote character, or a line break.
csv.QUOTE_NONEconstantNever quote fields.
csv.QUOTE_NONNUMERICconstantQuote all non-numeric fields.
csv.ReaderclassAn incremental, row-by-row CSV reader that wraps a Zuri file object (or any object implementing .read()…
csv.WriterclassAn incremental, row-by-row CSV writer that wraps a Zuri file object (or any object implementing .write()…
csv.format_recordfunctionEncodes a single list of values as one CSV record and returns the string (including the line terminator).
csv.parsefunctionParses a CSV string and returns all records as a list.
csv.parse_recordfunctionParses a single CSV record string and returns the fields as a list.
csv.read_filefunctionReads a CSV file and returns all records as a list.
csv.sniff_dialectfunctionAttempts to detect the field delimiter used in sample and returns a Dialect configured with it.
csv.stringifyfunctionEncodes a list of records into a CSV string and returns it.
csv.write_filefunctionEncodes 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 to Dialect()).

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 to Dialect()).
  • keys (?list) — Optional column key order when rows are 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

FieldTypeDescription
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

FieldTypeDescription
delimiterstringThe single-character field delimiter.
quote_charstringThe character used to quote fields.
line_terminatorstringThe line terminator written between records.
quotingnumberQuoting strategy.
trimboolWhen true (default), leading and trailing whitespace is stripped from unquoted fields during reading.
lenientboolWhen true, the parser accepts minor deviations from RFC 4180 such as a quote character that appears in the…
has_headerboolWhen true, reading expects the first record to be a header row and returns subsequent records as dicts…
max_field_sizenumberMaximum 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(). - When dialect.has_header is true, the first record is consumed as the header and is accessible via the headers property.
  • When dialect.has_header is true and a data row has fewer fields than the header, the missing fields are set to nil. - When dialect.has_header is true and a row has more fields than the header, the extra fields are silently discarded (lenient mode) or raise CsvError (strict mode).

Fields

FieldTypeDescription
dialectDialectThe dialect governing parsing behaviour.
headerslistThe header row, as a list of strings, once the first record has been consumed.
record_numnumberThe 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 to Dialect()).

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 a keys list that controls the field order and which keys are written. - The writer does not buffer: every call to write_row() immediately writes to the underlying file.

Fields

FieldTypeDescription
dialectDialectThe dialect governing encoding behaviour.
record_numnumberThe 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 to Dialect()).

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 from row.

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