Skip to content

Commit

Permalink
fix: Ensure all calls to O365 library methods are async
Browse files Browse the repository at this point in the history
  • Loading branch information
RogerSelwyn committed Sep 28, 2024
1 parent 5e4b5f2 commit bc3e3fd
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 64 deletions.
10 changes: 9 additions & 1 deletion custom_components/o365/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,15 @@ async def _async_setup_account(hass, account_conf, conf_type):
_LOGGER.debug("do setup")
check_token = await _async_check_token(hass, account, account_name)
if check_token:
await do_setup(hass, account_conf, account, account_name, conf_type, perms)
await do_setup(
hass,
account_conf,
account,
is_authenticated,
account_name,
conf_type,
perms,
)
else:
await _async_authorization_repair(
hass,
Expand Down
37 changes: 25 additions & 12 deletions custom_components/o365/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
CONF_EXCLUDE,
CONF_HOURS_BACKWARD_TO_GET,
CONF_HOURS_FORWARD_TO_GET,
CONF_IS_AUTHENTICATED,
CONF_MAX_RESULTS,
CONF_PERMISSIONS,
CONF_SEARCH,
Expand Down Expand Up @@ -100,7 +101,8 @@ async def async_setup_platform(hass, config, add_entities, discovery_info=None):
account_name = discovery_info[CONF_ACCOUNT_NAME]
conf = hass.data[DOMAIN][account_name]
account = conf[CONF_ACCOUNT]
if not account.is_authenticated:
is_authenticated = conf[CONF_IS_AUTHENTICATED]
if not is_authenticated:
return False

update_supported = bool(
Expand Down Expand Up @@ -143,6 +145,7 @@ async def _async_setup_add_entities(
conf,
update_supported,
)
await cal.data.async_calendar_data_init(hass)
except HTTPError:
_LOGGER.warning(
"No permission for calendar, please remove - Name: %s; Device: %s;",
Expand Down Expand Up @@ -231,7 +234,7 @@ def __init__(
self.entity_id = entity_id
self._offset_reached = False
self._data_attribute = []
self.data = self._init_data(account, calendar_id, entity)
self.data = self._init_data(calendar_id, entity)
self._calendar_id = calendar_id
self._device_id = device_id
if update_supported:
Expand All @@ -242,12 +245,12 @@ def __init__(
)
self._error = None

def _init_data(self, account, calendar_id, entity):
def _init_data(self, calendar_id, entity):
max_results = entity.get(CONF_MAX_RESULTS)
search = entity.get(CONF_SEARCH)
exclude = entity.get(CONF_EXCLUDE)
return O365CalendarData(
account,
self._account,
self.entity_id,
calendar_id,
search,
Expand Down Expand Up @@ -547,21 +550,29 @@ def __init__(
limit=999,
):
"""Initialise the O365 Calendar Data."""
self._account = account
self._limit = limit
self.group_calendar = calendar_id.startswith(CONST_GROUP)
self.calendar_id = calendar_id
if self.group_calendar:
self._schedule = None
self.calendar = account.schedule(resource=self.calendar_id)
else:
self._schedule = account.schedule()
self.calendar = None
self._schedule = None
self.calendar = None
self._search = search
self._exclude = exclude
self.event = None
self._entity_id = entity_id
self._error = False

async def async_calendar_data_init(self, hass):
"""Async init of calendar data."""
if self.group_calendar:
self._schedule = None
self.calendar = await hass.async_add_executor_job(
ft.partial(self._account.schedule, resource=self.calendar_id)
)
else:
self._schedule = await hass.async_add_executor_job(self._account.schedule)
self.calendar = None

async def _async_get_calendar(self, hass):
try:
self.calendar = await hass.async_add_executor_job(
Expand Down Expand Up @@ -621,7 +632,7 @@ async def _async_calendar_schedule_get_events(
self, hass, calendar_schedule, start_date, end_date
):
"""Get the events for the calendar."""
query = calendar_schedule.new_query()
query = await hass.async_add_executor_job(calendar_schedule.new_query)
query = query.select(
"subject",
"body",
Expand Down Expand Up @@ -806,7 +817,9 @@ async def async_scan_for_calendars(self, call): # pylint: disable=unused-argume
for config in self._hass.data[DOMAIN]:
config = self._hass.data[DOMAIN][config]
if CONF_ACCOUNT in config:
schedule = config[CONF_ACCOUNT].schedule()
schedule = await self._hass.async_add_executor_job(
config[CONF_ACCOUNT].schedule
)
calendars = await self._hass.async_add_executor_job(
schedule.list_calendars
)
Expand Down
35 changes: 22 additions & 13 deletions custom_components/o365/classes/mailsensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,12 @@ def __init__(self, coordinator, name, entity_id, config, unique_id):
coordinator, config, name, entity_id, SENSOR_AUTO_REPLY, unique_id
)
self._config = config
account = self._config[CONF_ACCOUNT]
self.mailbox = account.mailbox()
self._account = self._config[CONF_ACCOUNT]
self.mailbox = None

async def async_init(self, hass):
"""async initialise."""
self.mailbox = await hass.async_add_executor_job(self._account.mailbox)

@property
def native_value(self):
Expand All @@ -111,7 +115,7 @@ def extra_state_attributes(self):
ATTR_END: ars.scheduled_enddatetime.strftime(DATETIME_FORMAT),
}

def auto_reply_enable(
async def async_auto_reply_enable(
self,
external_reply,
internal_reply,
Expand All @@ -123,16 +127,21 @@ def auto_reply_enable(
if not self._validate_autoreply_permissions():
return

self.mailbox.set_automatic_reply(
internal_reply, external_reply, start, end, external_audience
await self.hass.async_add_executor_job(
self.mailbox.set_automatic_reply,
internal_reply,
external_reply,
start,
end,
external_audience,
)

def auto_reply_disable(self):
async def async_auto_reply_disable(self):
"""Disable out of office autoreply."""
if not self._validate_autoreply_permissions():
return

self.mailbox.set_disable_reply()
await self.hass.async_add_executor_job(self.mailbox.set_disable_reply)

def _validate_autoreply_permissions(self):
return self._validate_permissions(
Expand All @@ -142,12 +151,12 @@ def _validate_autoreply_permissions(self):
)


def _build_base_query(mail_folder, sensor_conf):
async def _async_build_base_query(hass, mail_folder, sensor_conf):
"""Build base query for mail."""
download_attachments = sensor_conf.get(CONF_DOWNLOAD_ATTACHMENTS)
show_body = sensor_conf.get(CONF_SHOW_BODY)
html_body = sensor_conf.get(CONF_HTML_BODY)
query = mail_folder.new_query()
query = await hass.async_add_executor_job(mail_folder.new_query)
query = query.select(
"sender",
"from",
Expand All @@ -171,9 +180,9 @@ def _build_base_query(mail_folder, sensor_conf):
return query


def build_inbox_query(mail_folder, sensor_conf):
async def async_build_inbox_query(hass, mail_folder, sensor_conf):
"""Build query for email sensor."""
query = _build_base_query(mail_folder, sensor_conf)
query = await _async_build_base_query(hass, mail_folder, sensor_conf)

is_unread = sensor_conf.get(CONF_IS_UNREAD)

Expand All @@ -183,9 +192,9 @@ def build_inbox_query(mail_folder, sensor_conf):
return query


def build_query_query(mail_folder, sensor_conf):
async def async_build_query_query(hass, mail_folder, sensor_conf):
"""Build query for query sensor."""
query = _build_base_query(mail_folder, sensor_conf)
query = await _async_build_base_query(hass, mail_folder, sensor_conf)
query.order_by("receivedDateTime", ascending=False)

body_contains = sensor_conf.get(CONF_BODY_CONTAINS)
Expand Down
34 changes: 25 additions & 9 deletions custom_components/o365/classes/teamssensor.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""O365 teams sensors."""

import functools as ft
import logging

from homeassistant.components.sensor import SensorEntity
Expand Down Expand Up @@ -71,7 +72,9 @@ def __init__(self, coordinator, name, entity_id, config, unique_id, email):
)
self._email = email

def update_user_status(self, availability, activity, expiration_duration=None):
async def async_update_user_status(
self, availability, activity, expiration_duration=None
):
"""Update the users teams status."""
if self._email:
raise ServiceValidationError(
Expand All @@ -85,16 +88,22 @@ def update_user_status(self, availability, activity, expiration_duration=None):
if not self._validate_status_permissions():
return False

status = self.teams.set_my_presence(
self._application_id, availability, activity, expiration_duration
status = await self.hass.async_add_executor_job(
self.teams.set_my_presence,
self._application_id,
availability,
activity,
expiration_duration,
)
self._raise_event(
EVENT_UPDATE_USER_STATUS,
{ATTR_AVAILABILITY: status.availability, ATTR_ACTIVITY: status.activity},
)
return False

def update_user_preferred_status(self, availability, expiration_duration=None):
async def async_update_user_preferred_status(
self, availability, expiration_duration=None
):
"""Update the users teams status."""
if self._email:
raise ServiceValidationError(
Expand All @@ -113,8 +122,11 @@ def update_user_preferred_status(self, availability, expiration_duration=None):
if availability != PreferredAvailability.OFFLINE
else PreferredActivity.OFFWORK
)
status = self.teams.set_my_user_preferred_presence(
availability, activity, expiration_duration
status = await self.hass.async_add_executor_job(
self.teams.set_my_user_preferred_presence,
availability,
activity,
expiration_duration,
)
self._raise_event(
EVENT_UPDATE_USER_PREFERRED_STATUS,
Expand Down Expand Up @@ -168,15 +180,19 @@ def extra_state_attributes(self):
attributes[ATTR_DATA] = self.coordinator.data[self.entity_key][ATTR_DATA]
return attributes

def send_chat_message(self, chat_id, message, content_type):
async def async_send_chat_message(self, chat_id, message, content_type):
"""Send a message to the specified chat."""
if not self._validate_chat_permissions():
return False

chats = self.teams.get_my_chats()
chats = await self.hass.async_add_executor_job(self.teams.get_my_chats)
for chat in chats:
if chat.object_id == chat_id:
message = chat.send_message(content=message, content_type=content_type)
message = await self.hass.async_add_executor_job(
ft.partial(
chat.send_message, content=message, content_type=content_type
)
)
self._raise_event(EVENT_SEND_CHAT_MESSAGE, chat_id)
return True
_LOGGER.warning("Chat %s not found for send message", chat_id)
Expand Down
1 change: 1 addition & 0 deletions custom_components/o365/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ class EventResponse(Enum):
CONF_HTML_BODY = "html_body"
CONF_SHOW_BODY = "show_body"
CONF_IMPORTANCE = "importance"
CONF_IS_AUTHENTICATED = "is_authenticated"
CONF_IS_UNREAD = "is_unread"
CONF_KEYS_EMAIL = "keys_email"
CONF_KEYS_SENSORS = "keys_sensors"
Expand Down
17 changes: 11 additions & 6 deletions custom_components/o365/helpers/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from homeassistant.util import dt as dt_util
from requests.exceptions import HTTPError

from ..classes.mailsensor import build_inbox_query, build_query_query
from ..classes.mailsensor import async_build_inbox_query, async_build_query_query
from ..const import (
ATTR_AUTOREPLIESSETTINGS,
ATTR_CHAT_ID,
Expand Down Expand Up @@ -164,7 +164,7 @@ async def _async_todo_sensors(self):

async def _async_todo_entities(self, o365_task_lists):
keys = []
o365_tasks = self._account.tasks()
o365_tasks = await self.hass.async_add_executor_job(self._account.tasks)
for o365_tasklist in o365_task_lists:
track = o365_tasklist.get(CONF_TRACK)
if not track:
Expand Down Expand Up @@ -392,6 +392,7 @@ def __init__(self, hass, config):
# Polling interval. Will only be polled if there are subscribers.
update_interval=timedelta(seconds=30),
)
self._hass = hass
self._config = config
self._account = config[CONF_ACCOUNT]
self._account_name = config[CONF_ACCOUNT_NAME]
Expand Down Expand Up @@ -427,7 +428,9 @@ async def _async_email_sensors(self):
CONF_O365_MAIL_FOLDER: mail_folder,
CONF_NAME: name,
CONF_ENTITY_TYPE: SENSOR_EMAIL,
CONF_QUERY: build_inbox_query(mail_folder, sensor_conf),
CONF_QUERY: await async_build_inbox_query(
self._hass, mail_folder, sensor_conf
),
}

# Renames unique id to ensure uniqueness - To be deleted in early 2025
Expand Down Expand Up @@ -461,7 +464,9 @@ async def _async_query_sensors(self):
CONF_O365_MAIL_FOLDER: mail_folder,
CONF_NAME: name,
CONF_ENTITY_TYPE: SENSOR_EMAIL,
CONF_QUERY: build_query_query(mail_folder, sensor_conf),
CONF_QUERY: await async_build_query_query(
self._hass, mail_folder, sensor_conf
),
}

# Renames unique id to ensure uniqueness - To be deleted in early 2025
Expand All @@ -480,14 +485,14 @@ async def _async_query_sensors(self):

async def _async_get_mail_folder(self, sensor_conf, sensor_type):
"""Get the configured folder."""
mailbox = self._account.mailbox()
mailbox = await self.hass.async_add_executor_job(self._account.mailbox)
_LOGGER.debug("Get mail folder: %s", sensor_conf.get(CONF_NAME))
if mail_folder_conf := sensor_conf.get(CONF_MAIL_FOLDER):
return await self._async_get_configured_mail_folder(
mail_folder_conf, mailbox, sensor_type
)

return mailbox.inbox_folder()
return await self.hass.async_add_executor_job(mailbox.inbox_folder)

async def _async_get_configured_mail_folder(
self, mail_folder_conf, mailbox, sensor_type
Expand Down
6 changes: 5 additions & 1 deletion custom_components/o365/helpers/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
CONF_EMAIL_SENSORS,
CONF_ENABLE_CALENDAR,
CONF_ENABLE_UPDATE,
CONF_IS_AUTHENTICATED,
CONF_KEYS_EMAIL,
CONF_KEYS_SENSORS,
CONF_PERMISSIONS,
Expand All @@ -32,7 +33,9 @@
_LOGGER = logging.getLogger(__name__)


async def do_setup(hass, config, account, account_name, conf_type, perms):
async def do_setup(
hass, config, account, is_authenticated, account_name, conf_type, perms
):
"""Run the setup after we have everything configured."""
email_sensors = config.get(CONF_EMAIL_SENSORS, [])
query_sensors = config.get(CONF_QUERY_SENSORS, [])
Expand All @@ -46,6 +49,7 @@ async def do_setup(hass, config, account, account_name, conf_type, perms):
account_config = {
CONF_CLIENT_ID: config.get(CONF_CLIENT_ID),
CONF_ACCOUNT: account,
CONF_IS_AUTHENTICATED: is_authenticated,
CONF_EMAIL_SENSORS: email_sensors,
CONF_QUERY_SENSORS: query_sensors,
CONF_STATUS_SENSORS: status_sensors,
Expand Down
Loading

0 comments on commit bc3e3fd

Please sign in to comment.