Skip to content

Commit 393f4b3

Browse files
committed
Generate repeated condition codes once
A condition code maps 1:1 to a <code>.wnd filename, so a repeated code (for example an "EWM True [50, 50]" row or two "- EWM50" lines) can only describe the same output. generate_all now generates each distinct code once, preserving first-seen order. This fixes a P1 in atomic mode: the duplicate path was staged twice, so the second os.replace raised FileNotFoundError after one file had already been committed, breaking the advertised all-or-nothing behaviour. It also keeps the non-atomic result count honest. Adds regression tests for repeated codes in both atomic and non-atomic paths.
1 parent f84ba05 commit 393f4b3

3 files changed

Lines changed: 39 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ valid case.
4343

4444
- Input files are read with `utf-8-sig`, so a leading byte-order mark (common in
4545
files saved by Windows editors) no longer corrupts the first line.
46+
- A repeated condition code (it maps to the same `.wnd` file) is now generated
47+
once instead of being staged twice, which previously broke `atomic=True` with a
48+
`FileNotFoundError` and left partial output behind.
4649

4750
## [0.1.2] - 2026-05-22
4851

src/pyiecwind/generation.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,18 @@ def gen_ewm(code: str, params: IECParameters, output_dir: str | Path | None = No
603603
}
604604

605605

606+
def _ordered_unique(codes: tuple[str, ...]) -> list[str]:
607+
"""Return ``codes`` with duplicates removed, preserving first-seen order.
608+
609+
Each condition code maps to a single ``<code>.wnd`` file, so a repeated code
610+
can only ever describe the same output. Generating it once keeps the result
611+
count honest and -- crucially for ``atomic=True`` -- avoids staging the same
612+
path twice and then failing to move it on the second commit pass.
613+
"""
614+
615+
return list(dict.fromkeys(codes))
616+
617+
606618
def _generate_one(
607619
code: str,
608620
params: IECParameters,
@@ -656,7 +668,7 @@ def _generate_all_atomic(
656668
generated: list[Path] = []
657669
with tempfile.TemporaryDirectory(prefix=".pyiecwind-staging-", dir=final_dir) as staging:
658670
staging_dir = Path(staging)
659-
for code in params.conditions:
671+
for code in _ordered_unique(params.conditions):
660672
_generate_one(code, params, staging_dir, strict=strict, generated=staged, errors=errors)
661673
# Reaching here means every condition was generated (or, under
662674
# strict=False, recorded as an error). Now commit by moving into place.
@@ -677,7 +689,8 @@ def generate_all(
677689
"""Generate every condition listed in ``params``.
678690
679691
Fails closed by default: the first invalid condition raises, so a caller
680-
never silently receives partial output.
692+
never silently receives partial output. Repeated condition codes describe the
693+
same ``<code>.wnd`` file and are generated once (first-seen order preserved).
681694
682695
Parameters
683696
----------
@@ -725,7 +738,7 @@ def generate_all(
725738

726739
generated: list[Path] = []
727740
errors: list[GenerationError] = []
728-
for code in params.conditions:
741+
for code in _ordered_unique(params.conditions):
729742
_generate_one(code, params, output_dir, strict=strict, generated=generated, errors=errors)
730743
return GenerationResult(tuple(generated), tuple(errors))
731744

tests/test_generation.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,23 @@ def test_atomic_non_strict_commits_valid_files_and_records_errors(self) -> None:
190190
self.assertTrue((out / "EWM50.wnd").exists())
191191
self.assertTrue((out / "NWP10.0.wnd").exists())
192192
self.assertEqual([entry for entry in out.iterdir() if entry.is_dir()], [])
193+
194+
def test_atomic_handles_repeated_condition_codes(self) -> None:
195+
# A repeated code maps to the same file; atomic mode must generate it once
196+
# rather than staging it twice and failing on the second commit.
197+
out = self.workspace_tempdir() / "out"
198+
params = default_parameters(conditions=["EWM50", "NWP10.0", "EWM50"])
199+
200+
result = generate_all(params, output_dir=out, strict=True, atomic=True)
201+
202+
self.assertEqual(result.count, 2)
203+
self.assertEqual(sorted(p.name for p in out.glob("*.wnd")), ["EWM50.wnd", "NWP10.0.wnd"])
204+
205+
def test_non_atomic_dedupes_repeated_condition_codes(self) -> None:
206+
out = self.workspace_tempdir() / "out"
207+
params = default_parameters(conditions=["EWM50", "EWM50"])
208+
209+
result = generate_all(params, output_dir=out, strict=True, atomic=False)
210+
211+
self.assertEqual(result.count, 1)
212+
self.assertEqual([p.name for p in out.glob("*.wnd")], ["EWM50.wnd"])

0 commit comments

Comments
 (0)