Skip to content

refactor(exporters): ExporterConfig + registry — uniform ctor for buffered exporters#180

Merged
ake2l merged 5 commits into
developmentfrom
refactor/exporter-config
Jul 3, 2026
Merged

refactor(exporters): ExporterConfig + registry — uniform ctor for buffered exporters#180
ake2l merged 5 commits into
developmentfrom
refactor/exporter-config

Conversation

@ake2l

@ake2l ake2l commented Jul 3, 2026

Copy link
Copy Markdown
Member

Why

A cross-cutting exporter change touched too many files: adding exportUri (#179) had to edit 5 constructors + 5 factory calls + the base, because the common params (setup_context, product_name, chunk_size, encoding, export_uri) are repeated in every exporter signature and the factory is a hand-written if/elif chain.

What (behaviour-neutral)

  • ExporterConfig (frozen dataclass) bundles the 5 shared params.
  • UnifiedBufferedExporter.__init__(exporter_type, config) reads them from the config.
  • Every file exporter now has the uniform ctor __init__(config, params) and pulls its own format-specific options (delimiter, sheet_name, root_element, use_ndjson, …) from params — self-contained.
  • _BUFFERED_EXPORTERS registry (name → class) replaces the factory if/elif chain; the factory builds one config and dispatches. Non-buffered exporters (Console/Log/DB/Mongo/Memstore/TestResult) keep their branches — genuinely different (no config, lazy, client-based).
  • Fixes a latent bug: the old fieldnames-is-str branch was elif-chained to the exporter dispatch, so a string fieldnames fell through and returned no exporter.

Payoff (measured)

The exportUri change would now be one field on ExporterConfig + one line in the base instead of 11 edits. A new buffered exporter = one registry entry.

Scope kept honest

Format-specific params stay per-exporter (they are different). Non-buffered exporters keep their factory branches. This is composition/config cleanup, not forcing everything into one mold.

Tests

Existing exporter unit tests route construction through a small make_exporter helper (old kwargs → config) — same assertions, new ctor. New registry unit test asserts every entry is a UnifiedBufferedExporter with the uniform (config, params) signature. 798 passed, lint clean, mypy at baseline.

ake2l added 2 commits July 3, 2026 22:11
…fered exporters

Behaviour-neutral restructuring so cross-cutting exporter changes stop touching N
files (the exportUri change had to edit 5 constructors + 5 factory calls + the base):

- ExporterConfig (frozen dataclass) bundles the 5 params every buffered file exporter
  shares: setup_context, product_name, chunk_size, encoding, export_uri.
- UnifiedBufferedExporter.__init__(exporter_type, config) reads them from the config.
- Every file exporter now has the uniform ctor __init__(config, params) and pulls its
  OWN format-specific options (delimiter, sheet_name, root_element, use_ndjson, ...)
  from params - self-contained, no repeated common params.
- _BUFFERED_EXPORTERS registry (name -> class) replaces the get_exporter_by_name
  if/elif chain; the factory builds one config and dispatches. Non-buffered exporters
  (Console/Log/DB/Mongo/Memstore/TestResult) keep their branches - genuinely different.
  Also fixes a latent bug: the old fieldnames-is-str branch was elif-chained to the
  exporter dispatch, so a string fieldnames returned no exporter.

Now a common setting = one field on ExporterConfig; a new buffered exporter = one
registry entry. Existing exporter unit tests route construction through a small
make_exporter helper (old kwargs -> config). 798 passed; lint clean, mypy at baseline.
DbUnit predated the ExporterConfig refactor and was the last file exporter
built via a special elif with the old positional ctor (broken against the
new UnifiedBufferedExporter signature after rebase). Now it takes the
uniform (config, params) ctor — forcing chunk_size=None via
dataclasses.replace, a dbunit dataset is one document — and registers in
_BUFFERED_EXPORTERS; the registry gate covers it.

Also: 'Target not found' message lists the registry keys instead of a
hardcoded name list, and the never-buildable JSONSingle constant is purged.
@ake2l
ake2l force-pushed the refactor/exporter-config branch from d310a44 to 1d4c088 Compare July 3, 2026 20:18
ake2l added 3 commits July 3, 2026 22:28
…redundant _task_id

The shared factory carried CSV-only knowledge (fieldnames literal_eval) plus
a params copy just to hold the parsed value — CSVExporter owns fieldnames, so
it parses them. CSV/JSON also re-set self._task_id that the base ctor already
sets identically.
… registered exporter

One descriptor writes 10 records to all buffered targets
(CSV,JSON,XML,XLSX,TXT,DbUnit) via multi-target + exportUri; each format is
read back and must yield ids 1..10. Self-extending: the reader map must equal
_BUFFERED_EXPORTERS, so registering a new exporter without matrix coverage
fails the gate. Would have caught the DbUnit ctor break instantly.
A JSON export is read back as a source across page boundaries: ordered+cyclic
(exact wraparound sequence), random+cyclic (each source-length block of the
global shuffle is a permutation), unique (each row exactly once), and
non-cyclic exhaustion (stops at source length). All asserts are
seed-independent. Note: absent distribution defaults to RANDOM, so ordered
is explicit.
@ake2l
ake2l merged commit 24e5e8a into development Jul 3, 2026
11 checks passed
@sonarqubecloud

sonarqubecloud Bot commented Jul 3, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant