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

Add coordinator to 17Track #115057

Merged
merged 24 commits into from
Apr 23, 2024
Merged

Conversation

shaiu
Copy link
Contributor

@shaiu shaiu commented Apr 6, 2024

Proposed change

Add coordinator to 17Track

Type of change

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

Additional information

  • This PR fixes or closes issue: fixes #
  • This PR is related to issue:
  • Link to documentation pull request:

Checklist

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

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

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

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

To help with the load of incoming pull requests:

@gjohansson-ST
Copy link
Member

There are more in here than just adding a DUC. So I would suggest you break out the refactoring and entity descriptions into their own PR's to make it more feasible to review. Thanks

@shaiu
Copy link
Contributor Author

shaiu commented Apr 7, 2024

@gjohansson-ST I removed the SensorEntityDescription
I'm not sure how I can remove more than that in this PR.
This code is a bit complicated as it has "dynamic" sensors added and removed
I plan (together with the help of @joostlek) to refactor this integration to have service calls instead of the "dynamic" sensors in different PRs

homeassistant/components/seventeentrack/coordinator.py Outdated Show resolved Hide resolved
Comment on lines 31 to 40

class SeventeenTrackData:
"""Class for handling the data retrieval."""

def __init__(self) -> None:
"""Initialize the data object."""
self.summary: dict[str, dict[str, Any]] = {}
self.current_packages: dict[str, dict[str, Any]] = {}
self.new_packages: dict[str, Package] = {}
self.old_packages: set[Package] = set()
Copy link
Member

Choose a reason for hiding this comment

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

This can be a dataclass

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added

Comment on lines 84 to 86
current_packages = set(
await self._client.profile.packages(show_archived=self._show_archived)
)
Copy link
Member

Choose a reason for hiding this comment

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

Can this also raise?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Added try catch

Comment on lines 75 to 80
for status, quantity in summary.items():
data.summary[slugify(status)] = {
"quantity": quantity,
"packages": [],
}

Copy link
Member

Choose a reason for hiding this comment

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

Keep things that can't raise out of the try block

Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved

self._status = status
self._attr_name = f"Seventeentrack Packages {status}"
self._attr_unique_id = f"summary_{data.account_id}_{slugify(status)}"
self._attr_extra_state_attributes = {}
Copy link
Member

Choose a reason for hiding this comment

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

Unneeded

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

homeassistant/components/seventeentrack/sensor.py Outdated Show resolved Hide resolved
Comment on lines 203 to 207
package_data = self.coordinator.data.current_packages.get(
self._tracking_number, {}
)
package = package_data.get("package")
if package is None or not (name := package.friendly_name):
Copy link
Member

Choose a reason for hiding this comment

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

Afaik (I know for sure for native value and some other properties, name I am not sure), if the available property returns False, the other properties aren't requested anymore, so we shouldn't have to check again here.

(We can use square brackets instead of get

self.coordinator.data.current_packages[self._tracking_number]
)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved it anyway to init

Comment on lines 214 to 216
package_data = self.coordinator.data.current_packages.get(
self._tracking_number, {}
)
Copy link
Member

Choose a reason for hiding this comment

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

Idem



class SeventeenTrackData:
"""Define a data handler for 17track.net."""
def remove_entity(hass: HomeAssistant, account_id: str, tracking_number: str) -> bool:
Copy link
Member

Choose a reason for hiding this comment

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

Can we maybe just pass the list of packages it should remove so it can do a for loop here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

)
def notify_delivered(hass: HomeAssistant, friendly_name: str, tracking_number: str):
"""Notify when package is delivered."""
LOGGER.info("Package delivered: %s", tracking_number)
Copy link
Member

Choose a reason for hiding this comment

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

Please don't use the info level of logging

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed to debug

@home-assistant
Copy link

home-assistant bot commented Apr 7, 2024

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

@home-assistant home-assistant bot marked this pull request as draft April 7, 2024 08:43
@shaiu shaiu marked this pull request as ready for review April 7, 2024 19:03
@home-assistant home-assistant bot requested a review from joostlek April 7, 2024 19:03
Comment on lines 37 to 42
def __init__(self) -> None:
"""Initialize the data object."""
self.summary: dict[str, dict[str, Any]] = {}
self.current_packages: dict[str, dict[str, Any]] = {}
self.new_packages: dict[str, Package] = {}
self.old_packages: set[Package] = set()
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
def __init__(self) -> None:
"""Initialize the data object."""
self.summary: dict[str, dict[str, Any]] = {}
self.current_packages: dict[str, dict[str, Any]] = {}
self.new_packages: dict[str, Package] = {}
self.old_packages: set[Package] = set()
summary: dict[str, dict[str, Any]]
current_packages: dict[str, dict[str, Any]]

I think it's wise to actually just keep the list of package tracking number of the last update in a set in async_setup_entry in sensor.py. This would make the coordinator less responsible for creating and removing entities. (and it concentrates the logic that we can remove in a few months)

Comment on lines 105 to 117
data.current_packages[package.tracking_number] = {
"package": package,
"extra": {
ATTR_DESTINATION_COUNTRY: package.destination_country,
ATTR_INFO_TEXT: package.info_text,
ATTR_TIMESTAMP: package.timestamp,
ATTR_LOCATION: package.location,
ATTR_ORIGIN_COUNTRY: package.origin_country,
ATTR_PACKAGE_TYPE: package.package_type,
ATTR_TRACKING_INFO_LANGUAGE: package.tracking_info_language,
ATTR_TRACKING_NUMBER: package.tracking_number,
},
}
Copy link
Member

Choose a reason for hiding this comment

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

If I recall correctly, these were the extra state attributes. I would keep that logic at the sensor.

Comment on lines 135 to 143
@property
def show_delivered(self):
"""Return whether delivered packages should be shown."""
return self._show_delivered

@property
def account_id(self):
"""Return the account ID."""
return self._account_id
Copy link
Member

Choose a reason for hiding this comment

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

instead of returning the self._something just assign the variable to self.something in the first place so you dont need to create separate properties

SeventeenTrackPackageSensor(
coordinator, t_number, p_data.friendly_name, p_data.status
)
for t_number, p_data in coordinator.data.new_packages.items()
Copy link
Member

Choose a reason for hiding this comment

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

I'd write out the variables, we write code for developers, not for the computer :)

self._status = status
self._attr_name = f"Seventeentrack Packages {status}"
self._attr_unique_id = f"summary_{data.account_id}_{slugify(status)}"
self._attr_name = f"Seventeentrack Packages {self._status}"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
self._attr_name = f"Seventeentrack Packages {self._status}"
self._attr_name = f"Seventeentrack Packages {status}"


@property
def available(self) -> bool:
"""Return whether the entity is available."""
return self._state is not None
return self.coordinator.data.summary[self._status]["quantity"] is not None
Copy link
Member

Choose a reason for hiding this comment

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

When this is unavalable, is summary[status] even set? Shouldn't it be

Suggested change
return self.coordinator.data.summary[self._status]["quantity"] is not None
return self._status in self.coordinator.data.summary

Comment on lines 194 to 195
name = friendly_name if friendly_name else tracking_number
self._name = f"Seventeentrack Package: {name}"
Copy link
Member

Choose a reason for hiding this comment

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

Why did we move this again?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

there is no need to calculate this each time property name is called.
We have the information at init time

Copy link
Member

Choose a reason for hiding this comment

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

Let's leave these optimizations for a future PR, we can use shorthand attributes for this for example

@home-assistant home-assistant bot marked this pull request as draft April 8, 2024 18:41
@shaiu shaiu marked this pull request as ready for review April 8, 2024 20:21
@home-assistant home-assistant bot requested a review from joostlek April 8, 2024 20:21
)
self.show_delivered = self.config_entry.options[CONF_SHOW_DELIVERED]
self.account_id = client.profile.account_id
self.data = SeventeenTrackData()
Copy link
Member

Choose a reason for hiding this comment

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

Why don't you construct a new one every time?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

Comment on lines 112 to 121

@callback
def _async_create_remove_entities():
old_packages = coordinator.data.current_packages - set(
coordinator.data.live_packages.values()
)
packages_to_add = (
set(coordinator.data.live_packages.values())
- coordinator.data.current_packages
)
Copy link
Member

Choose a reason for hiding this comment

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

You can create a set outside of this function with all the tracking numbers for which you already made sensors, that would simplify the coordinator and this function

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cool. Done

@shaiu shaiu requested a review from joostlek April 8, 2024 21:18
Copy link
Member

@joostlek joostlek left a comment

Choose a reason for hiding this comment

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

I'll look more thorough tomorrow

Comment on lines 59 to 60
summary = {}
live_packages = set()
Copy link
Member

Choose a reason for hiding this comment

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

Can be removed now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

why? I need it when getting the packages from the API.
Then I populate a dict [str, Package] to return as the coordinator data

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

except SeventeenTrackError as err:
LOGGER.error("There was an error retrieving the packages: %s", err)

data.live_packages.clear()
Copy link
Member

Choose a reason for hiding this comment

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

Can also be removed

if summary_value:
summary_value["packages"].append(package)

return data
Copy link
Member

Choose a reason for hiding this comment

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

I would almost say like, just make it like you return

return SeventeenTrackData(packages=...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure what you mean? the SeventeenTrackData is being constructed along the way

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it.
Done

@@ -111,242 +108,187 @@ async def async_setup_entry(
) -> None:
"""Set up a 17Track sensor entry."""

client = hass.data[DOMAIN][config_entry.entry_id]
coordinator: SeventeenTrackCoordinator = hass.data[DOMAIN][config_entry.entry_id]
current_packages: set[Package] = set()
Copy link
Member

Choose a reason for hiding this comment

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

Since the content of the set is unique, i am not sure if package works as type. If we can prove that this works, great, otherwise i think tracking numbers are the way to go

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Package holds the tracking number so it's unique. Also I need the Package itself for the various data it holds

Copy link
Member

Choose a reason for hiding this comment

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

I know it's unique for us, but I'm talking about unique in the set. If a package is set to "Delivered", is that counted as a different object? If so, this code would not work

Copy link
Contributor Author

Choose a reason for hiding this comment

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

right.
So I added a clear() to the set before I update() it. That solves it

Copy link
Member

Choose a reason for hiding this comment

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

Checkout

if self._previously_tracked is not None:
entries = currently_tracked - self._previously_tracked
exits = self._previously_tracked - currently_tracked
self._handle_boundary(entries, EVENT_OPENSKY_ENTRY, flight_metadata)
self._handle_boundary(exits, EVENT_OPENSKY_EXIT, flight_metadata)
self._previously_tracked = currently_tracked

This is what I use in Opensky to track the flights.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@home-assistant home-assistant bot marked this pull request as draft April 8, 2024 21:46
@shaiu shaiu marked this pull request as ready for review April 9, 2024 05:27
@home-assistant home-assistant bot requested a review from joostlek April 9, 2024 05:27
Comment on lines 61 to 81
try:
summary = await self._client.profile.summary(
show_archived=self._show_archived
)

except SeventeenTrackError as err:
LOGGER.error("There was an error retrieving the summary: %s", err)

for status, quantity in summary.items():
summary_dict[slugify(status)] = {
"quantity": quantity,
"packages": [],
"status_name": status,
}

try:
live_packages = set(
await self._client.profile.packages(show_archived=self._show_archived)
)
except SeventeenTrackError as err:
LOGGER.error("There was an error retrieving the packages: %s", err)
Copy link
Member

Choose a reason for hiding this comment

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

Should we raise UpdateFailed when one of these 2 calls fail?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure what is the added value for raising it. If either has an error we can continue with empty data for one but the other will have data populated

Copy link
Member

Choose a reason for hiding this comment

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

How stable is the API? If its not excessively flakey I would suggest just raising it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK. raising

Copy link
Member

Choose a reason for hiding this comment

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

These 2 calls can now be merged

Copy link
Contributor Author

Choose a reason for hiding this comment

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

merged. Thanks

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
@shaiu shaiu requested a review from joostlek April 22, 2024 21:02
@joostlek joostlek merged commit e0c785b into home-assistant:dev Apr 23, 2024
24 checks passed
tr4nt0r pushed a commit to tr4nt0r/ha-core that referenced this pull request Apr 23, 2024
* Add coordinator to 17Track

* Add coordinator to 17Track

remove SensorEntityDescription (different PR)

* Update homeassistant/components/seventeentrack/coordinator.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Update homeassistant/components/seventeentrack/sensor.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Add coordinator to 17Track

fix CR

* Add coordinator to 17Track

fix second CR

* Add coordinator to 17Track

remove commented out code + fix display name

* Add coordinator to 17Track

created a set outside _async_create_remove_entities function

* Add coordinator to 17Track

fix CR

* Add coordinator to 17Track

fix CR 2

* Update homeassistant/components/seventeentrack/coordinator.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Add coordinator to 17Track

raise UpdateFailed if API throws an exception

* Add coordinator to 17Track

merge calls

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
@github-actions github-actions bot locked and limited conversation to collaborators Apr 24, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants