CategoryPython

From CSV to Herma 8632 PDF file

Our first born is about to arrive. Instead of writing out all the birth announcements by hand I decided to write a little python script that creates a PDF that matches sticker sheets (Herma 8632 format). It exactly took me one laptop battery charge to create this script. I refactored a bit later to be a bit more OO.


#!/usr/bin/env python3

from fpdf import FPDF
import sys
import os

# Very simple CSV to Herma 8632

# CSV expected in following format (comma separated , WITH header):
#
#   Name, Relation, Street + number, ZIP, City, Country
#   John Doe, friends, Stationstraat 12, 1234 DD, Duckcity, NL


def main():

  args = InputParser(sys.argv)

  addresses = CSVParser(args.inputFile)
  hermaTemplate = Herma()

  hermaTemplate.drawAddressesOnHerma(addresses.data)
  hermaTemplate.save(args.outputFile)

# ------------------------------------------------------------------

## Class Definitions
class InputParser:
  def __init__(self, args) -> None:
    self.args = args
    if len(args) != 3:
      self.help()
      exit(1)

    self.input = args[1]
    self.output = args[2]

    if not os.path.exists(self.input):
      print(f"Inputfile \"{self.input}\" does not exist!")
      exit(1)

  def help(self):
    print(f"Use python3 {self.args[0]} input.csv output.pdf")

  @property
  def inputFile(self):
    return self.input

  @property
  def outputFile(self):
    return self.output

class CSVParser:
  def __init__(self, filename) -> None:
    self.addresses = []
    self.filename = filename
    self.parse()

  def parse(self):
    # TODO: File present checking
    with open(self.filename) as f:
      data = f.readlines()
      for d in data[1:]:
        split = d.split(',')
        address = "\n".join([split[0], split[2], split[3], split[4], split[5]])
        self.addresses.append(address)

  @property
  def data(self):
    return self.addresses

class Herma:
  def __init__(self) -> None:
    self.pdf = FPDF()
    self.pdf.add_page()
    self.pdf.set_auto_page_break(False)
    self.pdf.set_font("Times", size = 10)

    self.CELL_WIDTH = 63.5
    self.CELL_HEIGHT = 38.1
    self.LEFT_PAGE_MARGIN = 7.21
    self.TOP_PAGE_MARGIN = 15.15
    self.BETWEEN_CELL_SPACE = 2.54

    self.row = 0
    self.col = 0

    self.MAX_ROWS = 7
    self.MAX_COLS = 3

  def drawAddressesOnHerma(self, addresses):
    for a in addresses:
      x,y = self._selectCell(self.row, self.col)
      self._drawAddress(x, y, a)
      self._autoIncrement()


  def _drawAddress(self, x, y, address):
    self.pdf.set_xy(x, y+10)

    for l in address.split('\n'):
      self.pdf.set_x(x)
      self.pdf.cell(w = self.CELL_WIDTH, h = 5, ln = 1, txt = l, align= "C")


  def _selectCell(self, row, col):
    x = self.LEFT_PAGE_MARGIN + (col % self.MAX_COLS) * (self.BETWEEN_CELL_SPACE + self.CELL_WIDTH)
    y = self.TOP_PAGE_MARGIN + (row % self.MAX_ROWS * self.CELL_HEIGHT)

    self.pdf.set_xy(x, y)
    self.pdf.cell(w = self.CELL_WIDTH, h = self.CELL_HEIGHT, txt = "", border = 0)

    return x,y

  def _autoIncrement(self):
    self.col += 1

    if self.col % self.MAX_COLS == 0:
      self.row += 1
      if self.row % self.MAX_ROWS == 0:
        self.pdf.add_page()
        self.col = 0
        self.row = 0

  def save(self, filename):
    self.pdf.output(filename)
    print(f"Please find your file here: {filename}")

main()

Python3 custom logging module

I made a logging module based on the logging module of python itself.

It prints to file, and also in the console, and rotates the file automatically on a new start.

# Use it in your other modules as:
  # from Log import Log
  # logger = Log(__name__)

import logging
from logging import handlers
import os

LOG_LEVEL = logging.DEBUG

logger = logging.getLogger()
logger.setLevel(LOG_LEVEL)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

ch = logging.StreamHandler()
ch.setLevel(LOG_LEVEL)
ch.setFormatter(formatter)
logger.addHandler(ch)

filename = "./log/log.txt"
should_roll_over = os.path.isfile(filename)
rfh = handlers.RotatingFileHandler(filename, maxBytes=5 * 1000 * 1000, backupCount=20)
if should_roll_over:
  rfh.doRollover()
rfh.setLevel(LOG_LEVEL)
rfh.setFormatter(formatter)
logger.addHandler(rfh)


class Log():
  def __init__(self, name):
    self.logger = logging.getLogger(name)

    self.info(f"Instantiated logger for {name}")

  def critical(self, msg):
    self.logger.critical(msg)

  def fatal(self, msg):
    self.logger.fatal(msg)

  def error(self, msg):
    self.logger.error(msg)

  def warning(self, msg):
    self.logger.warning(msg)

  def warn(self, msg):
    self.logger.warn(msg)

  def info(self, msg):
    self.logger.info(msg)

  def debug(self, msg):
    self.logger.debug(msg)

© 2025 Roholt

Thema door Anders NorénOmhoog ↑