Card 04/ 09
Template Method Example: Importing CSV and JSON Files
Open, parse, validate and store, where only parse differs per format.
An importer for several file formats is a natural Template Method, and this example maps the template onto it. Every import opens its source, parses it into rows, keeps the rows that pass validation, and stores them. Only parsing differs between CSV and JSON, and the JSON importer also overrides the validation hook to reject a row with no email.
| Template | Importer example |
|---|---|
| AbstractClass | Importer |
| Template method | run, marked @final |
| Abstract step | parse |
| Steps with a default | open and store, which subclasses rarely change |
| Hook | validate, which accepts every row unless overridden |
| ConcreteClass | CsvImporter and JsonImporter |
import csv
import io
import json
from abc import ABC, abstractmethod
from typing import final
Row = dict[str, str]
class Importer(ABC):
def __init__(self) -> None:
self.stored: list[Row] = []
@final
def run(self, source: str) -> None:
text = self.open(source)
rows = self.parse(text)
valid = [row for row in rows if self.validate(row)]
self.store(valid)
print(f"{type(self).__name__}: {len(rows)} read, {len(valid)} stored")
def open(self, source: str) -> str:
return source.strip()
@abstractmethod
def parse(self, text: str) -> list[Row]: ...
def validate(self, row: Row) -> bool:
return True
def store(self, rows: list[Row]) -> None:
self.stored.extend(rows)
class CsvImporter(Importer):
def parse(self, text: str) -> list[Row]:
return list(csv.DictReader(io.StringIO(text)))
class JsonImporter(Importer):
def parse(self, text: str) -> list[Row]:
return json.loads(text)
def validate(self, row: Row) -> bool:
return bool(row.get("email"))
CsvImporter().run("""
name,email
Sam,[email protected]
Ali,[email protected]
""")
JsonImporter().run("""
[{"name": "Kim", "email": "[email protected]"}, {"name": "Lee", "email": ""}]
""")CsvImporter: 2 read, 2 stored
JsonImporter: 2 read, 1 storedThe CSV import reads and stores two rows. The JSON import reads two and stores one, because its validate rejects Lee, whose email is empty. CsvImporter is three lines long: it writes the one step that differs and inherits the order, the opening and the storing.
The template method is also where a rule for every import belongs. Logging, a count of rejected rows, or a transaction around store would each be one edit to run, and every importer, including ones not yet written, would follow it.
← → move through sections and cards