Skip to content

1.9.3#63

Open
eXPerience83 wants to merge 205 commits intomainfrom
1.9.2
Open

1.9.3#63
eXPerience83 wants to merge 205 commits intomainfrom
1.9.2

Conversation

@eXPerience83
Copy link
Owner

@eXPerience83 eXPerience83 commented Feb 14, 2026

PR Type

Enhancement, Tests, Bug fix, Documentation


Description

  • Major refactoring: Extracted PollenDataUpdateCoordinator and GooglePollenApiClient into separate modules (coordinator.py, client.py) with comprehensive HTTP retry logic and error handling

  • New runtime data management: Introduced PollenLevelsRuntimeData dataclass and migration logic to store coordinator/client instances per config entry instead of in hass.data

  • Enhanced config flow validation: Refactored with dynamic schema builders, comprehensive input validation (API key, coordinates, intervals, forecast days), and improved error handling distinguishing between auth failures and update failures

  • Improved error handling: Added extract_error_message() utility for safe HTTP response parsing, API key redaction in logs, and Retry-After header parsing for rate limit resilience

  • Setup validation: Implemented coordinate validation (finite values, in-range checks), numeric option sanitization, and config entry setup with initial data refresh

  • Comprehensive test expansion: Added 30+ new test cases covering migration logic, validation bounds, HTTP error handling, diagnostics privacy, and translation key discovery

  • Translation updates: Enhanced all 25 language files with new configuration fields (forecast_days, create_forecast_sensors), error message placeholders, and improved descriptions

  • Documentation improvements: Updated README with selector notation clarification, API setup guidance, troubleshooting section, and added CONTRIBUTING.md with code standards

  • Bug fix: Improved async test detection in pytest configuration using inspect.iscoroutinefunction()

  • Version bump: Updated to 1.9.2 with detailed changelog documenting all changes


Diagram Walkthrough

flowchart LR
  A["Config Flow<br/>Validation"] -->|"Dynamic Schema<br/>& Validation"| B["Setup Entry<br/>with Migration"]
  B -->|"Create & Store"| C["Runtime Data<br/>Coordinator/Client"]
  D["HTTP Client<br/>Retry Logic"] -->|"Fetch Data"| E["Coordinator<br/>Process Forecast"]
  E -->|"Update"| C
  C -->|"Access"| F["Sensors<br/>& Diagnostics"]
  G["Comprehensive<br/>Tests"] -->|"Validate"| A
  G -->|"Validate"| B
  G -->|"Validate"| D
  H["25 Language<br/>Translations"] -->|"Support"| A
Loading

File Walkthrough

Relevant files
Tests
6 files
test_sensor.py
Refactor tests to use injected client and coordinator modules

tests/test_sensor.py

  • Enhanced _StubSensorEntity with unique_id and device_info properties
    to support entity registry testing
  • Added _stub_parse_http_date() function to parse HTTP-date formatted
    retry headers
  • Refactored test fixtures to use separate client_mod and
    coordinator_mod modules instead of monkeypatching
  • Added 8 new coordinator tests covering forecast day clamping, missing
    dailyInfo handling, color channel validation, and deterministic key
    ordering
  • Updated existing tests to instantiate GooglePollenApiClient and
    PollenDataUpdateCoordinator directly with proper client injection
  • Modified RegistryEntry to use NamedTuple and updated RegistryStub to
    support async_entries_for_config_entry() method
  • Added tests for HTTP-date retry-after parsing and improved
    auth/forbidden error handling tests
+686/-83
test_translations.py
Enhance translation tests with services and sensor key validation

tests/test_translations.py

  • Added _extract_services_from_services_yaml() to parse service names
    from services.yaml without PyYAML
  • Added _extract_service_labels_from_services_yaml() to extract
    name/description labels from services.yaml
  • Added _extract_sensor_translation_key_usage() to discover entity and
    device translation keys from sensor.py AST
  • Added _extract_schema_key_aliases() to resolve schema key wrapper
    aliases like location_field = vol.Required(CONF_LOCATION)
  • Refactored _fields_from_schema_call() to return tuple of (fields,
    sections) and support nested section() wrappers
  • Added _extract_helper_error_keys() to discover error keys emitted by
    helper functions like _parse_int_option()
  • Added _ScopedErrorsVisitor to collect helper-propagated errors in
    class scopes
  • Added 4 new test functions:
    test_config_flow_extractor_includes_helper_error_keys(),
    test_sensor_translation_keys_present(),
    test_services_translation_keys_present(),
    test_services_yaml_labels_match_translations()
+474/-48
test_init.py
Comprehensive test infrastructure expansion with migration and
validation tests

tests/test_init.py

  • Added comprehensive stub modules for Home Assistant components
    (sensor, const, aiohttp, helpers, entity_registry, entity, dt, util,
    update_coordinator) to support integration testing
  • Expanded _FakeConfigEntries and _FakeEntry classes with additional
    attributes (data, options, version, runtime_data) and methods
    (async_update_entry, async_entries)
  • Added _ServiceRegistry class to mock Home Assistant service
    registration and calling
  • Implemented 13 new test cases covering config entry setup validation
    (missing API key, invalid/out-of-range coordinates, boundary
    conditions, decimal options)
  • Added 9 migration tests validating version bumping, legacy key
    cleanup, and per-day sensor mode normalization across data/options
  • Updated existing setup/unload tests to verify runtime_data coordinator
    assignment and cleanup
+584/-7 
test_config_flow.py
Enhanced test stubs and validation test coverage for config flow

tests/test_config_flow.py

  • Introduced _force_module() helper to replace setdefault() calls,
    ensuring test stubs override pre-existing modules
  • Extended selector stubs with NumberSelector, TextSelector,
    SelectSelector and their config classes
  • Enhanced _StubResponse with json() and text() async methods for
    realistic HTTP response handling
  • Added add_suggested_values_to_schema() method to _StubConfigFlow for
    form schema manipulation
  • Implemented 8 new validation tests for update interval parsing, HTTP
    error code mapping, API key redaction, and error placeholder
    management
  • Added 2 parametrized tests for _parse_int_option() helper validating
    non-finite and decimal value rejection
+508/-18
test_diagnostics.py
New diagnostics test module for privacy and payload validation

tests/test_diagnostics.py

  • New test module with 3 test cases validating diagnostics privacy and
    payload sizing
  • Tests verify coordinate rounding, data key truncation, and non-finite
    value handling
  • Validates that API keys are redacted and coordinates are removed from
    entry data
+195/-0 
test_options_flow.py
Options flow validation and schema sanitization tests       

tests/test_options_flow.py

  • Added 3 tests validating update interval bounds checking and error
    handling
  • Implemented 3 parametrized tests for schema default sanitization
    (update interval, forecast days, sensor mode)
  • Tests verify that invalid defaults are clamped to supported ranges
    during form rendering
+144/-1 
Enhancement
8 files
config_flow.py
Refactor config flow with comprehensive validation and UI improvements

custom_components/pollenlevels/config_flow.py

  • Increased VERSION from 1 to 3 to trigger re-validation of existing
    entries
  • Replaced hardcoded schema with _build_step_user_schema() that
    dynamically constructs form with all fields (API key, location,
    interval, language, forecast days, sensor mode)
  • Added helper functions _parse_int_option(), _parse_update_interval(),
    _sanitize_*_for_default() for robust input validation and clamping
  • Enhanced _async_validate_input() to validate all config options
    (interval, forecast days, sensor mode compatibility) and extract error
    messages from API responses
  • Improved error handling to distinguish between 401 (auth failed), 403
    with invalid key message (auth failed), and 403 forbidden (update
    failed)
  • Updated async_step_init() (options flow) to use the new schema
    builders and validation helpers
  • Added imports for NumberSelector, SelectSelector, TextSelector and
    related config classes
+355/-156
util.py
Add utility functions for error extraction and mode normalization

custom_components/pollenlevels/util.py

  • Added extract_error_message() async function to safely extract and
    normalize error messages from HTTP responses
  • Added normalize_sensor_mode() function to validate and default
    forecast sensor mode with logging
  • Updated __all__ export list to include new functions
  • Added TYPE_CHECKING imports for ClientResponse type hint with runtime
    fallback
+76/-1   
coordinator.py
New coordinator module with data update and forecast processing

custom_components/pollenlevels/coordinator.py

  • New module extracting PollenDataUpdateCoordinator class from sensor.py
    with full pollen data fetch and forecast processing logic
  • Implemented helper functions for color normalization
    (_normalize_channel, _rgb_from_api, _rgb_to_hex_triplet) and plant
    code normalization
  • Added comprehensive forecast attribute processing
    (_process_forecast_attributes) with trend calculation and expected
    peak detection
  • Implemented per-day type sensor creation (D+1, D+2) with day-specific
    inSeason and health recommendations
  • Integrated plant forecast attributes with cross-day code matching and
    color handling
+529/-0 
__init__.py
Migration logic, setup validation, and runtime data management

custom_components/pollenlevels/init.py

  • Added async_migrate_entry() function to migrate per-day sensor mode
    from data to options and clean up legacy keys
  • Implemented comprehensive config entry setup validation: API key
    presence, coordinate validation (finite and in-range), numeric option
    sanitization
  • Integrated PollenDataUpdateCoordinator and GooglePollenApiClient
    instantiation during setup with initial data refresh
  • Refactored async_setup() service handler to use entry.runtime_data for
    coordinator access and improved error logging with API key redaction
  • Updated async_unload_entry() to clear runtime_data instead of managing
    hass.data dictionary
  • Changed log level from INFO to DEBUG for force_update service
    execution
+228/-20
client.py
New HTTP client with retry logic and error handling           

custom_components/pollenlevels/client.py

  • New module implementing GooglePollenApiClient for Google Pollen API
    HTTP requests with retry logic
  • Implemented retry handling for 429 (rate limit), 5xx (server errors),
    and transient failures (timeout, network errors)
  • Added HTTP status-specific error handling: 401 raises
    ConfigEntryAuthFailed, 403 checks for invalid API key message
  • Integrated Retry-After header parsing and jittered exponential backoff
    for resilient API calls
  • Implemented error message extraction and API key redaction for safe
    logging
+224/-0 
diagnostics.py
Runtime data access and defensive numeric handling in diagnostics

custom_components/pollenlevels/diagnostics.py

  • Updated coordinator access to use entry.runtime_data instead of
    hass.data dictionary
  • Enhanced coordinate rounding to handle non-finite values defensively
    with math.isfinite() checks
  • Implemented forecast days clamping to supported range
    (MIN/MAX_FORECAST_DAYS) in diagnostics examples
  • Added data_keys_total field and truncated data_keys list to first 50
    items to limit payload size
  • Removed precise coordinates from entry data section while keeping
    rounded coordinates in request examples
+31/-23 
const.py
New constants for validation bounds and API configuration

custom_components/pollenlevels/const.py

  • Added numeric bounds constants: MIN_UPDATE_INTERVAL_HOURS,
    MAX_UPDATE_INTERVAL_HOURS, MIN_FORECAST_DAYS
  • Added API-related constants: POLLEN_API_TIMEOUT, MAX_RETRIES,
    POLLEN_API_KEY_URL, RESTRICTING_API_KEYS_URL
  • Implemented is_invalid_api_key_message() function to detect invalid
    API key error messages
  • Added ATTRIBUTION constant for data source attribution
  • Converted FORECAST_SENSORS_CHOICES to explicit list type annotation
+31/-1   
runtime.py
New runtime data container for config entry state               

custom_components/pollenlevels/runtime.py

  • New module defining PollenLevelsRuntimeData dataclass to hold
    coordinator and client instances per config entry
  • Added type alias PollenLevelsConfigEntry for type-hinted config entry
    access to runtime data
+24/-0   
Refactoring
1 files
sensor.py
Extract coordinator and runtime data to separate modules 

custom_components/pollenlevels/sensor.py

  • Removed PollenDataUpdateCoordinator class (moved to separate
    coordinator.py module)
  • Removed color processing functions _normalize_channel(),
    _rgb_from_api(), _rgb_to_hex_triplet() (moved to coordinator)
  • Simplified async_setup_entry() to retrieve coordinator from
    config_entry.runtime_data instead of creating it inline
  • Added ForecastSensorMode enum for type-safe forecast sensor mode
    values
  • Updated imports to use PollenLevelsConfigEntry and
    PollenLevelsRuntimeData types from new runtime.py module
  • Removed direct imports of aiohttp, random, asyncio from coordinator
    logic
  • Updated PollenSensor.extra_state_attributes() to exclude color_raw
    from public attributes
  • Enhanced device_info property to infer group type when missing from
    data
  • Changed attribution constant reference to use ATTRIBUTION from const
    module
+60/-626
Configuration changes
3 files
__init__.py
Mark tests directory as a Python package                                 

tests/init.py

  • Created new __init__.py file to mark tests directory as a package
  • Added docstring explaining purpose of preventing import conflicts with
    third-party tests package
+6/-0     
pyproject.toml
Version bump and pytest configuration updates                       

pyproject.toml

  • Bumped version from 1.8.6 to 1.9.2
  • Added pytest configuration with importlib mode to avoid test module
    name collisions
+7/-1     
manifest.json
Version update in manifest                                                             

custom_components/pollenlevels/manifest.json

  • Bumped version from 1.8.6 to 1.9.2
+1/-1     
Documentation
26 files
services.yaml
Add service metadata for force_update action                         

custom_components/pollenlevels/services.yaml

  • Added name field with value "Force Update" to the force_update service
  • Added description field explaining the service purpose
+2/-0     
CHANGELOG.md
Detailed release notes for versions 1.9.2, 1.9.1, and 1.9.0-rc1

CHANGELOG.md

  • Added comprehensive 1.9.2 release notes documenting fixes for
    coordinator updates, coordinate validation, and numeric parsing
    hardening
  • Added 1.9.1 release notes covering data preservation, API key
    redaction, and service improvements
  • Added 1.9.0-rc1 release notes detailing major refactoring: runtime
    data migration, client extraction, HTTP error handling, and migration
    logic
  • Updated existing changelog entries with improved formatting and
    clarity
+189/-12
cs.json
Czech translation updates for new fields and error messages

custom_components/pollenlevels/translations/cs.json

  • Enhanced setup flow description with API key URL placeholders and best
    practices link
  • Added new fields to setup data: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed error context
  • Added new error codes: invalid_update_interval and
    invalid_forecast_days with localized messages
  • Fixed options flow description typo (D+2 → D+1+2) and added new error
    codes
+14/-13 
hu.json
Hungarian translation updates for enhanced configuration and error
handling

custom_components/pollenlevels/translations/hu.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
uk.json
Ukrainian translation updates for enhanced configuration and error
handling

custom_components/pollenlevels/translations/uk.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
pl.json
Polish translation updates for enhanced configuration and error
handling

custom_components/pollenlevels/translations/pl.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
ca.json
Catalan translation updates for enhanced configuration and error
handling

custom_components/pollenlevels/translations/ca.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+13/-12 
fi.json
Finnish translation updates for enhanced configuration and error
handling

custom_components/pollenlevels/translations/fi.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
ro.json
Romanian translation updates for enhanced configuration and error
handling

custom_components/pollenlevels/translations/ro.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
sv.json
Swedish translation updates for enhanced configuration and error
handling

custom_components/pollenlevels/translations/sv.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
it.json
Italian translation updates for enhanced configuration and error
handling

custom_components/pollenlevels/translations/it.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
fr.json
French translation updates for enhanced configuration and error
handling

custom_components/pollenlevels/translations/fr.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
nb.json
Norwegian Bokmål translation updates for enhanced configuration and
error handling

custom_components/pollenlevels/translations/nb.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
ru.json
Russian translation updates for enhanced configuration and error
handling

custom_components/pollenlevels/translations/ru.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
da.json
Danish translation updates for enhanced configuration and error
handling

custom_components/pollenlevels/translations/da.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
de.json
German translation updates for enhanced configuration and error
handling

custom_components/pollenlevels/translations/de.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
zh-Hant.json
Traditional Chinese translation updates for enhanced configuration and
error handling

custom_components/pollenlevels/translations/zh-Hant.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
nl.json
Dutch translation updates for enhanced configuration and error
handling

custom_components/pollenlevels/translations/nl.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
es.json
Spanish translation updates for enhanced configuration and error
handling

custom_components/pollenlevels/translations/es.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+13/-12 
pt-PT.json
Portuguese (Portugal) translation updates for enhanced configuration
and error handling

custom_components/pollenlevels/translations/pt-PT.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
pt-BR.json
Portuguese (Brazil) translation updates for enhanced configuration and
error handling

custom_components/pollenlevels/translations/pt-BR.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
zh-Hans.json
Simplified Chinese translation updates for enhanced configuration and
error handling

custom_components/pollenlevels/translations/zh-Hans.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+14/-13 
en.json
English translation updates for enhanced configuration and error
handling

custom_components/pollenlevels/translations/en.json

  • Enhanced user setup description with links to API key documentation
    and best practices
  • Added new configuration fields: forecast_days and
    create_forecast_sensors
  • Updated error messages to include {error_message} placeholder for
    detailed API responses
  • Added validation error messages for invalid_update_interval and
    invalid_forecast_days
  • Fixed per-day sensor option notation from D+2 to D+1+2 in options flow
+13/-12 
README.md
Documentation updates for configuration options and API setup guidance

README.md

  • Clarified per-day sensor selector options with D+1+2 notation for
    creating both D+1 and D+2 sensors
  • Updated update interval documentation to specify 1–24 hour range
  • Added section on modern Home Assistant selectors with links to Google
    API setup and security best practices
  • Enhanced API key setup instructions with IP address restriction
    recommendations
  • Added troubleshooting section for 403 errors with details on common
    causes
  • Removed color_raw from rich attributes list
+26/-7   
CONTRIBUTING.md
New contributing guidelines for code standards and translations

CONTRIBUTING.md

  • New file establishing contribution guidelines for the project
  • Specifies Python 3.13 compatibility requirement for integration code
  • Documents code formatting standards using Black and Ruff
  • Clarifies translation source of truth as en.json with sync
    requirements
  • Emphasizes coordinator-driven architecture and avoiding blocking I/O
+10/-0   
AGENTS.md
Translation guidance updates for agent instructions           

AGENTS.md

  • Added guidance on translation source of truth being en.json
  • Clarified requirement to keep all locale files in sync without
    strings.json
+1/-0     
Bug fix
1 files
conftest.py
Improved async test detection in pytest configuration       

tests/conftest.py

  • Updated pytest_pyfunc_call() to use inspect.iscoroutinefunction()
    instead of asyncio.iscoroutinefunction() for more reliable async test
    detection
+2/-1     

…in-pollensensor

Handle missing source in device info
…s-before-1.9.0-release

Update CHANGELOG to note diagnostics redaction await (1.9.1)
@gemini-code-assist
Copy link

Summary of Changes

Hello @eXPerience83, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a substantial overhaul of the Pollen Levels integration, focusing on enhancing its robustness, configurability, and maintainability. Key changes include a significant refactoring of the core architecture, the implementation of more resilient API interaction with improved error handling and retry mechanisms, and a more user-friendly configuration experience through updated selectors and clearer validation. Additionally, it streamlines diagnostics for better supportability and includes a breaking change by removing a redundant color attribute.

Highlights

  • Core Refactoring: The integration's core logic has been significantly refactored, moving runtime state to config_entry.runtime_data and extracting the API client and data coordinator into dedicated modules for improved modularity and maintainability.
  • Enhanced Configuration & Validation: The setup and options flows now utilize modern Home Assistant selectors, with robust validation for numeric inputs (update interval, forecast days) and coordinates, preventing malformed requests and providing clearer error messages.
  • Improved API Interaction Robustness: A dedicated API client (GooglePollenApiClient) has been introduced, featuring retry logic with exponential backoff for transient failures and explicit handling of various HTTP error codes (401, 403, 429, 5xx) to enhance resilience.
  • Breaking Change: color_raw Removal: The color_raw attribute has been removed from pollen sensors to reduce state size; users should now rely on color_hex or color_rgb.
  • Migration & Cleanup: A migration path (async_migrate_entry) has been implemented to handle legacy configuration settings, including the removal of http_referer support and normalization of per-day sensor modes. Entity registry cleanup for disabled per-day sensors is also improved.
  • Diagnostics & Logging: Diagnostics data is now more informative yet privacy-conscious, redacting sensitive information like API keys and precise coordinates, and truncating large data lists. Logging for force_update and API errors has been refined.
  • Documentation & Testing: New contribution guidelines have been added, and translation coverage tests have been expanded to ensure consistency across locales and service definitions.
Changelog
  • AGENTS.md
    • Updated contribution guidelines with a new rule regarding translation source of truth.
  • CHANGELOG.md
    • Updated the changelog with detailed entries for versions 1.9.2, 1.9.1, and 1.9.0-rc1, summarizing fixes and changes.
  • CONTRIBUTING.md
    • Added a new file outlining contribution guidelines, including Python version, formatting, and translation practices.
  • README.md
    • Updated documentation to reflect changes in per-day sensor options.
    • Removed color_raw from the attributes list.
    • Clarified update interval ranges.
    • Added API key restriction guidance.
  • custom_components/pollenlevels/init.py
    • Refactored the integration setup to use runtime_data.
    • Added async_migrate_entry for config entry migration.
    • Refined the force_update service to use async_request_refresh and improved error logging.
  • custom_components/pollenlevels/client.py
    • Added a new module defining GooglePollenApiClient to encapsulate API calls, including robust retry logic, error message extraction, and API key redaction.
  • custom_components/pollenlevels/config_flow.py
    • Implemented new Home Assistant selectors for configuration.
    • Added comprehensive validation for numeric inputs and coordinates.
    • Improved API error handling and messaging.
  • custom_components/pollenlevels/const.py
    • Introduced new constants for API timeout, maximum retries, min/max update intervals, and URLs for API key documentation.
    • Added a helper for detecting invalid API key messages.
  • custom_components/pollenlevels/coordinator.py
    • Added a new module for PollenDataUpdateCoordinator, centralizing data fetching and processing, including color normalization, plant code handling, and forecast attribute enrichment.
  • custom_components/pollenlevels/diagnostics.py
    • Updated diagnostics generation to redact sensitive API keys and precise coordinates.
    • Rounded location data.
    • Limited the number of reported data keys for privacy and conciseness.
  • custom_components/pollenlevels/manifest.json
    • Bumped the integration version to 1.9.2.
  • custom_components/pollenlevels/runtime.py
    • Added a new module to define PollenLevelsRuntimeData and PollenLevelsConfigEntry for better type hinting and runtime data management.
  • custom_components/pollenlevels/sensor.py
    • Updated sensor setup to utilize the new runtime_data and ForecastSensorMode enum.
    • Removed the color_raw attribute.
    • Improved device info translation key handling.
  • custom_components/pollenlevels/services.yaml
    • Added name and description fields for the force_update service to improve UI discoverability.
  • custom_components/pollenlevels/translations/ca.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/cs.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/da.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/de.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/en.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/es.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/fi.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/fr.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/hu.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/it.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/nb.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/nl.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/pl.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/pt-BR.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/pt-PT.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/ro.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/ru.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/sv.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/uk.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/zh-Hans.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/translations/zh-Hant.json
    • Updated translation file to include new strings for config flow descriptions, error messages, and service labels.
  • custom_components/pollenlevels/util.py
    • Added extract_error_message to safely parse HTTP error responses.
    • Added normalize_sensor_mode to validate and normalize sensor mode values.
  • pyproject.toml
    • Bumped the project version to 1.9.2.
    • Added pytest.ini_options for test configuration.
  • tests/init.py
    • Added a new file to mark the tests directory as a Python package, preventing import conflicts.
  • tests/conftest.py
    • Updated the pytest_pyfunc_call hook to use inspect.iscoroutinefunction for better compatibility.
  • tests/test_config_flow.py
    • Enhanced test stubs for Home Assistant components.
    • Added new tests for API key validation, update interval, forecast days, and error message handling in the config flow.
  • tests/test_diagnostics.py
    • Added a new test file to verify the correct redaction and formatting of diagnostic data.
  • tests/test_init.py
    • Expanded test coverage for the __init__.py module, including tests for coordinate validation, config entry migration, and the force_update service.
  • tests/test_options_flow.py
    • Added new tests to validate update interval and forecast days handling within the options flow.
  • tests/test_sensor.py
    • Updated test stubs and added new tests for coordinator data handling, color channel normalization, plant code matching, and entity cleanup logic.
  • tests/test_translations.py
    • Updated translation tests to include checks for new service definitions and helper error keys, ensuring comprehensive coverage.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@qodo-code-review
Copy link

qodo-code-review bot commented Feb 14, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Unstructured logging: New log statements use standard formatted string logging rather than structured (e.g.,
JSON) logging as required by the checklist success criteria.

Referred Code
_LOGGER.debug("Validating Pollen API (days=%s, lang_set=%s)", 1, bool(lang))

async with session.get(
    url,
    params=params,
    timeout=aiohttp.ClientTimeout(total=POLLEN_API_TIMEOUT),
) as resp:
    status = resp.status
    if status != 200:
        _LOGGER.debug("Validation HTTP %s (body omitted)", status)
        raw_msg = await extract_error_message(resp, f"HTTP {status}")
        placeholders["error_message"] = redact_api_key(raw_msg, api_key)
        if status == 401:
            errors["base"] = "invalid_auth"
        elif status == 403:
            if is_invalid_api_key_message(raw_msg):
                errors["base"] = "invalid_auth"
            else:
                errors["base"] = "cannot_connect"
        elif status == 429:
            errors["base"] = "quota_exceeded"


 ... (clipped 65 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Audit scope unclear: The diff shows operational logging for validation and errors but does not demonstrate
audit-trail style logging with user identity and outcomes for critical actions, which may
be required depending on system audit requirements.

Referred Code
    description_placeholders: dict[str, Any] | None = None,
) -> tuple[dict[str, str], dict[str, Any] | None]:
    """Validate user or reauth input and return normalized data."""
    placeholders = (
        description_placeholders if description_placeholders is not None else {}
    )
    errors: dict[str, str] = {}
    normalized: dict[str, Any] = dict(user_input)
    normalized.pop(CONF_NAME, None)
    normalized.pop(CONF_LOCATION, None)

    api_key = str(user_input.get(CONF_API_KEY, "")) if user_input else ""
    api_key = api_key.strip()

    if not api_key:
        errors[CONF_API_KEY] = "empty"
        return errors, None

    interval_value, interval_error = _parse_update_interval(
        normalized.get(CONF_UPDATE_INTERVAL),
        default=DEFAULT_UPDATE_INTERVAL,


 ... (clipped 180 lines)

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

qodo-code-review bot commented Feb 14, 2026

PR Code Suggestions ✨

Latest suggestions up to 4edfc09

CategorySuggestion                                                                                                                                    Impact
Possible issue
Skip disabled per-day sensors

Add checks to prevent creating _d1 and _d2 forecast sensors if they are disabled
by the current configuration options (allow_d1 and allow_d2).

custom_components/pollenlevels/sensor.py [191-195]

 sensors: list[CoordinatorEntity] = []
 for code in coordinator.data:
     if code in ("region", "date"):
         continue
+    if code.endswith("_d1") and not allow_d1:
+        continue
+    if code.endswith("_d2") and not allow_d2:
+        continue
     sensors.append(PollenSensor(coordinator, code))

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies and fixes a bug where forecast sensors disabled via options would be immediately re-created after being removed, rendering the cleanup logic ineffective.

Medium
Fix async/sync mismatch in coordinator stub method

Align the _StubCoordinator.async_request_refresh method signature with the
synchronous behavior of the production _StubDataUpdateCoordinator by removing
async and returning a future.

tests/test_init.py [600-601]

-async def async_request_refresh(self):
-        await self._mark()
+def async_request_refresh(self):
+        return asyncio.ensure_future(self._mark())
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a signature mismatch between the test stub and the production code's stub, improving test accuracy by ensuring the mock behaves like the real object.

Medium
Restore missing language error key

Restore the invalid_language translation key in the config.error block. This
prevents potential missing translation errors from legacy code paths.

custom_components/pollenlevels/translations/en.json [25-36]

 "error": {
   "invalid_auth": "Invalid API key\n\n{error_message}",
   "cannot_connect": "Unable to connect to the pollen service.\n\n{error_message}",
   "quota_exceeded": "Quota exceeded\n\n{error_message}",
+  "invalid_language": "Invalid language code\n\n{error_message}",
   "invalid_language_format": "Use a canonical BCP-47 code such as \"en\" or \"es-ES\".",
   "empty": "This field cannot be empty",
   "invalid_option_combo": "Increase 'Forecast days' to cover selected per-day sensors.",
   "invalid_coordinates": "Please select a valid location on the map.",
   "unknown": "Unknown error",
   "invalid_update_interval": "Update interval must be between 1 and 24 hours.",
   "invalid_forecast_days": "Forecast days must be between 1 and 5."
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why: This is a valid defensive suggestion to prevent untranslated errors if the invalid_language key is still used, but it's likely the key was intentionally removed.

Low
Restore removed options error keys

Restore the invalid_auth, cannot_connect, quota_exceeded, and invalid_language
keys to the options.error block. This ensures UI errors remain stable if these
validation errors occur.

custom_components/pollenlevels/translations/en.json [53-63]

 "options": {
   "step": {
     "init": {
       "title": "Pollen Levels – Options",
       "description": "Change the update interval, API language, forecast days and per-day TYPE sensors for {title}.\nOptions for per-day TYPE sensors: Only today (none), Through tomorrow (D+1), Through day after tomorrow (D+2; creates both D+1 and D+2 sensors).",
       "data": {
         "update_interval": "Update interval (hours)",
         "language_code": "API response language code",
         "forecast_days": "Forecast days (1–5)",
         "create_forecast_sensors": "Per-day TYPE sensors range"
       }
     }
   },
   "error": {
+    "invalid_auth": "Invalid API key\n\n{error_message}",
+    "cannot_connect": "Unable to connect to the pollen service.\n\n{error_message}",
+    "quota_exceeded": "Quota exceeded\n\n{error_message}",
+    "invalid_language": "Invalid language code\n\n{error_message}",
     "invalid_language_format": "Use a canonical BCP-47 code such as \"en\" or \"es-ES\".",
     "empty": "This field cannot be empty",
     "invalid_option_combo": "Increase 'Forecast days' to cover selected per-day sensors.",
     "unknown": "Unknown error",
     "invalid_update_interval": "Update interval must be between 1 and 24 hours.",
     "invalid_forecast_days": "Forecast days must be between 1 and 5."
   }
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why: This is a reasonable defensive suggestion to ensure UI stability, but it's likely the keys were removed because they are not expected to be used in the options flow.

Low
General
Handle asyncio.TimeoutError for broader compatibility

Handle asyncio.TimeoutError in addition to TimeoutError for better
compatibility, as aiohttp raises the former.

custom_components/pollenlevels/client.py [187-196]

-except TimeoutError as err:
+except (TimeoutError, asyncio.TimeoutError) as err:
             if attempt < max_retries:
                 await self._async_backoff(
                     attempt=attempt,
                     max_retries=max_retries,
                     message=(
                         "Pollen API timeout — retrying in %.2fs " "(attempt %d/%d)"
                     ),
                 )
                 continue

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that aiohttp raises asyncio.TimeoutError and adds it to the except block, which improves error handling robustness across different Python versions.

Medium
  • More

Previous suggestions

✅ Suggestions up to commit 20250be
CategorySuggestion                                                                                                                                    Impact
Possible issue
Clear runtime state on failure
Suggestion Impact:The setup exception handling was updated to set entry.runtime_data = None when async_forward_entry_setups raises ConfigEntryAuthFailed, ConfigEntryNotReady, or any other exception, ensuring runtime state is cleared on failure.

code diff:

     try:
         await hass.config_entries.async_forward_entry_setups(entry, ["sensor"])
-    except ConfigEntryAuthFailed:
-        raise
-    except ConfigEntryNotReady:
+    except (ConfigEntryAuthFailed, ConfigEntryNotReady):
+        entry.runtime_data = None
         raise
     except Exception as err:
+        entry.runtime_data = None
         _LOGGER.exception("Error forwarding entry setups: %s", err)
         raise ConfigEntryNotReady from err

In async_setup_entry, clear entry.runtime_data within the except block for
async_forward_entry_setups to prevent leaving a partially initialized state on
failure.

custom_components/pollenlevels/init.py [277-287]

 entry.runtime_data = PollenLevelsRuntimeData(coordinator=coordinator, client=client)
 
 try:
     await hass.config_entries.async_forward_entry_setups(entry, ["sensor"])
-except ConfigEntryAuthFailed:
+except Exception:
+    entry.runtime_data = None
     raise
-except ConfigEntryNotReady:
-    raise
-except Exception as err:
-    _LOGGER.exception("Error forwarding entry setups: %s", err)
-    raise ConfigEntryNotReady from err

[Suggestion processed]

Suggestion importance[1-10]: 8

__

Why: This is a critical fix for ensuring system stability by correctly managing state during setup failures, preventing potential runtime errors from a partially initialized component.

Medium
Prevent crashes on invalid numbers
Suggestion Impact:The commit imports safe_parse_int and uses it to parse forecast_days with a None fallback to MIN_FORECAST_DAYS before clamping, removing the unsafe int() cast.

code diff:

@@ -19,7 +19,7 @@
     MAX_FORECAST_DAYS,
     MIN_FORECAST_DAYS,
 )
-from .util import redact_api_key
+from .util import redact_api_key, safe_parse_int
 
 if TYPE_CHECKING:
     from homeassistant.core import HomeAssistant
@@ -130,9 +130,10 @@
         self.entry_id = entry_id
         self.entry_title = entry_title or DEFAULT_ENTRY_TITLE
         # Clamp defensively for legacy/manual entries to supported range.
-        self.forecast_days = max(
-            MIN_FORECAST_DAYS, min(MAX_FORECAST_DAYS, int(forecast_days))
-        )
+        parsed_days = safe_parse_int(forecast_days)
+        if parsed_days is None:
+            parsed_days = MIN_FORECAST_DAYS
+        self.forecast_days = max(MIN_FORECAST_DAYS, min(MAX_FORECAST_DAYS, parsed_days))

Replace the direct int() cast on forecast_days with the safe_parse_int helper to
prevent crashes from invalid or non-integer values during coordinator
initialization.

custom_components/pollenlevels/coordinator.py [133-135]

+parsed_days = safe_parse_int(forecast_days)
+if parsed_days is None:
+    parsed_days = MIN_FORECAST_DAYS
+
 self.forecast_days = max(
-    MIN_FORECAST_DAYS, min(MAX_FORECAST_DAYS, int(forecast_days))
+    MIN_FORECAST_DAYS, min(MAX_FORECAST_DAYS, parsed_days)
 )

[Suggestion processed]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that int(forecast_days) can cause a crash with invalid input, and proposes using the safe_parse_int helper for robust parsing, which aligns with the defensive coding practice already present in the PR.

Medium
Prevent global aiohttp clobbering
Suggestion Impact:The test stub logic was updated to preserve an existing aiohttp module in sys.modules, conditionally set stub attributes only if absent, and only insert the stub into sys.modules when aiohttp was not already present.

code diff:

-aiohttp_mod = sys.modules.get("aiohttp") or types.ModuleType("aiohttp")
+aiohttp_existing = sys.modules.get("aiohttp")
+aiohttp_mod = aiohttp_existing or types.ModuleType("aiohttp")
 
 
 class _StubClientError(Exception):
@@ -244,10 +245,14 @@
         self.total = total
 
 
-aiohttp_mod.ClientError = _StubClientError
-aiohttp_mod.ClientSession = _StubClientSession
-aiohttp_mod.ClientTimeout = _StubClientTimeout
-sys.modules["aiohttp"] = aiohttp_mod
+if not hasattr(aiohttp_mod, "ClientError"):
+    aiohttp_mod.ClientError = _StubClientError
+if not hasattr(aiohttp_mod, "ClientSession"):
+    aiohttp_mod.ClientSession = _StubClientSession
+if not hasattr(aiohttp_mod, "ClientTimeout"):
+    aiohttp_mod.ClientTimeout = _StubClientTimeout
+if aiohttp_existing is None:
+    sys.modules["aiohttp"] = aiohttp_mod

To improve test isolation, avoid overwriting the global aiohttp module. Instead,
only register the stub if aiohttp is not already in sys.modules, and patch
attributes only if they are missing.

tests/test_sensor.py [231-250]

-aiohttp_mod = sys.modules.get("aiohttp") or types.ModuleType("aiohttp")
+aiohttp_existing = sys.modules.get("aiohttp")
+aiohttp_mod = aiohttp_existing or types.ModuleType("aiohttp")
 
 
 class _StubClientError(Exception):
     pass
 
 
 class _StubClientSession:  # pragma: no cover - structure only
     pass
 
 
 class _StubClientTimeout:
     def __init__(self, total: float | None = None):
         self.total = total
 
 
-aiohttp_mod.ClientError = _StubClientError
-aiohttp_mod.ClientSession = _StubClientSession
-aiohttp_mod.ClientTimeout = _StubClientTimeout
-sys.modules["aiohttp"] = aiohttp_mod
+if not hasattr(aiohttp_mod, "ClientError"):
+    aiohttp_mod.ClientError = _StubClientError
+if not hasattr(aiohttp_mod, "ClientSession"):
+    aiohttp_mod.ClientSession = _StubClientSession
+if not hasattr(aiohttp_mod, "ClientTimeout"):
+    aiohttp_mod.ClientTimeout = _StubClientTimeout
 
+if aiohttp_existing is None:
+    sys.modules["aiohttp"] = aiohttp_mod
+

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that unconditionally overwriting sys.modules["aiohttp"] can cause side effects in other tests, and proposes a more robust way to stub the module, improving test isolation.

Low
Reject non-integer day values
Suggestion Impact:Replaced float-based parsing/int-casting with an integer-safe parser (safe_parse_int) using the raw option value, defaulting to coordinator.forecast_days when parsing fails and logging a warning for invalid values.

code diff:

@@ -52,6 +52,7 @@
 )
 from .coordinator import PollenDataUpdateCoordinator
 from .runtime import PollenLevelsConfigEntry, PollenLevelsRuntimeData
+from .util import safe_parse_int
 
 _LOGGER = logging.getLogger(__name__)
 
@@ -156,13 +157,16 @@
     coordinator = runtime.coordinator
 
     opts = config_entry.options or {}
-    try:
-        val = float(opts.get(CONF_FORECAST_DAYS, coordinator.forecast_days))
-        if val != val or val in (float("inf"), float("-inf")):
-            raise ValueError
-        forecast_days = int(val)
-    except (TypeError, ValueError, OverflowError):
-        forecast_days = coordinator.forecast_days
+    raw_days = opts.get(CONF_FORECAST_DAYS, coordinator.forecast_days)
+    parsed = safe_parse_int(raw_days)
+    if parsed is None:
+        _LOGGER.warning(
+            "Invalid forecast_days '%s' for entry %s; defaulting to %s",
+            raw_days,
+            config_entry.entry_id,
+            coordinator.forecast_days,
+        )
+    forecast_days = parsed if parsed is not None else coordinator.forecast_days
     forecast_days = max(MIN_FORECAST_DAYS, min(MAX_FORECAST_DAYS, forecast_days))

To prevent silent truncation of non-integer inputs, parse forecast_days as an
integer. Reject non-integer values and fall back to the coordinator's default.

custom_components/pollenlevels/sensor.py [158-168]

 opts = config_entry.options or {}
+raw_days = opts.get(CONF_FORECAST_DAYS, coordinator.forecast_days)
 try:
-    val = float(opts.get(CONF_FORECAST_DAYS, coordinator.forecast_days))
-    if val != val or val in (float("inf"), float("-inf")):
+    if isinstance(raw_days, bool):
         raise ValueError
-    forecast_days = int(val)
+
+    if isinstance(raw_days, str):
+        raw_days = raw_days.strip()
+        if not raw_days:
+            raise ValueError
+
+    if isinstance(raw_days, float):
+        if raw_days != raw_days or raw_days in (float("inf"), float("-inf")):
+            raise ValueError
+        if not raw_days.is_integer():
+            raise ValueError
+
+    forecast_days = int(raw_days)
 except (TypeError, ValueError, OverflowError):
     forecast_days = coordinator.forecast_days
+
 forecast_days = max(MIN_FORECAST_DAYS, min(MAX_FORECAST_DAYS, forecast_days))
 create_d1 = coordinator.create_d1
 create_d2 = coordinator.create_d2

[Suggestion processed]

Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out that parsing forecast_days via float can lead to silent truncation, but the proposed fix is overly complex and the current implementation is reasonable for handling configuration values.

Low
General
Clarify per-day sensor option text
Suggestion Impact:Updated the English UI description string to use "D+2; creates both D+1 and D+2 sensors" instead of "D+1+2", matching the suggested clarification.

code diff:

@@ -44,7 +44,7 @@
     "step": {
       "init": {
         "title": "Pollen Levels – Options",
-        "description": "Change the update interval, API language, forecast days and per-day TYPE sensors for {title}.\nOptions for per-day TYPE sensors: Only today (none), Through tomorrow (D+1), Through day after tomorrow (D+1+2).",
+        "description": "Change the update interval, API language, forecast days and per-day TYPE sensors for {title}.\nOptions for per-day TYPE sensors: Only today (none), Through tomorrow (D+1), Through day after tomorrow (D+2; creates both D+1 and D+2 sensors).",

Clarify the per-day sensor option text in the UI, as the label D+1+2 is
confusing for users and inconsistent with the description "Through day after
tomorrow".

custom_components/pollenlevels/translations/en.json [44-54]

 "step": {
   "init": {
     "title": "Pollen Levels – Options",
-    "description": "Change the update interval, API language, forecast days and per-day TYPE sensors for {title}.\nOptions for per-day TYPE sensors: Only today (none), Through tomorrow (D+1), Through day after tomorrow (D+1+2).",
+    "description": "Change the update interval, API language, forecast days and per-day TYPE sensors for {title}.\nOptions for per-day TYPE sensors: Only today (none), Through tomorrow (D+1), Through day after tomorrow (D+2; creates both D+1 and D+2 sensors).",
     "data": {
       "update_interval": "Update interval (hours)",
       "language_code": "API response language code",
       "forecast_days": "Forecast days (1–5)",
       "create_forecast_sensors": "Per-day TYPE sensors range"
     }
   }
 }
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies that the UI text D+1+2 is confusing and inconsistent with the previous D+2, improving user experience by proposing clearer wording.

Low
✅ Suggestions up to commit 1df2375
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix force update scheduling

Fix a runtime error in the force_update service handler. async_request_refresh
is not awaitable and should not be used with asyncio.gather.

custom_components/pollenlevels/init.py [130-164]

-tasks: list[Awaitable[None]] = []
-task_entries: list[ConfigEntry] = []
 for entry in entries:
     runtime = getattr(entry, "runtime_data", None)
     coordinator = getattr(runtime, "coordinator", None)
     if coordinator:
-        tasks.append(coordinator.async_request_refresh())
-        task_entries.append(entry)
+        coordinator.async_request_refresh()
     else:
         _LOGGER.debug(
             "Skipping force_update for entry %s (no coordinator)",
             entry.entry_id,
         )
 
-if not tasks:
-    _LOGGER.debug("No coordinators available for force_update")
-    return
-
-results = await asyncio.gather(*tasks, return_exceptions=True)
-for entry, result in zip(task_entries, results, strict=False):
-    if isinstance(result, asyncio.CancelledError):
-        _LOGGER.debug(
-            "Manual refresh cancelled for entry %s",
-            entry.entry_id,
-        )
-        continue
-    if isinstance(result, Exception):
-        api_key = (entry.data or {}).get(CONF_API_KEY)
-        safe_message = redact_api_key(result, api_key)
-        _LOGGER.warning(
-            "Manual refresh failed for entry %s (%s): %s",
-            entry.entry_id,
-            type(result).__name__,
-            safe_message or "no error details",
-        )
-
Suggestion importance[1-10]: 10

__

Why: This suggestion correctly identifies a critical runtime bug where a non-awaitable function is passed to asyncio.gather, which would cause the force_update service to fail.

High
Stabilize plant sensor identifiers

Use the normalized _norm_code for the plant sensor key to ensure stable and
unique entity IDs, preventing potential collisions and lookup failures.

custom_components/pollenlevels/coordinator.py [306-337]

 for _norm_code, pitem in sorted(plant_by_day_code[0].items()):
-    # NOTE: plant_by_day_code[0] is built using normalized, non-empty plant codes as keys,
-    # so `_norm_code` is guaranteed to be a stable non-empty identifier.
-    # We still derive `code` from the raw API field (stripped) for attributes, while
-    # using lowercased `code` for the sensor key to keep entity creation deterministic.
     idx_raw = pitem.get("indexInfo")
     idx = idx_raw if isinstance(idx_raw, dict) else {}
     desc_raw = pitem.get("plantDescription")
     desc = desc_raw if isinstance(desc_raw, dict) else {}
     rgb = _rgb_from_api(idx.get("color"))
     raw_code = pitem.get("code")
     code = str(raw_code).strip() if raw_code is not None else ""
-    key = f"plants_{code.lower()}"
+    key = f"plants_{_norm_code.lower()}"
     new_data[key] = {
         "source": "plant",
         "value": idx.get("value"),
         "category": idx.get("category"),
-        "displayName": pitem.get("displayName", code),
-        "code": code,
+        "displayName": pitem.get("displayName", code or _norm_code),
+        "code": code or _norm_code,
         "inSeason": pitem.get("inSeason"),
         "type": desc.get("type"),
         "family": desc.get("family"),
         "season": desc.get("season"),
         "cross_reaction": desc.get("crossReaction"),
         "description": idx.get("indexDescription"),
         "advice": pitem.get("healthRecommendations"),
         "color_hex": _rgb_to_hex_triplet(rgb),
         "color_rgb": list(rgb) if rgb is not None else None,
         "picture": desc.get("picture"),
         "picture_closeup": desc.get("pictureCloseup"),
     }
     plant_keys.append(key)
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a potential bug where using a non-normalized code for the sensor key could lead to unstable or colliding entity IDs, which is a critical issue for Home Assistant entity management.

Medium
Reject whitespace API keys
Suggestion Impact:Updated API key validation to check for a string and non-whitespace content, and strip surrounding whitespace before use; error message changed to "Invalid API key".

code diff:

     api_key = entry.data.get(CONF_API_KEY)
-    if not api_key:
-        raise ConfigEntryAuthFailed("Missing API key")
+    if not isinstance(api_key, str) or not api_key.strip():
+        raise ConfigEntryAuthFailed("Invalid API key")
+    api_key = api_key.strip()

Improve API key validation by rejecting blank or whitespace-only keys during
entry setup to prevent later network failures.

custom_components/pollenlevels/init.py [217-219]

 api_key = entry.data.get(CONF_API_KEY)
-if not api_key:
+if not isinstance(api_key, str) or not api_key.strip():
     raise ConfigEntryAuthFailed("Missing API key")
+api_key = api_key.strip()

[Suggestion processed]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that whitespace-only API keys are not handled, which would lead to an avoidable API error. This improves input validation and user experience.

Medium
Incremental [*]
Reject non-integer float inputs

In _parse_int_option, add a check to reject non-integer float values to prevent
silent truncation and ensure user input is handled as expected.

custom_components/pollenlevels/config_flow.py [218-229]

 def _parse_int_option(
     value: Any,
     default: int,
     *,
     min_value: int | None = None,
     max_value: int | None = None,
     error_key: str | None = None,
 ) -> tuple[int, str | None]:
     """Parse a numeric option to int and enforce bounds."""
+    if isinstance(value, float) and not value.is_integer():
+        return default, error_key
+
     parsed = safe_parse_int(value if value is not None else default)
     if parsed is None:
         return default, error_key
Suggestion importance[1-10]: 7

__

Why: This suggestion correctly identifies a potential issue where non-integer float values from UI widgets could be silently truncated. Adding a check to reject such values improves the robustness of the input validation.

Medium
Security
Mask API key input field
Suggestion Impact:The API key field in the initial setup schema was changed from a plain string to a password TextSelector, masking the value in the UI. Additionally, the re-auth schema was also updated to use a password selector and not prefill the existing key.

code diff:

@@ -149,7 +149,9 @@
 
     schema = vol.Schema(
         {
-            vol.Required(CONF_API_KEY): str,
+            vol.Required(CONF_API_KEY): TextSelector(
+                TextSelectorConfig(type=TextSelectorType.PASSWORD)
+            ),
             vol.Required(CONF_NAME, default=default_name): str,
             location_field: LocationSelector(LocationSelectorConfig(radius=False)),
             vol.Optional(
@@ -563,8 +565,8 @@
             {
                 vol.Required(
                     CONF_API_KEY,
-                    default=self._reauth_entry.data.get(CONF_API_KEY, ""),
-                ): str
+                    default="",
+                ): TextSelector(TextSelectorConfig(type=TextSelectorType.PASSWORD))
             }

Use a password input field for the API key in the initial setup form to mask the
sensitive value, consistent with the re-authentication flow.

custom_components/pollenlevels/config_flow.py [150-192]

 schema = vol.Schema(
     {
-        vol.Required(CONF_API_KEY): str,
+        vol.Required(CONF_API_KEY): TextSelector(
+            TextSelectorConfig(type=TextSelectorType.PASSWORD)
+        ),
         vol.Required(CONF_NAME, default=default_name): str,
         location_field: LocationSelector(LocationSelectorConfig(radius=False)),
         vol.Optional(
             CONF_UPDATE_INTERVAL,
             default=interval_default,
         ): NumberSelector(
             NumberSelectorConfig(
                 min=MIN_UPDATE_INTERVAL_HOURS,
                 max=MAX_UPDATE_INTERVAL_HOURS,
                 step=1,
                 mode=NumberSelectorMode.BOX,
                 unit_of_measurement="h",
             )
         ),
         ...
     }
 )
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies an exposed API key in the UI and proposes using a password field, which is a good security practice to prevent accidental exposure.

Medium
Suggestions up to commit 87dc783
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix invalid timezone constant

Fix an AttributeError in tests by replacing the invalid dt.UTC constant with the
correct dt.timezone.utc for timezone-aware datetimes.

tests/test_diagnostics.py [105]

-last_updated=dt.datetime(2025, 1, 1, tzinfo=dt.UTC),
+last_updated=dt.datetime(2025, 1, 1, tzinfo=dt.timezone.utc),
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies that dt.UTC is not a valid attribute and will cause a runtime AttributeError, which breaks the tests.

High
Use current options for sensor creation

In async_setup_entry, determine create_d1 and create_d2 flags from
config_entry.options instead of the coordinator to ensure sensor creation
reflects the latest settings.

custom_components/pollenlevels/sensor.py [145-172]

 async def async_setup_entry(
     hass: HomeAssistant,
     config_entry: PollenLevelsConfigEntry,
     async_add_entities: AddEntitiesCallback,
 ) -> None:
     """Create coordinator and build sensors."""
     runtime = cast(
         PollenLevelsRuntimeData | None, getattr(config_entry, "runtime_data", None)
     )
     if runtime is None:
         raise ConfigEntryNotReady("Runtime data not ready")
     coordinator = runtime.coordinator
 
     opts = config_entry.options or {}
     try:
         val = float(opts.get(CONF_FORECAST_DAYS, coordinator.forecast_days))
         if val != val or val in (float("inf"), float("-inf")):
             raise ValueError
         forecast_days = int(val)
     except (TypeError, ValueError, OverflowError):
         forecast_days = coordinator.forecast_days
     forecast_days = max(MIN_FORECAST_DAYS, min(MAX_FORECAST_DAYS, forecast_days))
-    create_d1 = coordinator.create_d1
-    create_d2 = coordinator.create_d2
+
+    mode = opts.get(
+        "create_forecast_sensors",
+        ForecastSensorMode.NONE,
+    )
+    create_d1 = mode in (ForecastSensorMode.D1, ForecastSensorMode.D1_D2)
+    create_d2 = mode == ForecastSensorMode.D1_D2
 
     allow_d1 = create_d1 and forecast_days >= 2
     allow_d2 = create_d2 and forecast_days >= 3
 ...
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that sensor creation logic uses stale coordinator state instead of current config options, which is a bug, and provides a correct fix.

Medium
Fix reading response body once

Refactor extract_error_message to read the response body only once. First read
the body as text, then attempt to parse it as JSON to avoid issues with
consuming the response stream multiple times.

custom_components/pollenlevels/util.py [16-49]

 async def extract_error_message(resp: ClientResponse, default: str = "") -> str:
     """Extract and normalize an HTTP error message without secrets."""
+    import json
 
     message: str | None = None
+    text: str | None = None
     try:
-        try:
-            json_obj = await resp.json(content_type=None)
-        except TypeError:
-            json_obj = await resp.json()
+        text = await resp.text()
+        json_obj = json.loads(text)
         if isinstance(json_obj, dict):
             error = json_obj.get("error")
             if isinstance(error, dict):
                 raw_msg = error.get("message")
                 if isinstance(raw_msg, str):
                     message = raw_msg
+    except (json.JSONDecodeError, TypeError):
+        # Not a JSON response or json.loads failed, fallback to text
+        pass
     except Exception:  # noqa: BLE001
+        # Other exceptions during text() or parsing
         message = None
 
-    if not message:
-        try:
-            text = await resp.text()
-            if isinstance(text, str):
-                message = text
-        except Exception:  # noqa: BLE001
-            message = None
+    if not message and text:
+        message = text
 
     normalized = " ".join(
         (message or "").replace("\r", " ").replace("\n", " ").split()
     ).strip()
 
     if len(normalized) > 300:
         normalized = normalized[:300]
 
     return normalized or default
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a bug where the response body could be read multiple times, leading to missed error messages, and provides a robust fix.

Medium
Use current options during re-authentication

In async_step_reauth_confirm, merge self._reauth_entry.options into
combined_input to ensure validation uses the most current configuration values,
preventing failures from stale data.

custom_components/pollenlevels/config_flow.py [538-578]

 async def async_step_reauth_confirm(self, user_input: dict[str, Any] | None = None):
     """Prompt for a refreshed API key and validate it."""
 
     assert self._reauth_entry is not None
 
     errors: dict[str, str] = {}
     placeholders = {
         "latitude": f"{self._reauth_entry.data.get(CONF_LATITUDE)}",
         "longitude": f"{self._reauth_entry.data.get(CONF_LONGITUDE)}",
         "api_key_url": POLLEN_API_KEY_URL,
         "restricting_api_keys_url": RESTRICTING_API_KEYS_URL,
     }
 
     if user_input:
-        # Combine new API key with existing data for validation
-        combined_input = {**self._reauth_entry.data, **user_input}
+        # Combine new API key with existing data and options for validation
+        combined_input = {
+            **self._reauth_entry.data,
+            **self._reauth_entry.options,
+            **user_input,
+        }
         errors, normalized = await self._async_validate_input(
             combined_input, description_placeholders=placeholders
         )
 
         if not errors:
             # Update the entry with the new key and reload
             self.hass.config_entries.async_update_entry(
                 self._reauth_entry, data=normalized
             )
             await self.hass.config_entries.async_reload(self._reauth_entry.entry_id)
             return self.async_abort(reason="reauth_successful")
 
     schema = vol.Schema(
         {
             vol.Required(CONF_API_KEY): TextSelector(
                 TextSelectorConfig(type=TextSelectorType.PASSWORD)
             )
         }
     )
 
-    # Ensure the form posts back to this handler.
     return self.async_show_form(
         step_id="reauth_confirm",
         data_schema=schema,
         errors=errors,
         description_placeholders=placeholders,
     )
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential bug in the re-authentication flow where stale configuration data could cause validation to fail, and the proposed fix is accurate.

Medium
Reject negative color channel values

Explicitly reject negative color channel values in _normalize_channel by
returning None instead of silently clamping them to zero.

custom_components/pollenlevels/coordinator.py [31-44]

     def _normalize_channel(v: Any) -> int | None:
         """Normalize a single channel to 0..255 (accept 0..1 or 0..255 inputs).
     
         Returns None if the value cannot be interpreted as a number.
         """
         try:
             f = float(v)
         except (TypeError, ValueError, OverflowError):
             return None
-        if not math.isfinite(f):
+        if not math.isfinite(f) or f < 0:
             return None
         if 0.0 <= f <= 1.0:
             f *= 255.0
         return max(0, min(255, int(round(f))))
Suggestion importance[1-10]: 5

__

Why: The suggestion's reasoning about the final return value is incorrect due to the max(0, ...) clamp, but its core point about explicitly rejecting invalid negative inputs instead of silently clamping them is valid and improves the function's robustness.

Low
General
Enforce strict zip for AST parsing

In _fields_from_schema_dict, change zip(..., strict=False) to zip(...,
strict=True) to make the AST parsing more robust and fail on mismatched keys and
values.

tests/test_translations.py [412-423]

     def _fields_from_schema_dict(
         schema_dict: ast.Dict, mapping: dict[str, str]
     ) -> tuple[set[str], set[str]]:
         """Extract field keys and section IDs from an AST dict representing a schema."""
     
         fields: set[str] = set()
         sections: set[str] = set()
-        for key_node, value_node in zip(schema_dict.keys, schema_dict.values, strict=False):
+        for key_node, value_node in zip(schema_dict.keys, schema_dict.values, strict=True):
             if isinstance(key_node, ast.Name):
                 resolved = _resolve_name(key_node.id, mapping)
                 if resolved:
 ...
Suggestion importance[1-10]: 4

__

Why: This is a good practice that improves test robustness by ensuring the test fails explicitly on malformed AST nodes rather than silently truncating data, which could hide issues.

Low
Improve API key error detection

Improve the is_invalid_api_key_message function by normalizing the error
message. Remove punctuation and extra whitespace before checking for signal
phrases to make detection more reliable.

custom_components/pollenlevels/const.py [41-55]

 def is_invalid_api_key_message(message: str | None) -> bool:
     """Return True if *message* strongly indicates an invalid API key."""
+    import string
 
     if not message:
         return False
 
-    msg = message.casefold()
+    # Normalize by removing punctuation and collapsing whitespace
+    translator = str.maketrans("", "", string.punctuation)
+    normalized_msg = " ".join(message.casefold().translate(translator).split())
+
     signals = (
         "api key not valid",
         "invalid api key",
-        "api_key_invalid",
+        "api key invalid",  # "api_key_invalid" becomes this
         "apikeynotvalid",
         "api key is not valid",
     )
-    return any(signal in msg for signal in signals)
+    return any(signal in normalized_msg for signal in signals)
Suggestion importance[1-10]: 4

__

Why: This suggestion improves the robustness of the API key error detection by normalizing the message, making it less prone to variations in punctuation and spacing.

Low

@eXPerience83
Copy link
Owner Author

/gemini review

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This is a massive and impressive pull request that significantly refactors the integration, improves validation, error handling, and test coverage. The move to separate client and coordinator modules, along with the adoption of runtime data storage, aligns the integration with modern Home Assistant best practices. The expanded test suite and thorough translation updates are also commendable. I found one minor opportunity for code cleanup.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This is a massive and high-quality pull request that significantly refactors and enhances the pollenlevels integration. The changes introduce a more robust architecture by separating the API client and data coordinator, improve configuration validation and error handling, and greatly expand test coverage. The code demonstrates a strong understanding of Home Assistant's best practices. I have one minor suggestion to ensure deterministic behavior in sensor creation.

eXPerience83 and others added 6 commits February 14, 2026 16:03
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@eXPerience83 eXPerience83 changed the title 1.9.2 1.9.3 Feb 14, 2026
@eXPerience83
Copy link
Owner Author

/gemini review

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This is a massive and impressive pull request that significantly refactors the integration for improved robustness, maintainability, and adherence to modern Home Assistant practices. The extraction of the API client and coordinator, the move to per-entry runtime data, and the comprehensive enhancements to validation and error handling are all excellent changes. The expansion of the test suite is particularly noteworthy and provides strong confidence in the stability of this major refactoring. I have one suggestion regarding code duplication for a parsing helper, but overall, this is an exemplary contribution.

@eXPerience83
Copy link
Owner Author

/gemini review

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This is an extensive and high-quality pull request that significantly refactors the integration. The changes, including the extraction of the API client and coordinator, the introduction of a runtime data container, and the complete overhaul of the config flow with improved validation and error handling, are excellent. The test suite has been massively expanded, covering migrations, validation, and even translation key discovery, which is impressive. The code is robust, secure, and follows modern Home Assistant best practices. I have reviewed the changes thoroughly and could not find any issues of medium or higher severity. This is an outstanding contribution.

@qodo-code-review
Copy link

Persistent suggestions updated to latest commit 4edfc09

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.

1 participant

Comments