Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Refactor and simplify using DataUpdateCoordinator #293

Closed
wants to merge 14 commits into from

Conversation

jschlyter
Copy link
Contributor

@jschlyter jschlyter commented Jan 23, 2025

This PRs is a major refactor of sensors and the data updates using DateUpdateCoordinator. This makes the integration more "mainstream" and easier to read, and also fixes issues with unit conversion. There should be no configuration changes required and existing data should be kept as is.

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced multi-vehicle support by introducing a coordinator-based architecture
    • Improved data management and retrieval for Polestar vehicles
  • Bug Fixes

    • Simplified error handling during API setup and data synchronization
    • Refined data update mechanisms for more reliable vehicle information
  • Refactor

    • Restructured internal components to use a more modular coordinator pattern
    • Updated entity management to align with Home Assistant best practices
  • Chores

    • Added attribution for data sourcing
    • Improved type hinting and code clarity

Copy link
Contributor

coderabbitai bot commented Jan 23, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This pull request introduces a significant architectural change in the Polestar API integration for Home Assistant. The core modification shifts from a single-car model to a multi-coordinator model, enabling support for multiple vehicles under one account. The implementation replaces the previous PolestarCar class with a new PolestarCoordinator that manages vehicle data retrieval, error handling, and state updates. The changes span multiple files, updating import statements, class structures, and data management approaches to support this new architecture.

Changes

File Change Summary
__init__.py Replaced single coordinator setup with multi-coordinator approach, added async_reload_entry method
binary_sensor.py Transitioned from PolestarCar to PolestarCoordinator in entity creation and data handling
const.py Added ATTRIBUTION constant
coordinator.py New PolestarCoordinator class with methods for fetching vehicle data and managing API interactions
data.py Updated PolestarData to use api_client and coordinators instead of coordinator and cars
diagnostics.py Updated data retrieval to use new coordinator and API client structure
entity.py Refactored PolestarEntity to inherit from CoordinatorEntity
image.py Modified to use PolestarCoordinator instead of PolestarCar
polestar.py Entire file removed, replacing previous car and coordinator implementations
sensor.py Updated to use PolestarCoordinator for data retrieval and sensor state management
system_health.py Minor type hinting improvements

Sequence Diagram

sequenceDiagram
    participant HA as Home Assistant
    participant API as Polestar API
    participant Coordinator as PolestarCoordinator
    
    HA->>API: Authenticate
    API-->>HA: Return Available VINs
    HA->>HA: Create Coordinators
    loop For Each VIN
        HA->>Coordinator: Initialize
        Coordinator->>API: Fetch Vehicle Data
        API-->>Coordinator: Return Vehicle Information
        Coordinator-->>HA: Update Vehicle State
    end
Loading

Possibly related PRs

Suggested reviewers

  • BJReplay
  • dgomes

Poem

🚗 Coordinators dance, VINs in line,
A Polestar ballet, code so fine!
From single car to multiple streams,
Home Assistant weaves technological dreams
🐰 Hop, hop, integration complete! 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@jschlyter
Copy link
Contributor Author

@coderabbitai review

Copy link
Contributor

coderabbitai bot commented Jan 23, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
custom_components/polestar_api/coordinator.py (2)

95-97: Avoid unnecessary check for zero in battery charge percentage

In lines 95-97, you're checking if battery_charge_level_percentage is not None and not equal to zero:

if (
    data.battery_charge_level_percentage is not None
    and data.battery_charge_level_percentage != 0
    and data.estimated_distance_to_empty_km is not None
):

Since a zero percentage is a valid state (e.g., the battery is completely discharged), consider removing the check data.battery_charge_level_percentage != 0 to handle all valid cases.

Apply this diff to adjust the condition:

-if (
-    data.battery_charge_level_percentage is not None
-    and data.battery_charge_level_percentage != 0
-    and data.estimated_distance_to_empty_km is not None
-):
+if (
+    data.battery_charge_level_percentage is not None
+    and data.estimated_distance_to_empty_km is not None
+    and data.battery_charge_level_percentage > 0
+):

167-172: Catch specific exceptions instead of the broad Exception

In lines 167-172, you're catching all exceptions using except Exception as exc::

except Exception as exc:
    _LOGGER.error(
        "Unexpected error updating data for VIN %s: %s", self.vin, str(exc)
    )
    res["api_connected"] = False
    raise UpdateFailed(exc) from exc

Catching the base Exception class can inadvertently catch unexpected exceptions, including those that are system-exiting. It's recommended to catch specific exceptions to avoid masking other issues.

custom_components/polestar_api/sensor.py (1)

269-271: Consider swapping the order of loops in list comprehension for clarity

Currently, the list comprehension loops over entity_description first, then over coordinator. Swapping the order can make it clearer that sensors are being created for each coordinator's entity descriptions:

 async_add_entities(
     [
-        PolestarSensor(coordinator, entity_description)
-        for entity_description in POLESTAR_SENSOR_TYPES
-        for coordinator in entry.runtime_data.coordinators
+        PolestarSensor(coordinator, entity_description)
+        for coordinator in entry.runtime_data.coordinators
+        for entity_description in POLESTAR_SENSOR_TYPES
     ]
 )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d77b27 and f6b2ee2.

📒 Files selected for processing (11)
  • custom_components/polestar_api/__init__.py (2 hunks)
  • custom_components/polestar_api/binary_sensor.py (3 hunks)
  • custom_components/polestar_api/const.py (1 hunks)
  • custom_components/polestar_api/coordinator.py (1 hunks)
  • custom_components/polestar_api/data.py (1 hunks)
  • custom_components/polestar_api/diagnostics.py (2 hunks)
  • custom_components/polestar_api/entity.py (1 hunks)
  • custom_components/polestar_api/image.py (3 hunks)
  • custom_components/polestar_api/polestar.py (0 hunks)
  • custom_components/polestar_api/sensor.py (4 hunks)
  • custom_components/polestar_api/system_health.py (1 hunks)
💤 Files with no reviewable changes (1)
  • custom_components/polestar_api/polestar.py
✅ Files skipped from review due to trivial changes (1)
  • custom_components/polestar_api/const.py
🔇 Additional comments (14)
custom_components/polestar_api/__init__.py (1)

70-71: Ensure proper cleanup with async_on_unload and add_update_listener

In lines 70-71, you're adding an update listener and registering it for cleanup:

entry.async_on_unload(entry.add_update_listener(async_reload_entry))

Confirm that this pattern correctly unregisters the listener when the config entry is unloaded. If not, consider adjusting it to ensure the listener is properly cleaned up to prevent potential memory leaks.

✅ Verification successful

Pattern correctly handles cleanup of update listener

The implementation follows Home Assistant's standard pattern for automatic cleanup. When the config entry is unloaded, the cleanup function returned by add_update_listener will be automatically called through the async_on_unload registration, ensuring proper removal of the listener.

🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
# Search for similar patterns using ast-grep
ast-grep --pattern 'entry.async_on_unload(entry.add_update_listener($_))'

# Search for variations using ripgrep
echo "=== Searching for similar patterns with ripgrep ==="
rg -U "async_on_unload.*add_update_listener" -A 2 -B 2

# Search for standalone usage of these methods to check for alternative patterns
echo "=== Searching for standalone add_update_listener usage ==="
rg "add_update_listener" -A 2 -B 2

echo "=== Searching for standalone async_on_unload usage ==="
rg "async_on_unload" -A 2 -B 2

Length of output: 1819

custom_components/polestar_api/sensor.py (1)

290-300: ⚠️ Potential issue

Verify Python version compatibility due to use of the match statement

The match statement introduced here requires Python 3.10 or newer. Please ensure that the project supports Python 3.10+ and update the documentation and project requirements if necessary to prevent compatibility issues.

custom_components/polestar_api/data.py (1)

17-20: Ensure immutability of PolestarData does not impact functionality

Adding frozen=True to the @dataclass decorator makes PolestarData immutable. Verify that there are no parts of the codebase that attempt to modify PolestarData instances after creation, as this will now raise FrozenInstanceError.

custom_components/polestar_api/system_health.py (1)

12-13: Type annotations enhance code clarity and static analysis

Adding type annotations for hass in the async_register function improves code readability and assists with static type checking tools.

custom_components/polestar_api/diagnostics.py (2)

5-14: LGTM! Good type checking optimization.

Moving imports inside TYPE_CHECKING block is a good practice to avoid circular imports and reduce runtime overhead.


24-25: LGTM! Clean transition to coordinator pattern.

The change from accessing cars to coordinators aligns well with the PR's objective of refactoring to use DataUpdateCoordinator.

custom_components/polestar_api/binary_sensor.py (4)

3-22: LGTM! Well-organized imports.

Good use of TYPE_CHECKING and clear organization of imports.


45-49: LGTM! Clean entity setup.

The transition to using coordinators in the entity setup is well implemented.


58-62: LGTM! Proper constructor update.

Constructor properly updated to use PolestarCoordinator instead of PolestarCar.


67-67: LGTM! Clean data access.

Data access through coordinator follows the new pattern consistently.

custom_components/polestar_api/image.py (4)

6-18: LGTM! Well-organized imports.

Good use of TYPE_CHECKING and clear organization of imports.


41-43: LGTM! Clean entity setup.

The transition to using coordinators in the entity setup is well implemented.


56-61: LGTM! Proper constructor update.

Constructor properly updated to use PolestarCoordinator instead of PolestarCar.


65-65: LGTM! Clean data access.

Data access through coordinator follows the new pattern consistently.

custom_components/polestar_api/entity.py Outdated Show resolved Hide resolved
@jschlyter jschlyter linked an issue Jan 24, 2025 that may be closed by this pull request
@jschlyter jschlyter closed this Jan 24, 2025
@jschlyter jschlyter deleted the date_update_coordinator branch January 24, 2025 22:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Refactor to use DataUpdateCoordinator
1 participant