Skip to content

feat(downloader): add download-resilience module (resilience.py)#387

Merged
bartzbeielstein merged 5 commits into
developfrom
feat/download-resilience
Jun 14, 2026
Merged

feat(downloader): add download-resilience module (resilience.py)#387
bartzbeielstein merged 5 commits into
developfrom
feat/download-resilience

Conversation

@bartzbeielstein

Copy link
Copy Markdown
Collaborator

What & why

Adds spotforecast2_safe.downloader.resilience as a first-class public module: the per-zone ENTSO-E download → snapshot-fallback decision tree, previously living as _team4_resilience.py in the bart26l-vorlesung lecture scripts. Upstreaming it lets the lecture scripts and a new standalone book import one canonical, tested, documented implementation instead of duplicating the file.

How it works

download_with_fallback(...) orchestrates existing sf2-safe primitives (downloader.entsoe.download_zone_loads/download_new_data, utils.snapshot_store.SnapshotStore): per-zone collect-mode retry with exponential backoff → per-zone snapshot fallback within TTL → combined DE-total fallback → mode decision (four_zone / combined / None). Returns a DownloadResult (ZoneOutcome per zone); never raises on download failure. Logic is byte-faithful to the original.

  • Public API: download_with_fallback, DownloadResult, ZoneOutcome, make_store, ZONE_COLUMNS, SNAPSHOT_TTL (canonical path from spotforecast2_safe.downloader import resilience as resil; no package-root re-export, so __all__ is unchanged).
  • All sf2-safe submodule imports stay lazy inside functions (env read at call time + monkeypatch support) — invariant preserved.

Safety / compliance

  • New STRIDE threat-model table in the module docstring (network-facing module rule): outbound ENTSO-E HTTPS (countermeasures in entsoe.py) and the SnapshotStore.restore filesystem trust boundary (Tampering → TTL bound + atomic writes).
  • Deterministic, fail-safe, no silent failures.

Tests & docs

  • 20 tests in tests/downloader/test_resilience.py (scenario matrix + retry/backoff + DownloadResult.report + API surface). Full suite: 2710 passed, 1 skipped.
  • quartodoc reference pages added for the module and its public symbols.
  • ruff clean; reuse lint compliant.

No breaking changes. feat → minor bump (22.8.0) via semantic-release.

🤖 Generated with Claude Code

bartzbeielstein and others added 4 commits June 14, 2026 11:02
…2.8.0)

Port the download-resilience decision tree from bart26l-vorlesung
/_team4_resilience.py into src/spotforecast2_safe/downloader/resilience.py
with logic unchanged. Public names: download_with_fallback, DownloadResult,
ZoneOutcome, make_store, ZONE_COLUMNS, SNAPSHOT_TTL (and private _ZONE_AREAS,
_fmt_age). All sf2-safe imports remain lazy inside functions so monkeypatching
works in tests and configure_environment() runs before any get_data_home()
call.

Add top-level re-exports of download_with_fallback, DownloadResult, ZoneOutcome
in spotforecast2_safe.__init__. Expose resilience as a submodule in
spotforecast2_safe.downloader.__init__.

Add 18 scenario + unit tests in tests/downloader/test_resilience.py covering:
all-four-live, per-zone snapshot fallback (age, gate flag, WARNING log),
multi-zone cache, combined fallback (live / cache / missing), expired-TTL
handling, --no-fallback skips reads but still writes, _fmt_age, report(),
make_store, and public API surface. All 2709 tests pass.

Bump version 22.7.0 -> 22.8.0 in pyproject.toml; update CHANGELOG.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the new public downloader.resilience module into the quartodoc API
reference and harden its docstrings to the sf2-safe standard.

quartodoc / _quarto.yml:
  * Add download_with_fallback, DownloadResult, ZoneOutcome, make_store to
    the Downloader quartodoc.sections contents and matching website.sidebar
    entries. Closes the doc-coverage gap for the 22.8.0 public API.

Docstrings (src/spotforecast2_safe/downloader/resilience.py):
  * make_store: add a Returns section.
  * download_with_fallback: add a non-executed Quarto Examples block
    (#| eval: false), matching the network-dependent sibling pattern in
    downloader.entsoe (download_zone_loads / ZoneResult).
  * Convert the module-level docstring from RST (:: literal blocks, ----
    underlines) to the project's Google-style prose so griffe parses it.

Generated stubs (docs/reference/*.qmd, index.qmd, _freeze/) regenerated via
docs/quartodoc_build.py. DownloadResult.report / fallback_gate are embedded
on the class page (no separate listing, matching repo convention).
The existing tests/downloader/test_resilience.py already mirrors the
download_with_fallback / make_store example logic; all 18 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ol-managed)

semantic-release owns pyproject version and CHANGELOG.md; the feat commit
drives the 22.8.0 minor bump on release. Keep the branch at 22.7.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…E table, retry tests

1. Remove DownloadResult/ZoneOutcome/download_with_fallback re-exports
   from spotforecast2_safe/__init__.py and its __all__. Canonical consumer
   path is `from spotforecast2_safe.downloader import resilience as resil`.

2. Add STRIDE threat-model table to resilience.py module docstring covering
   two data flows: (a) outbound ENTSO-E HTTPS delegated to entsoe.py with
   countermeasures noted there; (b) SnapshotStore.restore filesystem trust
   boundary with TTL + atomic-write countermeasures and operator permission
   note.

3. Remove dead `from pathlib import Path` import (Path not used at module
   level); ruff check is now clean.

4. Change module logger from getLogger("team4_resilience") to
   getLogger(__name__) = "spotforecast2_safe.downloader.resilience";
   update caplog logger name in test_tennet_fails_valid_snapshot.

5. Add two retry/backoff tests (max_retries=2):
   - test_retry_zone_succeeds_on_second_attempt: zone fails attempt 1,
     succeeds attempt 2; asserts status "live" and sleep called once with
     backoff=5.0 (injected sleep=list.append).
   - test_retry_only_retries_failed_zones: tracks zones arg per call;
     first call requests all four, second call requests only the still-
     failing zone; zone ends "cache" via snapshot fallback.

Suite: 2710 passed, 1 skipped. ruff clean. REUSE compliant (725/725).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread tests/downloader/test_resilience.py Fixed
Comment thread tests/downloader/test_resilience.py Fixed
Comment thread tests/downloader/test_resilience.py Fixed
Comment thread tests/downloader/test_resilience.py Fixed
…nce tests

Reference ZoneResult via the entsoe module alias instead of a parallel
'from ... import ZoneResult', so the module is imported a single way.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bartzbeielstein bartzbeielstein merged commit bc45ea1 into develop Jun 14, 2026
10 checks passed
@bartzbeielstein bartzbeielstein deleted the feat/download-resilience branch June 14, 2026 15:13
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.

2 participants