Skip to content

v1.0.0

Latest

Choose a tag to compare

@github-actions github-actions released this 05 Oct 17:36
Immutable release. Only release title and notes can be modified.
a42e0c0

This release is a rewrite of the library. It now covers the whole AdGuard Home API, returns typed models instead of loose values, and comes with a command-line interface. Almost everything changed, so read the upgrade notes below before you update.

Highlights

  • Typed models, one request each. Every area returns frozen dataclasses from a single request. Reading all statistics used to take a request per number; now await adguard.stats.get() gets them all at once, including the top lists and history.
  • The whole API. Next to the areas the library already had, it now covers clients, DNS rewrites, DNS server settings, blocked services, access lists, DHCP, encryption (TLS), and the query log entries. Every area follows the same shape: get() for data, config() and set_config() for settings, and verbs like enable() or refresh() for actions.
  • Units you do not have to think about. AdGuard Home mixes milliseconds, seconds, hours, and days. Durations are timedelta and timestamps datetime now, whatever the API uses underneath, and versions are AwesomeVersion.
  • A command-line interface. Install adguardhome[cli] and run adguardhome status, adguardhome stats, adguardhome log, or adguardhome disable protection --for 10m. Every command that shows data can emit JSON.
  • Errors you can act on. Connection problems, timeouts, rejected credentials, unsupported endpoints, and error responses each have their own exception, carrying the status code and the message of AdGuard Home.
  • Tested against real servers. Next to a test suite at 100% coverage, every area ran against AdGuard Home v0.107.68 and v0.107.79.

Requirements

  • AdGuard Home v0.107.68 or newer. Use status.supported to check the server you connect to.
  • Python 3.11 or newer.
  • aiohttp 3.14 or newer.

Upgrading

Connecting

The client takes the URL of the web interface, the same one you open in your browser, instead of a host, port, TLS flag, and base path. Credentials go in separate arguments, never in the URL.

# Before
AdGuardHome("192.168.1.2", port=3000, tls=False, username="admin", password="secret")

# Now
AdGuardHome("http://192.168.1.2:3000", username="admin", password="secret")
AdGuardHome("https://example.com/adguard", username="admin", password="secret")

Redirects are not followed anymore, as they would resend a request, including a TLS private key, to wherever they point. With force HTTPS enabled in AdGuard Home, use its HTTPS URL; the error tells you which one.

Reading values

Methods that each returned a single value are gone. Read the model instead:

Before Now
version() (await adguard.status()).version
protection_enabled() (await adguard.status()).protection_enabled
stats.dns_queries() and the other stats methods stats = await adguard.stats.get(), then stats.dns_queries, stats.blocked_filtering, stats.blocked_percentage, and so on
stats.replaced_safebrowsing() stats.blocked_safebrowsing
stats.replaced_parental() stats.blocked_parental
stats.replaced_safesearch() stats.enforced_safesearch
stats.avg_processing_time() (milliseconds) stats.avg_processing_time (a timedelta)
stats.period() (days) (await adguard.stats.config()).retention (a timedelta)
filtering.enabled() (await adguard.filtering.config()).enabled
filtering.rules_count(allowlist=False) sum(f.rules_count for f in await adguard.filtering.blocklists.list())
filtering.interval() (hours) (await adguard.filtering.config()).update_interval (a timedelta)
querylog.enabled() (await adguard.querylog.config()).enabled
querylog.interval() (days) (await adguard.querylog.config()).retention (a timedelta)
safesearch.enabled() (await adguard.safesearch.config()).enabled
update.update_available() update.get()

Parental control and safe browsing still have enabled(), enable(), and disable().

Changing settings

Settings are read as a model, changed with dataclasses.replace(), and written back:

from dataclasses import replace
from datetime import timedelta

config = await adguard.querylog.config()
await adguard.querylog.set_config(replace(config, retention=timedelta(days=7)))

The interval() setters of filtering and the query log are replaced by that. enable() and disable() remain on every area that can be turned on and off.

Filter lists

Blocklists and allowlists are separate collections now, instead of an allowlist argument on every method:

Before Now
filtering.add_url(allowlist=False, name=..., url=...) filtering.blocklists.add(url, name=...)
filtering.remove_url(allowlist=False, url=...) filtering.blocklists.remove(url)
filtering.enable_url(...) and disable_url(...) filtering.blocklists.enable(url) and disable(url)
filtering.url_enabled(...) (await filtering.blocklists.get(url)).enabled
filtering.refresh(allowlist=False, force=True) filtering.blocklists.refresh(), which returns the number of updated lists

The force argument is gone: AdGuard Home always forces a refresh. Filter lists are matched by their exact URL, like AdGuard Home does.

Other changes

  • disable_protection() takes an optional timedelta, to pause protection for a while. status().protection_resumes_in tells you how long a pause has left.
  • update.begin_update() is update.install().
  • request() is private now. Every endpoint has a method instead.
  • Exceptions raised by the library keep the status code and message of AdGuard Home. Catch AdGuardHomeAuthenticationError, AdGuardHomeConnectionTimeoutError, AdGuardHomeUnsupportedError, or AdGuardHomeResponseError for specific cases; all of them still derive from AdGuardHomeError.
  • Safe search uses the newer settings endpoints, so enabling it keeps the settings per search engine.

Thanks

To everyone who contributed to this release, and to those whose earlier pull requests asked for things that are part of it now, like blocked services, pausing protection, and the top domains in the statistics.

What’s changed

🚨 Breaking changes

  • Restructure the library around the AdGuard Home API resources @frenck (#1863)
  • Cover the rest of the AdGuard Home API @frenck (#1865)
  • Fix the findings of a review round @frenck (#1867)

✨ New features

🐛 Bug fixes

  • Fix the Prettier hook, which never ran, and the editorconfig @frenck (#1862)
  • Fix the findings of a review round @frenck (#1867)
  • Accept the answers AdGuard Home gives when adding a filter list @frenck (#1868)

🚀 Enhancements

  • Migrate tests to aiointercept and use aiohttp.encode_basic_auth @balloobbot (#1861)
  • Restructure the library around the AdGuard Home API resources @frenck (#1863)

🧰 Maintenance

📚 Documentation

  • Add agent guidance and an AI policy, and require full coverage @frenck (#1864)

⬆️ Dependency updates

168 changes