How to Convert CSV to PDF with Python
This Python example sends CSV data to the pdfRest Convert to PDF API Tool and creates a styled, tagged PDF table. The conversion profile controls column widths, alignment, header formatting, row shading, borders, padding, page setup, and repeated headers.
Why Convert CSV to PDF with Python?
Python data workflows produce CSV at nearly every stage because it is simple to generate and analyze. When results are delivered to auditors, clients, or project leaders, a designed PDF table can communicate the dataset more clearly and preserve a consistent reporting artifact.
A data-quality pipeline might write rule names, failure descriptions, and affected-record counts to CSV after validating an import. Python can immediately convert that output into a PDF for the review package. The description column receives extra room, counts align to the right, and alternating fills make a long exception list easier to scan.
Keeping this step in the pipeline eliminates manual spreadsheet formatting and makes every run reproducible. The source remains available for analysis, while the PDF serves the human review and documentation need.
Python Code Example for Converting CSV to PDF
import json
import os
import requests
# By default, we use the US-based API service. This is the primary endpoint for global use.
api_url = "https://api.pdfrest.com"
# For GDPR compliance and enhanced performance for European users, you can switch to the EU-based service by uncommenting the URL below.
# For more information visit https://pdfrest.com/pricing#how-do-eu-gdpr-api-calls-work
#api_url = "https://eu-api.pdfrest.com"
# This sample converts CSV input to a tagged PDF through multipart /pdf.
# It demonstrates structured_text_options and the format-specific conversion options.
input_path = "/path/to/sample.csv"
options = json.loads(r'''{
"title": "Structured Content Sample",
"language": "en-US",
"enable_tagging": true,
"page_setup": {
"size": "Letter",
"orientation": "portrait",
"margin": {
"top": 36,
"right": 42,
"bottom": 36,
"left": 42
}
},
"style": {
"font": "Arial",
"heading_font": "Arial",
"code_font": "Courier",
"text_size": 11,
"text_color_rgb": [
34,
34,
34
],
"heading_scale": 1.35,
"table": {
"column_width_weights": [
2,
3,
2
],
"keep_header_with_first_row": true,
"repeat_headers_on_overflow": true,
"show_borders": true,
"border_width": 0.75,
"border_color_rgb": [
180,
188,
200
],
"header_fill_color_rgb": [
33,
64,
98
],
"header_text_color_rgb": [
255,
255,
255
],
"row_fill_color_rgb": [
250,
250,
252
],
"alternate_row_fill_color_rgb": [
235,
240,
246
],
"cell_padding": {
"top": 6,
"right": 8,
"bottom": 6,
"left": 8
}
}
},
"csv": {
"first_row_is_header": true,
"delimiter": ",",
"columns": [
{
"index": 0,
"width_weight": 2,
"text_align": "left"
},
{
"index": 1,
"width_weight": 3,
"text_align": "left"
},
{
"index": 2,
"width_weight": 1,
"text_align": "right"
}
]
}
}''')
from requests_toolbelt import MultipartEncoder
with open(input_path, "rb") as input_file:
fields = {
"file": (os.path.basename(input_path), input_file, "text/plain"),
"structured_text_options": json.dumps(options),
}
form = MultipartEncoder(fields=fields)
response = requests.post(api_url + "/pdf", data=form, headers={
"Accept": "application/json",
"Content-Type": form.content_type,
"Api-Key": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
})
print("Response status code: " + str(response.status_code))
if response.ok:
print(json.dumps(response.json(), indent=2))
else:
print(response.text)
raise SystemExit(1)
Source: View the sample on GitHub
Breaking Down the Code
The Python CSV sample uses requests and MultipartEncoder from requests-toolbelt. Install both with python -m pip install requests requests-toolbelt when they are not already present.
import json import os import requests
This Python endpoint excerpt identifies the regional service used for CSV conversion:
# By default, we use the US-based API service. This is the primary endpoint for global use. api_url = "https://api.pdfrest.com" # For GDPR compliance and enhanced performance for European users, you can switch to the EU-based service by uncommenting the URL below. # For more information visit https://pdfrest.com/pricing#how-do-eu-gdpr-api-calls-work #api_url = "https://eu-api.pdfrest.com"
Python opens the .csv input in binary mode and sends its base filename with the stream. /pdf uses that filename extension to infer the CSV input type.
# It demonstrates structured_text_options and the format-specific conversion options.
input_path = "/path/to/sample.csv"
options = json.loads(r'''{
The options dictionary becomes pdfRest’s structured_text_options field. It supplies document metadata, explicitly requests tags, establishes Letter portrait pages and 36-point margins, and selects the fonts, text size, color, and CSV-specific presentation values.
{
"csv": {
"first_row_is_header": true,
"delimiter": ",",
"columns": [
{
"index": 0,
"width_weight": 2,
"text_align": "left"
},
{
"index": 1,
"width_weight": 3,
"text_align": "left"
},
{
"index": 2,
"width_weight": 1,
"text_align": "right"
}
]
}
}
first_row_is_header defaults to true; setting it to false renders every record as a body row. delimiter defaults to a comma and must contain exactly one character, so a tab or semicolon can be selected but a multi-character separator cannot.
Column indexes are zero-based. Unspecified columns default to weight 1 and left alignment. Width weights are positive relative proportions, not points or pixels, and text_align accepts only left, center, or right. An index outside the parsed CSV, a nonpositive weight, or another alignment value produces a validation error.
The per-column CSV weights take precedence over style.table.column_width_weights; the broader table list is only a fallback when CSV-specific widths are absent. The remaining table style controls borders, colors, row striping, and padding. keep_header_with_first_row prevents a header from appearing alone, and repeat_headers_on_overflow repeats it as rows continue onto later pages.
For the CSV call, MultipartEncoder combines the file tuple with serialized structured_text_options. Its content_type, including the boundary, becomes the request’s Content-Type header.
with open(input_path, "rb") as input_file:
fields = {
"file": (os.path.basename(input_path), input_file, "text/plain"),
"structured_text_options": json.dumps(options),
}
form = MultipartEncoder(fields=fields)
response = requests.post(api_url + "/pdf", data=form, headers={
"Accept": "application/json",
"Content-Type": form.content_type,
"Api-Key": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
})
requests.post sends the encoded CSV form with the API key. The script pretty-prints resource JSON after success and otherwise displays the API message before exiting with a nonzero status.
"file": (os.path.basename(input_path), input_file, "text/plain"),
"structured_text_options": json.dumps(options),
}
form = MultipartEncoder(fields=fields)
response = requests.post(api_url + "/pdf", data=form, headers={
"Accept": "application/json",
"Content-Type": form.content_type,
"Api-Key": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
})
print("Response status code: " + str(response.status_code))
if response.ok:
print(json.dumps(response.json(), indent=2))
Beyond the Tutorial
Teams can reuse the pattern for experiment results, validation summaries, survey data, and scheduled KPI extracts. A different columns array can adapt the table to each schema without replacing the overall report design.
Tagged structure can make headers and cells more understandable to assistive and automated tools, but additional evaluation is needed for any accessibility claim. Validate representative files in API Lab and review the Convert to PDF reference for all available settings.