Skip to content

Use device-reported repo for firmware releases and upgrades#2049

Open
netmindz wants to merge 7 commits intofrenck:mainfrom
netmindz:respect-repo
Open

Use device-reported repo for firmware releases and upgrades#2049
netmindz wants to merge 7 commits intofrenck:mainfrom
netmindz:respect-repo

Conversation

@netmindz
Copy link
Copy Markdown
Contributor

@netmindz netmindz commented Apr 22, 2026

This pull request adds support for devices to specify their own GitHub repository for firmware upgrades, allowing the system to prefer a device-reported repository over the default. This improves flexibility when fetching releases or performing upgrades, especially for devices with custom firmware.

Device-reported repository support:

  • Added a new repo field to the Info model in src/wled/models.py, which stores the GitHub repository in 'org/repo' format as reported by the device firmware. This field is used in preference to the default repository when fetching releases or upgrading.
  • Imported DEFAULT_REPO in src/wled/models.py to provide a fallback value for the new repo field.
  • Updated the upgrade logic in src/wled/wled.py to prefer the repo reported by the device itself, improving compatibility with devices running custom firmware.# Proposed Changes

(Describe the changes and rationale behind them)

Related Issues

(GitHub link to related issues or pull requests)

Summary by CodeRabbit

  • New Features

    • Firmware upgrades now use the repository reported by each device, ensuring updates are fetched from the device-specific source rather than a library default.
  • Tests

    • Added tests verifying device-reported repository handling during info parsing and upgrade download URL construction.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

Warning

Rate limit exceeded

@netmindz has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes and 43 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 51 minutes and 43 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40580e7c-2d85-437d-8042-64bfad1b2096

📥 Commits

Reviewing files that changed from the base of the PR and between d6896c4 and 97b9a71.

📒 Files selected for processing (3)
  • src/wled/wled.py
  • tests/const.py
  • tests/test_models.py
📝 Walkthrough

Walkthrough

Adds a device-reported repo field to the Info model and updates WLED.upgrade() to use device.info.repo when constructing firmware download URLs; tests added to verify Info.from_dict repo behavior and that upgrade uses the device-reported repo.

Changes

Cohort / File(s) Summary
Model
src/wled/models.py
Added repo: str to Info dataclass with DEFAULT_REPO default and alias "repo" to accept/store device-provided repo values.
Upgrade Logic
src/wled/wled.py
WLED.upgrade() now overwrites the local repo variable with self._device.info.repo (device-reported repo) before computing the download_url.
Tests
tests/test_models.py, tests/test_wled.py
Added tests: Info.from_dict fallback to DEFAULT_REPO and acceptance of provided repo; new test ensuring upgrade() builds download URL from device repo (note: duplicate test added in tests/test_wled.py).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • frenck

Poem

🐰 I found a little repo the device did show,
It told me where to fetch the firmware to go,
No hardcoded path, the rabbit sings,
Following device-led firmware springs,
Hooray—down the stream of bytes we flow! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: using device-reported repository for firmware operations rather than a hardcoded default.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/wled/wled.py (1)

607-659: ⚠️ Potential issue | 🟠 Major

Caller-supplied repo argument is silently ignored.

Line 659 unconditionally overwrites the local repo with self._device.info.repo, which discards whatever the caller passed via the repo= keyword argument. Since Info.repo defaults to DEFAULT_REPO, any explicit override (e.g. await wled.upgrade(version="...", repo="myfork/WLED")) now has zero effect — a silent behavioral regression of the public API.

Either remove the repo parameter from upgrade() (breaking change, document it) or give the explicit argument precedence and fall back to the device-reported value only when the caller didn't customize it. The latter preserves backward compatibility:

🛠️ Suggested fix
-        # Prefer the repo reported by the device itself; older firmware that does
-        # not include the field will have fallen back to DEFAULT_REPO already.
-        repo = self._device.info.repo
+        # Prefer an explicitly supplied repo; otherwise use the repo reported by
+        # the device itself. Older firmware without the field will have fallen
+        # back to DEFAULT_REPO during deserialization.
+        if repo == DEFAULT_REPO:
+            repo = self._device.info.repo

Also consider updating the repo argument docstring (Line 618) to reflect the new precedence rules.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/wled/wled.py` around lines 607 - 659, The upgrade() method currently
unconditionally overwrites the caller-supplied repo with self._device.info.repo;
change repo handling so an explicit caller argument wins: update the signature
repo: str | None = None (instead of DEFAULT_REPO), then set local repo = repo if
repo is not None else (self._device.info.repo or DEFAULT_REPO) inside upgrade(),
and update the repo docstring to state that an explicit repo parameter takes
precedence over the device-reported repo. Refer to function upgrade, parameter
repo, self._device.info.repo, and DEFAULT_REPO when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/wled/wled.py`:
- Around line 607-659: The upgrade() method currently unconditionally overwrites
the caller-supplied repo with self._device.info.repo; change repo handling so an
explicit caller argument wins: update the signature repo: str | None = None
(instead of DEFAULT_REPO), then set local repo = repo if repo is not None else
(self._device.info.repo or DEFAULT_REPO) inside upgrade(), and update the repo
docstring to state that an explicit repo parameter takes precedence over the
device-reported repo. Refer to function upgrade, parameter repo,
self._device.info.repo, and DEFAULT_REPO when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 453a78d8-c076-401a-ad5a-fc2038917300

📥 Commits

Reviewing files that changed from the base of the PR and between ea7e0e2 and cceb573.

📒 Files selected for processing (2)
  • src/wled/models.py
  • src/wled/wled.py

@frenck frenck requested a review from Copilot April 22, 2026 18:01
@frenck frenck added the new-feature New features or options. label Apr 22, 2026
@netmindz netmindz marked this pull request as draft April 22, 2026 18:02
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 22, 2026

Codecov Report

❌ Patch coverage is 75.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 97.12%. Comparing base (3e87d76) to head (97b9a71).
⚠️ Report is 597 commits behind head on main.

Files with missing lines Patch % Lines
src/wled/wled.py 50.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #2049       +/-   ##
===========================================
+ Coverage   58.61%   97.12%   +38.51%     
===========================================
  Files           6        8        +2     
  Lines         662     1079      +417     
  Branches      143      113       -30     
===========================================
+ Hits          388     1048      +660     
+ Misses        270       20      -250     
- Partials        4       11        +7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@netmindz netmindz marked this pull request as ready for review April 22, 2026 18:05
@netmindz
Copy link
Copy Markdown
Contributor Author

@coderabitai review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a device-reported GitHub repository field to the WLED Info model and updates firmware upgrade behavior to prefer that repository, enabling upgrades for custom-firmware devices without relying solely on the library default.

Changes:

  • Add Info.repo (defaulting to DEFAULT_REPO) to store the device-reported org/repo.
  • Update WLED.upgrade() to use the device-reported repo when constructing the GitHub download URL.
  • Add tests covering Info.repo defaulting/device override and upgrade behavior using a device-reported repo.

Reviewed changes

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

File Description
src/wled/models.py Adds Info.repo with DEFAULT_REPO fallback and documentation for the new field.
src/wled/wled.py Changes upgrade URL construction to prefer the device-reported repository.
tests/test_models.py Adds unit tests for Info.repo default and device-provided values.
tests/test_wled.py Adds an upgrade test verifying device-reported repo is used for firmware download.

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

Comment thread src/wled/wled.py Outdated
Comment thread src/wled/models.py Outdated
Comment thread tests/test_wled.py
Copy link
Copy Markdown
Owner

@frenck frenck left a comment

Choose a reason for hiding this comment

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

There are comments from copilot, and above all... the CI is failing.

../Frenck

                       

Blogging my personal ramblings at frenck.dev

netmindz and others added 2 commits April 25, 2026 17:37
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/test_wled.py (1)

1335-1368: Optional: reuse _get_wled_with_device_for_upgrade helper and add an explicit URL assertion.

The setup duplicates _get_wled_with_device_for_upgrade (Lines 1206-1233). Consider extending the helper to accept a repo kwarg and using it here, then explicitly assert the request was made by inspecting mocked.requests so the intent is clearer than relying on aioresponses raising on unmatched URLs.

♻️ Sketch
 async def _get_wled_with_device_for_upgrade(
     mocked: aioresponses,
     session: aiohttp.ClientSession,
     arch: str = "esp32",
     version: str = "0.14.0",
     wifi_bssid: str = "AA:BB:CC:DD:EE:FF",
+    repo: str | None = None,
 ) -> WLED:
     wled_data = load_fixture_json("wled")
     wled_data["info"]["arch"] = arch
     wled_data["info"]["ver"] = version
+    if repo is not None:
+        wled_data["info"]["repo"] = repo
     if wifi_bssid is not None:
         wled_data["info"]["wifi"]["bssid"] = wifi_bssid
     ...

Then in the test, after await wled.upgrade(...), additionally assert that the MoonModules URL was actually hit (e.g., via mocked.requests) rather than relying solely on aioresponses raising on an unmatched URL.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_wled.py` around lines 1335 - 1368, Refactor the duplicated setup
by extending the existing helper _get_wled_with_device_for_upgrade to accept a
repo kwarg (defaulting to current behavior), then call that helper inside
test_upgrade_uses_device_repo to create the WLED instance instead of repeating
the session/mock setup; after awaiting wled.upgrade(version="0.15.0") assert
explicitly that the GitHub release URL
(https://github.com/MoonModules/WLED/releases/download/v0.15.0/WLED_0.15.0_ESP32.bin)
was requested by inspecting mocked.requests so the test verifies the
device-reported repo was actually used by WLED.upgrade.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/wled/models.py`:
- Around line 493-497: The repo field can contain malformed or malicious values
used by WLED.upgrade() to build GitHub URLs; add a lightweight
post-deserialization validation on the model (implement a __post_deserialize__
or equivalent hook in the class that defines repo) that checks repo against a
strict regex like ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ and, on mismatch, resets the
attribute to DEFAULT_REPO and optionally logs a warning; reference the repo
field name, DEFAULT_REPO constant, and the WLED.upgrade() call-site so reviewers
can verify the fallback and ensure the upgrade URL construction uses the
validated value.

In `@tests/test_models.py`:
- Around line 318-323: The test test_info_repo_defaults_to_default_repo is
importing DEFAULT_REPO inside the test body which causes lint errors
(PLC0415/C0415); move the DEFAULT_REPO import to the module-level import block
at the top of tests/test_models.py (alongside other wled.const imports) and
remove the inline from wled.const import DEFAULT_REPO line from inside the test
so the test simply references DEFAULT_REPO.

---

Nitpick comments:
In `@tests/test_wled.py`:
- Around line 1335-1368: Refactor the duplicated setup by extending the existing
helper _get_wled_with_device_for_upgrade to accept a repo kwarg (defaulting to
current behavior), then call that helper inside test_upgrade_uses_device_repo to
create the WLED instance instead of repeating the session/mock setup; after
awaiting wled.upgrade(version="0.15.0") assert explicitly that the GitHub
release URL
(https://github.com/MoonModules/WLED/releases/download/v0.15.0/WLED_0.15.0_ESP32.bin)
was requested by inspecting mocked.requests so the test verifies the
device-reported repo was actually used by WLED.upgrade.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ef44f2aa-d46b-4688-9fe8-9c7d996ff5ed

📥 Commits

Reviewing files that changed from the base of the PR and between cceb573 and d6896c4.

📒 Files selected for processing (4)
  • src/wled/models.py
  • tests/__snapshots__/test_models.ambr
  • tests/test_models.py
  • tests/test_wled.py

Comment thread src/wled/models.py
Comment thread tests/test_models.py
@netmindz
Copy link
Copy Markdown
Contributor Author

Fixed @frenck

Comment thread src/wled/wled.py
# Prefer the repo reported by the device itself only when the caller
# did not override the default repository. Older firmware that does not
# include the field will already have fallen back to DEFAULT_REPO.
if repo == DEFAULT_REPO:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This has a side effect, if the default repo was actually meant to be set, it wouldn't work. We probably need to use a sentinel to make this more robust.

Comment thread tests/const.py

from wled.const import DEFAULT_REPO

__all__ = ["DEFAULT_REPO"]
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This does not make much sense. Instead just import directly

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

Labels

new-feature New features or options.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants