Home Assistant Unofficial Reference 2024.12.1
coordinator.py
Go to the documentation of this file.
1 """Define the Azure DevOps DataUpdateCoordinator."""
2 
3 from collections.abc import Callable
4 from datetime import timedelta
5 import logging
6 from typing import Final
7 
8 from aioazuredevops.client import DevOpsClient
9 from aioazuredevops.helper import (
10  WorkItemTypeAndState,
11  work_item_types_states_filter,
12  work_items_by_type_and_state,
13 )
14 from aioazuredevops.models.build import Build
15 from aioazuredevops.models.core import Project
16 from aioazuredevops.models.work_item_type import Category
17 import aiohttp
18 
19 from homeassistant.config_entries import ConfigEntry
20 from homeassistant.core import HomeAssistant
21 from homeassistant.exceptions import ConfigEntryAuthFailed
22 from homeassistant.helpers.aiohttp_client import async_get_clientsession
23 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
24 
25 from .const import CONF_ORG, DOMAIN
26 from .data import AzureDevOpsData
27 
28 BUILDS_QUERY: Final = "?queryOrder=queueTimeDescending&maxBuildsPerDefinition=1"
29 IGNORED_CATEGORIES: Final[list[Category]] = [Category.COMPLETED, Category.REMOVED]
30 
31 
32 def ado_exception_none_handler(func: Callable) -> Callable:
33  """Handle exceptions or None to always return a value or raise."""
34 
35  async def handler(*args, **kwargs):
36  try:
37  response = await func(*args, **kwargs)
38  except aiohttp.ClientError as exception:
39  raise UpdateFailed from exception
40 
41  if response is None:
42  raise UpdateFailed("No data returned from Azure DevOps")
43 
44  return response
45 
46  return handler
47 
48 
50  """Class to manage and fetch Azure DevOps data."""
51 
52  client: DevOpsClient
53  organization: str
54  project: Project
55 
56  def __init__(
57  self,
58  hass: HomeAssistant,
59  logger: logging.Logger,
60  *,
61  entry: ConfigEntry,
62  ) -> None:
63  """Initialize global Azure DevOps data updater."""
64  self.titletitle = entry.title
65 
66  super().__init__(
67  hass=hass,
68  logger=logger,
69  name=DOMAIN,
70  update_interval=timedelta(seconds=300),
71  )
72 
73  self.clientclient = DevOpsClient(session=async_get_clientsession(hass))
74  self.organizationorganization = entry.data[CONF_ORG]
75 
76  @ado_exception_none_handler
77  async def authorize(
78  self,
79  personal_access_token: str,
80  ) -> bool:
81  """Authorize with Azure DevOps."""
82  await self.clientclient.authorize(
83  personal_access_token,
84  self.organizationorganization,
85  )
86  if not self.clientclient.authorized:
88  translation_domain=DOMAIN,
89  translation_key="authentication_failed",
90  translation_placeholders={"title": self.titletitle},
91  )
92 
93  return True
94 
95  @ado_exception_none_handler
96  async def get_project(
97  self,
98  project: str,
99  ) -> Project | None:
100  """Get the project."""
101  return await self.clientclient.get_project(
102  self.organizationorganization,
103  project,
104  )
105 
106  @ado_exception_none_handler
107  async def _get_builds(self, project_name: str) -> list[Build] | None:
108  """Get the builds."""
109  return await self.clientclient.get_builds(
110  self.organizationorganization,
111  project_name,
112  BUILDS_QUERY,
113  )
114 
115  @ado_exception_none_handler
116  async def _get_work_items(
117  self, project_name: str
118  ) -> list[WorkItemTypeAndState] | None:
119  """Get the work items."""
120 
121  if (
122  work_item_types := await self.clientclient.get_work_item_types(
123  self.organizationorganization,
124  project_name,
125  )
126  ) is None:
127  # If no work item types are returned, return an empty list
128  return []
129 
130  if (
131  work_item_ids := await self.clientclient.get_work_item_ids(
132  self.organizationorganization,
133  project_name,
134  # Filter out completed and removed work items so we only get active work items
135  states=work_item_types_states_filter(
136  work_item_types,
137  ignored_categories=IGNORED_CATEGORIES,
138  ),
139  )
140  ) is None:
141  # If no work item ids are returned, return an empty list
142  return []
143 
144  if (
145  work_items := await self.clientclient.get_work_items(
146  self.organizationorganization,
147  project_name,
148  work_item_ids,
149  )
150  ) is None:
151  # If no work items are returned, return an empty list
152  return []
153 
154  return work_items_by_type_and_state(
155  work_item_types,
156  work_items,
157  ignored_categories=IGNORED_CATEGORIES,
158  )
159 
160  async def _async_update_data(self) -> AzureDevOpsData:
161  """Fetch data from Azure DevOps."""
162  # Get the builds from the project
163  builds = await self._get_builds_get_builds(self.project.name)
164  work_items = await self._get_work_items_get_work_items(self.project.name)
165 
166  return AzureDevOpsData(
167  organization=self.organizationorganization,
168  project=self.project,
169  builds=builds,
170  work_items=work_items,
171  )
None __init__(self, HomeAssistant hass, logging.Logger logger, *ConfigEntry entry)
Definition: coordinator.py:62
list[WorkItemTypeAndState]|None _get_work_items(self, str project_name)
Definition: coordinator.py:118
Callable ado_exception_none_handler(Callable func)
Definition: coordinator.py:32
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)