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

Move zoneinfo & pytz file I/O to executor #126

Merged
merged 2 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions custom_components/sun2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
)
from .config_flow import loc_from_options
from .const import CONF_OBS_ELV, DOMAIN, SIG_ASTRAL_DATA_UPDATED, SIG_HA_LOC_UPDATED
from .helpers import ConfigData, ObsElvs, get_loc_data, sun2_data
from .helpers import ConfigData, ObsElvs, async_get_loc_data, init_sun2_data, sun2_data

PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]

Expand Down Expand Up @@ -134,14 +134,14 @@ def update_location(call: ServiceCall) -> None:
options.update(loc_config)
hass.config_entries.async_update_entry(entry, options=options)

s2data = sun2_data(hass)
s2data = await init_sun2_data(hass)

async def handle_core_config_update(event: Event) -> None:
"""Handle core config update."""
if not event.data:
return

new_ha_loc_data = get_loc_data(hass.config)
new_ha_loc_data = await async_get_loc_data(hass, hass.config)
if ha_loc_data_changed := new_ha_loc_data != s2data.ha_loc_data:
s2data.ha_loc_data = new_ha_loc_data

Expand Down Expand Up @@ -206,7 +206,7 @@ async def entry_updated(hass: HomeAssistant, entry: ConfigEntry) -> None:
and entry.options.get(CONF_BINARY_SENSORS, []) == config_data.binary_sensors
and entry.options.get(CONF_SENSORS, []) == config_data.sensors
):
loc_data = get_loc_data(entry.options)
loc_data = await async_get_loc_data(hass, entry.options)
obs_elvs = ObsElvs.from_entry_options(entry.options)
if loc_data != config_data.loc_data or obs_elvs != config_data.obs_elvs:
config_data.loc_data = loc_data
Expand All @@ -226,7 +226,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
entry.title,
entry.options.get(CONF_BINARY_SENSORS, [])[:],
entry.options.get(CONF_SENSORS, [])[:],
get_loc_data(entry.options),
await async_get_loc_data(hass, entry.options),
ObsElvs.from_entry_options(entry.options),
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
Expand Down
52 changes: 40 additions & 12 deletions custom_components/sun2/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,24 +115,48 @@ def _get_loc_data(lp: LocParams | None) -> LocData | None:


@overload
def get_loc_data(arg: Config) -> LocData:
async def async_get_loc_data(hass: HomeAssistant, arg: Config) -> LocData:
...


@overload
def get_loc_data(arg: Mapping[str, Any]) -> LocData | None:
async def async_get_loc_data(
hass: HomeAssistant, arg: Mapping[str, Any]
) -> LocData | None:
...


def get_loc_data(arg: Config | Mapping[str, Any]) -> LocData | None:
async def async_get_loc_data(
hass: HomeAssistant, arg: Config | Mapping[str, Any]
) -> LocData | None:
"""Get LocData from HA config or config entry options.

If config entry provided, and it does not contain location options,
then return None, meaning HA's location configuration should be used.
"""
if isinstance(arg, Config):
return _get_loc_data(LocParams.from_hass_config(arg))
return _get_loc_data(LocParams.from_entry_options(arg))

def get_loc_data() -> LocData | None:
"""Get LocData.

Must be run in an executor because dt_util.get_time_zone can do file I/O.
Also, astral's Location methods use pytz when local=True and pytz, when first
called with a given time zone, will do file I/O. After that the data will be
cached and it won't do file I/O again for the same time zone.
"""
if isinstance(arg, Config):
loc_data = _get_loc_data(LocParams.from_hass_config(arg))
else:
loc_data = _get_loc_data(LocParams.from_entry_options(arg))
if loc_data is None:
return None

# Force pytz to do its file I/O now by using the Location object's tzinfo
# property.
loc_data.loc.tzinfo # noqa: B018

return loc_data

return await hass.async_add_executor_job(get_loc_data)


ObsElv = float | tuple[float, float]
Expand Down Expand Up @@ -201,13 +225,17 @@ class Sun2Data:
config_data: dict[str, ConfigData] = field(default_factory=dict)


async def init_sun2_data(hass: HomeAssistant) -> Sun2Data:
"""Initialize Sun2 integration data."""
if DOMAIN not in hass.data:
loc_data = await async_get_loc_data(hass, hass.config)
hass.data[DOMAIN] = Sun2Data(loc_data)
return cast(Sun2Data, hass.data[DOMAIN])


def sun2_data(hass: HomeAssistant) -> Sun2Data:
"""Return Sun2 integration data."""
try:
return cast(Sun2Data, hass.data[DOMAIN])
except KeyError:
hass.data[DOMAIN] = s2data = Sun2Data(get_loc_data(hass.config))
return s2data
return cast(Sun2Data, hass.data[DOMAIN])


def hours_to_hms(hours: Num | None) -> str | None:
Expand All @@ -223,7 +251,7 @@ def hours_to_hms(hours: Num | None) -> str | None:

async def init_translations(hass: HomeAssistant) -> None:
"""Initialize translations."""
s2data = sun2_data(hass)
s2data = await init_sun2_data(hass)
if s2data.language != hass.config.language:
sel_trans = await async_get_translations(
hass, hass.config.language, "selector", [DOMAIN], False
Expand Down