v1.9.9 - Sampler Reader and Writer Module Extraction (2025-12-29)#

What Changed?#

This release completes Phase 2 decomposition by extracting reader and writer functionality from the Sampler class into dedicated modules. The _reader.py module handles all event reading operations, while _writer.py manages CSV writing and high-level acquisition methods. Combined with previously extracted helpers and iterators, the Sampler is now fully modularized following the Single Responsibility Principle.


What’s New#

Event Reader Module (_reader.py)#

What it does: Extracts all event reading functionality into a dedicated EventReader class. Handles low-level device I/O and event parsing with three core methods for different use cases.

Methods extracted:

  1. read_event() -> RawEvent | None: Read one measurement from device with timestamp

    • Blocks until detector sends data

    • Records exact timestamp (to microsecond precision)

    • Returns RawEvent or None if line is invalid

    • Handles corrupted data gracefully

  2. stream_events(iterator) -> Iterator[RawEvent]: Generator yielding events one-at-a-time

    • Memory-efficient for large datasets

    • Automatically skips invalid lines with warnings

    • Yields only valid RawEvent objects

    • Pauses until caller asks for next event

  3. collect_events(iterator) -> list[RawEvent]: Collect all events into a list

    • Reads all measurements first, returns complete list

    • Uses stream_events internally

    • Simple interface for all-at-once processing

    • Loads everything into memory at once

Event Writer Module (_writer.py)#

What it does: Extracts all event writing functionality into a dedicated EventWriter class. Handles CSV file creation with both streaming and buffered modes, plus high-level acquisition methods.

Methods extracted:

  1. save_events(file_path, source): Write measurements to CSV file

    • Accepts Iterator or list[RawEvent] as source

    • Supports streaming mode (write as you go)

    • Supports buffered mode (collect first, then write)

    • Proper CSV formatting with timestamps and sensor values

  2. acquire_by_count(file_path, event_count): Collect and save fixed number of events

    • Reads exactly N measurements from detector

    • Shows progress bar (if enabled)

    • High-level API for fixed-count acquisition

    • Recommended for most use cases

  3. acquire_by_time(file_path, duration, sleep_interval): Collect and save for fixed duration

    • Collects measurements for exactly N seconds

    • Configurable polling interval

    • Shows progress bar (if enabled)

    • Used extensively in threshold scanning

Code examples:

from haniwers.v1.daq.sampler._reader import EventReader
from haniwers.v1.daq.sampler._writer import EventWriter
from haniwers.v1.daq.sampler._iterators import count_based_iterator
from pathlib import Path

# Create reader and writer
reader = EventReader(device, logger)
writer = EventWriter(reader, Path("./data"), stream_mode=True)

# High-level API (recommended)
writer.acquire_by_count(Path("./data/run.csv"), 1000)
writer.acquire_by_time(Path("./data/scan.csv"), duration=10.0, sleep_interval=0.1)

# Low-level API
event = reader.read_event()
iterator = count_based_iterator(100)
for event in reader.stream_events(iterator):
    process(event)

Installation#

Quick Start#

# Get the release
git checkout v1.9.9

# Setup
task env:setup

# Run CLI
haniwers-v1 --help

What’s Different from the Last Version?#

✅ Added#

  • New src/haniwers/v1/daq/sampler/_reader.py module with EventReader class (read_event, stream_events, collect_events)

  • New src/haniwers/v1/daq/sampler/_writer.py module with EventWriter class (save_events, acquire_by_count, acquire_by_time)

  • Comprehensive docstrings with usage examples for both new modules

  • Clean separation of reading and writing concerns following Single Responsibility Principle

🔧 Changed#

  • Reorganized src/haniwers/v1/daq/sampler/_base.py to coordinate with new reader/writer modules

  • No changes to public API or CLI functionality

🐛 Fixed#

  • No bug fixes in this release


Is It Safe to Upgrade?#

Backward Compatible: ✅ Yes

All existing imports and APIs continue to work without modification. The extraction of reader and writer functionality into dedicated modules is a pure refactoring with zero breaking changes. Sampler API remains identical.


Tests Passed#

  • ✅ Builds without errors

  • ✅ All 133 unit tests passing (DAQ module)

  • ✅ Sampler functionality tests passing (10 tests covering count/time/run modes)

  • ✅ MAC address tests passing (3 essential tests)

  • ✅ Reader module functionality verified

  • ✅ Writer module functionality verified

  • ✅ Pre-commit hooks passing (ruff format, YAML, etc.)


Release Details#

  • Date: 2025-12-29

  • Version: v1.9.9

  • Files Changed: 2 (created _reader.py, _writer.py)

  • Commits:

    • 03779ce: refactor(daq/sampler): extract event reading methods to _reader.py

    • 9345343: refactor(daq/sampler): extract event writing methods to _writer.py

    • bd9440b: bump: version 1.9.8 → 1.9.9


Next Steps#

Phase 2 Complete! Sampler Fully Modularized#

src/haniwers/v1/daq/sampler/
├── __init__.py           # Re-exports all classes
├── _base.py              # Sampler orchestration layer (1,591 lines → pending simplification)
├── _reader.py            # EventReader (read_event, stream_events, collect_events) ✓
├── _writer.py            # EventWriter (save_events, acquire_by_count, acquire_by_time) ✓
├── _iterators.py         # Iterators (count_based_iterator, time_based_iterator) ✓
└── _helpers.py           # Static helpers (sanitize, tqdm_wrapper, mock_sample) ✓

Phase 2 Summary:

  • v1.9.7: Package structure created

  • v1.9.8: Helpers and iterators extracted

  • v1.9.9: Reader and writer extracted ✓

Phase 3 (v1.10.0 planned): Simplify Orchestration Layer#

The final step is simplifying _base.py from 1,591 lines to ~200-300 lines as a pure orchestration layer:

  • Remove all reading logic (now in _reader.py)

  • Remove all writing logic (now in _writer.py)

  • Keep only: initialization, configuration management, public API coordination

  • Delegate to specialized modules via composition

Phase 4 (v1.10.0+ planned): Technical Debt Resolution#

  • Complete removal of deprecated DaqConfig and ScanConfig models

  • Removal of TODO comments with implementations

  • Integration test stabilization

  • Performance optimization if needed

v1 → v2 Transition#

This refactoring prepares v1 for stable maintenance mode while providing:

  • Clean, modular codebase for v2 migration

  • Reusable reader/writer/helper modules for v2

  • Clear architectural patterns to follow

  • Foundation for subsystem integration in v2


Architecture Achievements#

This modularization follows the Single Responsibility Principle (SRP):

Module

Lines

Responsibility

Status

_helpers.py

217

Type conversion, progress display

_iterators.py

162

Count/time-based iteration

_reader.py

269

Event reading from device

_writer.py

370

Event writing to CSV

_base.py

1,591

Orchestration layer

Phase 3 pending

Target: Reduce _base.py to ~250 lines by delegating to specialized modules.

See project constitution for more details on architectural principles.