Note
Go to the end to download the full example code.
Reading files with SectionFile¶
This example demonstrates how to use Section and SectionFile to read
and write text files organized in sequential sections.
Unlike BlockFile (which searches for blocks by start pattern),
SectionFile processes sections in a fixed order: each section reads what
it needs from the character stream and passes control to the next one.
Defining the sections¶
Each Section subclass implements read() and write() to
process its part of the file. Reading is sequential – the first
section reads the beginning of the file, the second reads the
continuation, and so on.
import tempfile
from io import StringIO
from pathlib import Path
from typing import IO, Any
from cfinterface.components import Section
from cfinterface.files import SectionFile
class HeaderSection(Section):
"""Header section: reads key=value pairs until a blank line."""
def __init__(self, previous=None, next=None, data=None) -> None:
super().__init__(previous, next, data)
def read(self, file: IO[Any], *args: Any, **kwargs: Any) -> bool:
metadata: dict[str, str] = {}
while True:
line = file.readline()
if not line or line.strip() == "":
break
if "=" in line:
key, _, value = line.partition("=")
metadata[key.strip()] = value.strip()
self.data = metadata
return True
def write(self, file: IO[Any], *args: Any, **kwargs: Any) -> bool:
for key, value in (self.data or {}).items():
file.write(f"{key} = {value}\n")
file.write("\n")
return True
def __eq__(self, o: object) -> bool:
if not isinstance(o, HeaderSection):
return False
return o.data == self.data
class TableSection(Section):
"""Table section: reads data lines separated by '|' until EOF."""
def __init__(self, previous=None, next=None, data=None) -> None:
super().__init__(previous, next, data)
def read(self, file: IO[Any], *args: Any, **kwargs: Any) -> bool:
records: list[list[str]] = []
header: list[str] | None = None
while True:
line = file.readline()
if not line:
break
line = line.strip()
if not line:
continue
columns = [c.strip() for c in line.split("|")]
if header is None:
header = columns
else:
records.append(columns)
self.data = {"header": header or [], "records": records}
return True
def write(self, file: IO[Any], *args: Any, **kwargs: Any) -> bool:
data = self.data or {"header": [], "records": []}
header = data.get("header", [])
records = data.get("records", [])
if header:
file.write(" | ".join(header) + "\n")
for rec in records:
file.write(" | ".join(rec) + "\n")
return True
def __eq__(self, o: object) -> bool:
if not isinstance(o, TableSection):
return False
return o.data == self.data
Defining the file model¶
The SectionFile subclass declares the list of sections in SECTIONS
in the order they appear in the file. Convenience properties expose
each section by type via data.get_sections_of_type().
class MyTabularFile(SectionFile):
SECTIONS = [HeaderSection, TableSection]
@property
def header(self) -> HeaderSection | None:
return self.data.get_sections_of_type(HeaderSection)
@property
def table(self) -> TableSection | None:
return self.data.get_sections_of_type(TableSection)
Writing sample data and reading it back¶
We use a temporary file with known content to demonstrate the complete read cycle. The first section (header) consumes the key=value pairs followed by a blank line; the second section (table) consumes the remainder of the file.
EXAMPLE_CONTENT = (
"titulo = Relatorio de Vendas\n"
"responsavel = maria.souza\n"
"periodo = 2025-Q1\n"
"\n"
"produto | quantidade | receita\n"
"Widget A | 1200 | 59880.00\n"
"Widget B | 850 | 127500.00\n"
"Widget C | 430 | 21500.00\n"
"Servico X | 60 | 18000.00\n"
)
with tempfile.TemporaryDirectory() as tmpdir:
path = Path(tmpdir) / "report.txt"
path.write_text(EXAMPLE_CONTENT, encoding="utf-8")
file_obj = MyTabularFile.read(str(path))
# Inspect the header
hdr = file_obj.header
print("=== Header ===")
for key, value in hdr.data.items():
print(f" {key}: {value}")
# Inspect the table
tab = file_obj.table
header_cols = tab.data["header"]
records = tab.data["records"]
print(f"\n=== Table ({len(records)} records) ===")
print(" " + " | ".join(header_cols))
print(" " + "-" * 50)
for rec in records:
print(" " + " | ".join(rec))
=== Header ===
titulo: Relatorio de Vendas
responsavel: maria.souza
periodo: 2025-Q1
=== Table (4 records) ===
produto | quantidade | receita
--------------------------------------------------
Widget A | 1200 | 59880.00
Widget B | 850 | 127500.00
Widget C | 430 | 21500.00
Servico X | 60 | 18000.00
Round-trip via in-memory buffer¶
SectionFile.write() accepts an IO object in addition to a path.
Here we write to a StringIO and confirm that the content was preserved.
with tempfile.TemporaryDirectory() as tmpdir:
path = Path(tmpdir) / "report.txt"
path.write_text(EXAMPLE_CONTENT, encoding="utf-8")
file_obj2 = MyTabularFile.read(str(path))
buffer = StringIO()
file_obj2.write(buffer)
output_content = buffer.getvalue()
input_lines = EXAMPLE_CONTENT.strip().splitlines()
output_lines = output_content.strip().splitlines()
print(
f"\nRound-trip: {len(input_lines)} input lines -> "
f"{len(output_lines)} output lines"
)
Round-trip: 9 input lines -> 9 output lines
Iterating over all sections¶
SectionData is iterable: iterating over file_obj.data returns all
sections in the order they were read, including the default
DefaultSection that occupies index 0.
with tempfile.TemporaryDirectory() as tmpdir:
path = Path(tmpdir) / "report.txt"
path.write_text(EXAMPLE_CONTENT, encoding="utf-8")
file_obj3 = MyTabularFile.read(str(path))
print("\n=== All sections ===")
for sec in file_obj3.data:
print(f" {type(sec).__name__}")
=== All sections ===
DefaultSection
HeaderSection
TableSection
Total running time of the script: (0 minutes 0.004 seconds)