Skip to content

Enable Pihole API v6 #145890

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 58 commits into
base: dev
Choose a base branch
from
Draft

Enable Pihole API v6 #145890

wants to merge 58 commits into from

Conversation

HarvsG
Copy link
Contributor

@HarvsG HarvsG commented May 30, 2025

Proposed change

Move from #145723
The current version of the pi-hole integration does not support the the update to the pihole API, and this affects many users as can be seen by the issue below.

Some of the sensor endpoints are changed in the new API, significantly, the daily sensors no longer exist. In a sense this is breaking, but since the integrations is currently already broken, I haven't marked this as a breaking change.

Includes a dependency update of python-hole from 0.8.0 to 0.9.0 for V6 API support. The update makes the V6 API the default version and so a dependency upgrade alone would be breaking. In this PR all calls to the library declare the API version explicitly.

Considering that the V6 API was released in February, I think we should consider a deprecation procedure in another pull request after this one is merged.

Python-hole change log:

What's Changed

Add Python 3.11 and remove Pyth0n 3.8 by @fabaff in home-assistant-ecosystem/python-hole#19
Output proper information for the web versions by @mrwacky42 in home-assistant-ecosystem/python-hole#21
Poll for new status after update by @natefox in home-assistant-ecosystem/python-hole#23
Update install action by @fabaff in home-assistant-ecosystem/python-hole#26
Update actions/setup-python by @fabaff in home-assistant-ecosystem/python-hole#24
Update actions/checkout by @fabaff in home-assistant-ecosystem/python-hole#25
Add support for Pi-hole v6 API by @LeonKohli in home-assistant-ecosystem/python-hole#29
Don't depend on async-timeout for Python 3.11+. by @Romain-Geissler-1A in home-assistant-ecosystem/python-hole#30

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.

To help with the load of incoming pull requests:

@home-assistant
Copy link

Hey there @shenxn, mind taking a look at this pull request as it has been labeled with an integration (pi_hole) you are listed as a code owner for? Thanks!

Code owner commands

Code owners of pi_hole can trigger bot actions by commenting:

  • @home-assistant close Closes the pull request.
  • @home-assistant rename Awesome new title Renames the pull request.
  • @home-assistant reopen Reopen the pull request.
  • @home-assistant unassign pi_hole Removes the current integration label and assignees on the pull request, add the integration domain after the command.
  • @home-assistant add-label needs-more-information Add a label (needs-more-information, problem in dependency, problem in custom component) to the pull request.
  • @home-assistant remove-label needs-more-information Remove a label (needs-more-information, problem in dependency, problem in custom component) on the pull request.

@HarvsG HarvsG requested a review from a team as a code owner May 30, 2025 17:53
@@ -101,25 +104,6 @@ async def async_step_user(
errors=errors,
)

async def async_step_api_key(
Copy link
Contributor Author

@HarvsG HarvsG May 30, 2025

Choose a reason for hiding this comment

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

I have removed the API key as a separate step for a few reasons

  1. The info in the previous step is not required to accept an api_key
  2. Due to the ambiguity between V5 and V6 APIs it is not easy to determine connectivity and the API version separately

pi_hole = self._api_by_version(5)
await pi_hole.get_data()
if pi_hole.data is not None and "error" in pi_hole.data:
raise HoleError(pi_hole.data["error"]) # noqa: TRY301
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This lint error feels the exception should be handled in the api - I agree, but sadly it is not as the call returns a valid dictionary (which contains errors)

@@ -150,20 +135,72 @@ async def async_step_reauth_confirm(
errors=errors,
)

async def _async_try_connect(self) -> dict[str, str]:
def _api_by_version(
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like this could be factored out of setup_entry and reused here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done :)

@@ -17,3 +19,9 @@
SERVICE_DISABLE_ATTR_DURATION = "duration"

MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)

VERSION_6_RESPONSE_TO_5_ERROR = {
Copy link
Contributor

Choose a reason for hiding this comment

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

I understand the reason for this being defined here like this, and that it being here is nice to have rather than essential, but it'd be nice if there was some link to the source in FTL which generated this error for reference, and to use a pattern match on the string, since pi.hole is substituted for the configured hostname.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Link to source added. Now pattern matching used.

"issues": {
"v5_to_v6_migration": {
"title": "Recent migration from Pi-hole API v5 to v6",
"description": "We have detected that you have likely updated you Pi-hole to a version 6 API from version 5. Some sensors are different in the new API as the daily sensors have been removed and your old API token will be invalid, please provide your new app password by following the re-authentication process in repairs or in Settings -> Devices & services -> Pi-hole"
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you could word more simply and with less passive voice. Ref Home Assistant docs style guide

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hopefully improved now

@HarvsG HarvsG requested a review from mitchellrj June 16, 2025 15:46
@HarvsG HarvsG marked this pull request as draft June 19, 2025 20:24
@HarvsG HarvsG marked this pull request as ready for review June 19, 2025 20:24
Copy link
Contributor

@mitchellrj mitchellrj left a comment

Choose a reason for hiding this comment

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

LGTM

@HarvsG
Copy link
Contributor Author

HarvsG commented Jun 20, 2025

Thanks for the approval.

I've reached the limit of my abilities regarding code coverage and mocking the API. Hopefully it is enough

@mitchellrj
Copy link
Contributor

@zweckj could you weigh in on the code coverage aspect?

There's nothing in the developer's guide about 100% coverage being a requirement, the closest I found was "strongly consider adding tests".

@mkaatman
Copy link

Hopefully we're focusing on tests that add value over 100% coverage!

await holeV6.authenticate()
# Ideally python-hole would raise a specific exception for authentication failures
except HoleError as ex_v6:
if str(ex_v6) == "Authentication failed: Invalid password":
Copy link
Member

Choose a reason for hiding this comment

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

do we really have to go by exception method here, is there no special type?

Copy link
Contributor

Choose a reason for hiding this comment

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

Unfortunately, yes. Issue with the upstream library I already commented on.

_LOGGER.debug(
"No API version specified, determining Pi-hole API version for %s", host
)
version = await determine_api_version(hass, dict(entry.data))
Copy link
Member

Choose a reason for hiding this comment

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

we should probably store the determined version in the config entry. That way when it's 6 you know the user has upgraded and you don't need to check it anymore

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Isn't that achieved 2 lines later with

        hass.config_entries.async_update_entry(
            entry, data={**entry.data, CONF_API_VERSION: version}
        )

?

Copy link
Member

Choose a reason for hiding this comment

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

Oh yes, then we are just missing a check so we don’t do it every time unnecessarily

Copy link
Contributor Author

@HarvsG HarvsG Jun 30, 2025

Choose a reason for hiding this comment

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

if version is None is the check where version is instantiated as version = entry.data.get(CONF_API_VERSION) a few lines earlier. I could use a walrus as a one liner, if you think that would be clearer?

try:
await pi_hole.get_data()
if pi_hole.data is not None and "error" in pi_hole.data:
_LOGGER.debug(
Copy link
Member

Choose a reason for hiding this comment

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

we need 100% coverage for the config flow

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/pi_hole",
"iot_class": "local_polling",
"loggers": ["hole"],
"requirements": ["hole==0.8.0"]
"requirements": ["hole==0.9.0"]
Copy link
Member

Choose a reason for hiding this comment

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

I think I've asked that before, but we can bump that in a prerequisite PR right? (This way we don't trigger the full CI run every time while we work on this rather large PR)

Copy link
Contributor

Choose a reason for hiding this comment

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

0.9 is a breaking change. To hell with semver apparently.

@@ -89,6 +123,21 @@ def __init__(
def native_value(self) -> StateType:
"""Return the state of the device."""
try:
return round(self.api.data[self.entity_description.key], 2) # type: ignore[no-any-return]
return round(get_nested(self.api.data, self.entity_description.key), 2)
Copy link
Member

Choose a reason for hiding this comment

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

i know this is old, but instead of rounding we shuold set suggested_display_precision in the entity description

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed in 2eb1a21

Comment on lines +151 to +158
ir.async_create_issue(
hass,
DOMAIN,
issue_id=f"v5_to_v6_migration_{api.base_url}",
is_fixable=False,
severity=ir.IssueSeverity.ERROR,
translation_key="v5_to_v6_migration",
is_persistent=True,
Copy link
Member

Choose a reason for hiding this comment

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

let's not raise an issue here, you already get a reauth prompt and will need to act. We can log a bit more info

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Happy to remove, the procedure for obtaining the auth changes from version 5 to version 6. Version 5 uses an API Key, version 6 uses an app passowrd that is found in a different settings menu. Hence, I thought it would be helpful to provide the user more specific instructions. Happy to remove if you think this is unnecessary.

@home-assistant home-assistant bot marked this pull request as draft June 26, 2025 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Pi_hole integration doesn't work on pi_hole V6
4 participants