Skip to content

Fetch existing HACS data once before generation - #5424

Draft
ludeeus wants to merge 12 commits into
mainfrom
claude/hacs-data-preflight-job-dlfkg4
Draft

Fetch existing HACS data once before generation#5424
ludeeus wants to merge 12 commits into
mainfrom
claude/hacs-data-preflight-job-dlfkg4

Conversation

@ludeeus

@ludeeus ludeeus commented Jul 23, 2026

Copy link
Copy Markdown
Member

Summary

The generate workflow fetched the existing published data from R2 inside every category-data leg: each leg called HacsDataClient.get_data(<category>) for the category's data.json, and get_repositories("removed") for the removed list. Since the legs run serially, the removed list was fetched once per category, and a republish of R2 mid-run could leave later legs working off a different baseline than earlier ones.

This PR fetches that data exactly once in a new lightweight job and shares it with every category leg via an artifact, so the whole run uses one consistent snapshot. It is a standalone change against main; the sharding work in #5419 can rebase on top and reuse the same mechanism.

Key Changes

  • New helpers in generate_category_data.py (_read_snapshot, get_stored_data, get_removed_repositories): read each category's data.json and the removed list from $HACS_STORED_DATA_DIR when set, and fall back to the data client when the dir is unset or when a file is missing, is invalid JSON, or has the wrong type (a warning is logged in each fallback case). The two existing fetches now go through these helpers.

  • GitHub Actions workflow (generate-hacs-data.yml):

    • New fetch-stored-data job that fetches every category's data.json plus the removed list once with curl (retry 5× then hard-fail the run), into outputdata/stored, and uploads it as the stored-data artifact. A comment documents the accepted trade-off: any single fetch failing fails the whole run (one consistent snapshot), rather than being isolated to that category's leg.
    • category-data depends on fetch-stored-data, downloads that artifact, and runs with HACS_STORED_DATA_DIR=outputdata/stored.
    • generate-matrix now emits the categories list as valid JSON (via jq) so both fromJSON and the fetch job's jq -r parse it robustly.
    • summarize now iterates only the expected category directories (from generate-matrix) that produced a summary.json, so the new artifact (and any non-category directory) can't break it.
    • notify_on_failure depends on fetch-stored-data.
  • Tests (test_generate_category_data.py): _StubDataClient/_StubHacs return the real data-client shapes; tests cover the snapshot-read path, every fallback (unset dir, missing file, invalid JSON, wrong shape), and an end-to-end test proving the stored-snapshot path reproduces the fetch-path output.

Implementation Details

  • HACS_STORED_DATA_DIR is optional. When unset (tests, local dev, single-repo validate.yml) the original data-client fetch behavior is preserved, so output is unchanged.
  • Reading the snapshot is network-free file I/O; the fallbacks make a partial or corrupt snapshot degrade to fetching rather than failing generation.
  • The existing per-category stored.json published to R2 is untouched — only the source of the baseline changed.
  • Known limitation: because category-data runs serially over hours, a repo removed mid-run is not dropped until the next scheduled run (the removed list is part of the run-start snapshot). Honoring mid-run removals uniformly is a possible follow-up.

https://claude.ai/code/session_01L22jYdNGF4hB4fvTYPTzP4

claude added 4 commits July 23, 2026 17:06
The generate workflow fetched the existing published data from R2 inside
every category leg: each leg called HacsDataClient.get_data(<category>) for
the category's data.json, and get_repositories("removed") for the removed
list. Since the six legs run serially, the removed list was fetched once per
category, and a republish of R2 mid-run could leave later legs working off a
different baseline than earlier ones.

Fetch that data exactly once in a new lightweight preflight job (curl only)
and share it with every category leg via an artifact, so the whole run uses
one consistent snapshot:

- generate_category_data.py: add get_stored_data()/get_removed_repositories()
  helpers that read each category's data.json and the removed list from
  $HACS_EXISTING_DATA_DIR when set, and fall back to fetching from the data
  client when unset (tests, local dev, single-repo validate.yml). Re-point the
  two existing fetches at these helpers.
- generate-hacs-data.yml: add a preflight job that fetches every category's
  data.json plus the removed list into outputdata/existing (retrying 5 times
  then failing the run), uploads it as the existing-data artifact, and has
  category-data depend on it, download it, and run with
  HACS_EXISTING_DATA_DIR=outputdata/existing. Add preflight to
  notify_on_failure.needs.
- Add unit tests covering both helper paths (snapshot dir vs. fetch fallback).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L22jYdNGF4hB4fvTYPTzP4
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L22jYdNGF4hB4fvTYPTzP4
Build the generate-matrix categories output with jq so it is valid JSON
(double-quoted) instead of single-quoted, letting both the matrix (fromJSON)
and the preflight loop (jq -r) parse it robustly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L22jYdNGF4hB4fvTYPTzP4
Read HACS_EXISTING_DATA_DIR once at module level instead of keeping the env
var name in a constant and resolving it on every call.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L22jYdNGF4hB4fvTYPTzP4
Copilot AI review requested due to automatic review settings July 23, 2026 17:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds optional “snapshot” inputs to the category data generator so it can reuse already-published data from disk (when provided) instead of always fetching from the remote data client, and updates the GitHub Actions workflow to prefetch that snapshot once per run.

Changes:

  • Add get_stored_data() and get_removed_repositories() helpers to read pre-fetched JSON from HACS_EXISTING_DATA_DIR (with fallback to the data client).
  • Update the generator to use the new helpers for stored category data and removed repositories.
  • Extend CI with a preflight job to download published data into an artifact, then pass it to category jobs via HACS_EXISTING_DATA_DIR; add tests for the new helper behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
scripts/data/generate_category_data.py Adds snapshot-aware helper functions and wires them into category generation.
tests/scripts/data/test_generate_category_data.py Adds tests/stubs verifying snapshot read behavior and fallback fetching.
.github/workflows/generate-hacs-data.yml Adds a preflight fetch job + artifact handoff and improves matrix JSON handling.
Comments suppressed due to low confidence (2)

scripts/data/generate_category_data.py:80

  • When HACS_EXISTING_DATA_DIR is set but removed.json is missing/invalid, this will raise and fail the whole run. To match the intended "use snapshot when available" behavior, fall back to fetching removed repositories on file/JSON errors.
    if EXISTING_DATA_DIR:
        with open(
            os.path.join(EXISTING_DATA_DIR, "removed.json"), encoding="utf-8"
        ) as file:
            return json.load(file)

tests/scripts/data/test_generate_category_data.py:372

  • This assertion should match the (repo-id -> repo-data) shape returned by the real data client and the updated test stub, otherwise the test can pass while still diverging from production expectations.
    assert await get_stored_data(hacs, "plugin") == {"fetched": "plugin"}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scripts/data/generate_category_data.py
Comment thread tests/scripts/data/test_generate_category_data.py Outdated
Comment thread tests/scripts/data/test_generate_category_data.py
Comment thread .github/workflows/generate-hacs-data.yml
claude and others added 4 commits July 23, 2026 17:28
Reading the existing-data snapshot now degrades gracefully: a missing file or
invalid JSON logs a warning and falls back to the data client instead of
aborting generation, so a partial snapshot cannot fail the run. Make the test
stub return realistic data-client shapes and add regression tests for the
missing/invalid-file fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L22jYdNGF4hB4fvTYPTzP4
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L22jYdNGF4hB4fvTYPTzP4
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L22jYdNGF4hB4fvTYPTzP4

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

scripts/data/generate_category_data.py:92

  • Similarly, get_removed_repositories will return whatever JSON is in removed.json if it parses, even if it isn't a list of repository names. Validating that the snapshot is a list (and falling back when it's not) avoids hard-to-debug failures later in the generation flow.
async def get_removed_repositories(hacs: AdjustedHacs) -> list[str]:
    """Return the removed-repositories list from the snapshot dir when available, else fetch."""
    if (removed := _read_snapshot(hacs, "removed.json")) is not None:
        return removed
    return await hacs.data_client.get_repositories("removed")

Comment thread scripts/data/generate_category_data.py Outdated
Comment thread scripts/data/generate_category_data.py
@ludeeus ludeeus added the pr: action Changes to actions label Jul 23, 2026
ludeeus and others added 3 commits July 24, 2026 21:17
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
A snapshot file with valid JSON but the wrong shape (e.g. a list where a dict
is expected) would previously be returned as-is and crash later. _read_snapshot
now takes the expected type and falls back to fetching when the parsed content
does not match. Add regression tests for both helpers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L22jYdNGF4hB4fvTYPTzP4

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@ludeeus ludeeus changed the title Add snapshot data support to category data generation Fetch existing HACS data once before generation Jul 24, 2026
@ludeeus
ludeeus requested a review from Copilot July 24, 2026 19:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread scripts/data/generate_category_data.py Outdated
- summarize: iterate only the expected category directories (needs
  generate-matrix + CATEGORIES), so the new stored-data artifact (and any
  non-category dir) can't crash the JSON.parse(summary.json) loop.
- Rename the existing-data snapshot to stored-data across the workflow, the
  HACS_STORED_DATA_DIR env var, and the script constant, for consistency with
  fetch-stored-data / get_stored_data.
- Comment the fetch-stored-data failure-isolation trade-off (one failed fetch
  fails the whole run, by design).
- _read_snapshot: generic return type so callers keep their dict/list types.
- Add an end-to-end test proving the stored-snapshot path reproduces the
  fetch-path output byte-for-byte; add timeout-minutes to the fetch job.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L22jYdNGF4hB4fvTYPTzP4

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread .github/workflows/generate-hacs-data.yml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr: action Changes to actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants