Use GraphQL for GitHub integration (#66928)
This commit is contained in:
@@ -13,13 +13,7 @@ from homeassistant.helpers.aiohttp_client import (
|
||||
)
|
||||
|
||||
from .const import CONF_REPOSITORIES, DOMAIN, LOGGER
|
||||
from .coordinator import (
|
||||
DataUpdateCoordinators,
|
||||
RepositoryCommitDataUpdateCoordinator,
|
||||
RepositoryInformationDataUpdateCoordinator,
|
||||
RepositoryIssueDataUpdateCoordinator,
|
||||
RepositoryReleaseDataUpdateCoordinator,
|
||||
)
|
||||
from .coordinator import GitHubDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||
|
||||
@@ -37,24 +31,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
repositories: list[str] = entry.options[CONF_REPOSITORIES]
|
||||
|
||||
for repository in repositories:
|
||||
coordinators: DataUpdateCoordinators = {
|
||||
"information": RepositoryInformationDataUpdateCoordinator(
|
||||
hass=hass, entry=entry, client=client, repository=repository
|
||||
),
|
||||
"release": RepositoryReleaseDataUpdateCoordinator(
|
||||
hass=hass, entry=entry, client=client, repository=repository
|
||||
),
|
||||
"issue": RepositoryIssueDataUpdateCoordinator(
|
||||
hass=hass, entry=entry, client=client, repository=repository
|
||||
),
|
||||
"commit": RepositoryCommitDataUpdateCoordinator(
|
||||
hass=hass, entry=entry, client=client, repository=repository
|
||||
),
|
||||
}
|
||||
coordinator = GitHubDataUpdateCoordinator(
|
||||
hass=hass,
|
||||
client=client,
|
||||
repository=repository,
|
||||
)
|
||||
|
||||
await coordinators["information"].async_config_entry_first_refresh()
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data[DOMAIN][repository] = coordinators
|
||||
hass.data[DOMAIN][repository] = coordinator
|
||||
|
||||
async_cleanup_device_registry(hass=hass, entry=entry)
|
||||
|
||||
|
||||
@@ -3,9 +3,6 @@ from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from logging import Logger, getLogger
|
||||
from typing import NamedTuple
|
||||
|
||||
from aiogithubapi import GitHubIssueModel
|
||||
|
||||
LOGGER: Logger = getLogger(__package__)
|
||||
|
||||
@@ -18,12 +15,3 @@ DEFAULT_UPDATE_INTERVAL = timedelta(seconds=300)
|
||||
|
||||
CONF_ACCESS_TOKEN = "access_token"
|
||||
CONF_REPOSITORIES = "repositories"
|
||||
|
||||
|
||||
class IssuesPulls(NamedTuple):
|
||||
"""Issues and pull requests."""
|
||||
|
||||
issues_count: int
|
||||
issue_last: GitHubIssueModel | None
|
||||
pulls_count: int
|
||||
pull_last: GitHubIssueModel | None
|
||||
|
||||
@@ -1,44 +1,92 @@
|
||||
"""Custom data update coordinators for the GitHub integration."""
|
||||
"""Custom data update coordinator for the GitHub integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Literal, TypedDict
|
||||
from typing import Any
|
||||
|
||||
from aiogithubapi import (
|
||||
GitHubAPI,
|
||||
GitHubCommitModel,
|
||||
GitHubConnectionException,
|
||||
GitHubException,
|
||||
GitHubNotModifiedException,
|
||||
GitHubRatelimitException,
|
||||
GitHubReleaseModel,
|
||||
GitHubRepositoryModel,
|
||||
GitHubResponseModel,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, T
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DEFAULT_UPDATE_INTERVAL, DOMAIN, LOGGER, IssuesPulls
|
||||
from .const import DEFAULT_UPDATE_INTERVAL, DOMAIN, LOGGER
|
||||
|
||||
CoordinatorKeyType = Literal["information", "release", "issue", "commit"]
|
||||
GRAPHQL_REPOSITORY_QUERY = """
|
||||
query ($owner: String!, $repository: String!) {
|
||||
rateLimit {
|
||||
cost
|
||||
remaining
|
||||
}
|
||||
repository(owner: $owner, name: $repository) {
|
||||
default_branch_ref: defaultBranchRef {
|
||||
commit: target {
|
||||
... on Commit {
|
||||
message: messageHeadline
|
||||
url
|
||||
sha: oid
|
||||
}
|
||||
}
|
||||
}
|
||||
stargazers_count: stargazerCount
|
||||
forks_count: forkCount
|
||||
full_name: nameWithOwner
|
||||
id: databaseId
|
||||
watchers(first: 1) {
|
||||
total: totalCount
|
||||
}
|
||||
issue: issues(
|
||||
first: 1
|
||||
states: OPEN
|
||||
orderBy: {field: CREATED_AT, direction: DESC}
|
||||
) {
|
||||
total: totalCount
|
||||
issues: nodes {
|
||||
title
|
||||
url
|
||||
number
|
||||
}
|
||||
}
|
||||
pull_request: pullRequests(
|
||||
first: 1
|
||||
states: OPEN
|
||||
orderBy: {field: CREATED_AT, direction: DESC}
|
||||
) {
|
||||
total: totalCount
|
||||
pull_requests: nodes {
|
||||
title
|
||||
url
|
||||
number
|
||||
}
|
||||
}
|
||||
release: latestRelease {
|
||||
name
|
||||
url
|
||||
tag: tagName
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
class GitHubBaseDataUpdateCoordinator(DataUpdateCoordinator[T]):
|
||||
"""Base class for GitHub data update coordinators."""
|
||||
class GitHubDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
"""Data update coordinator for the GitHub integration."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
client: GitHubAPI,
|
||||
repository: str,
|
||||
) -> None:
|
||||
"""Initialize GitHub data update coordinator base class."""
|
||||
self.config_entry = entry
|
||||
self.repository = repository
|
||||
self._client = client
|
||||
self._last_response: GitHubResponseModel[T] | None = None
|
||||
self._last_response: GitHubResponseModel[dict[str, Any]] | None = None
|
||||
self.data = {}
|
||||
|
||||
super().__init__(
|
||||
hass,
|
||||
@@ -47,30 +95,14 @@ class GitHubBaseDataUpdateCoordinator(DataUpdateCoordinator[T]):
|
||||
update_interval=DEFAULT_UPDATE_INTERVAL,
|
||||
)
|
||||
|
||||
@property
|
||||
def _etag(self) -> str:
|
||||
"""Return the ETag of the last response."""
|
||||
return self._last_response.etag if self._last_response is not None else None
|
||||
|
||||
async def fetch_data(self) -> GitHubResponseModel[T]:
|
||||
"""Fetch data from GitHub API."""
|
||||
|
||||
@staticmethod
|
||||
def _parse_response(response: GitHubResponseModel[T]) -> T:
|
||||
"""Parse the response from GitHub API."""
|
||||
return response.data
|
||||
|
||||
async def _async_update_data(self) -> T:
|
||||
async def _async_update_data(self) -> GitHubResponseModel[dict[str, Any]]:
|
||||
"""Update data."""
|
||||
owner, repository = self.repository.split("/")
|
||||
try:
|
||||
response = await self.fetch_data()
|
||||
except GitHubNotModifiedException:
|
||||
LOGGER.debug(
|
||||
"Content for %s with %s not modified",
|
||||
self.repository,
|
||||
self.__class__.__name__,
|
||||
response = await self._client.graphql(
|
||||
query=GRAPHQL_REPOSITORY_QUERY,
|
||||
variables={"owner": owner, "repository": repository},
|
||||
)
|
||||
# Return the last known data if the request result was not modified
|
||||
return self.data
|
||||
except (GitHubConnectionException, GitHubRatelimitException) as exception:
|
||||
# These are expected and we dont log anything extra
|
||||
raise UpdateFailed(exception) from exception
|
||||
@@ -80,133 +112,4 @@ class GitHubBaseDataUpdateCoordinator(DataUpdateCoordinator[T]):
|
||||
raise UpdateFailed(exception) from exception
|
||||
else:
|
||||
self._last_response = response
|
||||
return self._parse_response(response)
|
||||
|
||||
|
||||
class RepositoryInformationDataUpdateCoordinator(
|
||||
GitHubBaseDataUpdateCoordinator[GitHubRepositoryModel]
|
||||
):
|
||||
"""Data update coordinator for repository information."""
|
||||
|
||||
async def fetch_data(self) -> GitHubResponseModel[GitHubRepositoryModel]:
|
||||
"""Get the latest data from GitHub."""
|
||||
return await self._client.repos.get(self.repository, **{"etag": self._etag})
|
||||
|
||||
|
||||
class RepositoryReleaseDataUpdateCoordinator(
|
||||
GitHubBaseDataUpdateCoordinator[GitHubReleaseModel]
|
||||
):
|
||||
"""Data update coordinator for repository release."""
|
||||
|
||||
@staticmethod
|
||||
def _parse_response(
|
||||
response: GitHubResponseModel[GitHubReleaseModel | None],
|
||||
) -> GitHubReleaseModel | None:
|
||||
"""Parse the response from GitHub API."""
|
||||
if not response.data:
|
||||
return None
|
||||
|
||||
for release in response.data:
|
||||
if not release.prerelease and not release.draft:
|
||||
return release
|
||||
|
||||
# Fall back to the latest release if no non-prerelease release is found
|
||||
return response.data[0]
|
||||
|
||||
async def fetch_data(self) -> GitHubReleaseModel | None:
|
||||
"""Get the latest data from GitHub."""
|
||||
return await self._client.repos.releases.list(
|
||||
self.repository, **{"etag": self._etag}
|
||||
)
|
||||
|
||||
|
||||
class RepositoryIssueDataUpdateCoordinator(
|
||||
GitHubBaseDataUpdateCoordinator[IssuesPulls]
|
||||
):
|
||||
"""Data update coordinator for repository issues."""
|
||||
|
||||
_issue_etag: str | None = None
|
||||
_pull_etag: str | None = None
|
||||
|
||||
@staticmethod
|
||||
def _parse_response(response: IssuesPulls) -> IssuesPulls:
|
||||
"""Parse the response from GitHub API."""
|
||||
return response
|
||||
|
||||
async def fetch_data(self) -> IssuesPulls:
|
||||
"""Get the latest data from GitHub."""
|
||||
pulls_count = 0
|
||||
pull_last = None
|
||||
issues_count = 0
|
||||
issue_last = None
|
||||
try:
|
||||
pull_response = await self._client.repos.pulls.list(
|
||||
self.repository,
|
||||
**{"params": {"per_page": 1}, "etag": self._pull_etag},
|
||||
)
|
||||
except GitHubNotModifiedException:
|
||||
# Return the last known data if the request result was not modified
|
||||
pulls_count = self.data.pulls_count
|
||||
pull_last = self.data.pull_last
|
||||
else:
|
||||
self._pull_etag = pull_response.etag
|
||||
pulls_count = pull_response.last_page_number or len(pull_response.data)
|
||||
pull_last = pull_response.data[0] if pull_response.data else None
|
||||
|
||||
try:
|
||||
issue_response = await self._client.repos.issues.list(
|
||||
self.repository,
|
||||
**{"params": {"per_page": 1}, "etag": self._issue_etag},
|
||||
)
|
||||
except GitHubNotModifiedException:
|
||||
# Return the last known data if the request result was not modified
|
||||
issues_count = self.data.issues_count
|
||||
issue_last = self.data.issue_last
|
||||
else:
|
||||
self._issue_etag = issue_response.etag
|
||||
issues_count = (
|
||||
issue_response.last_page_number or len(issue_response.data)
|
||||
) - pulls_count
|
||||
issue_last = issue_response.data[0] if issue_response.data else None
|
||||
|
||||
if issue_last is not None and issue_last.pull_request:
|
||||
issue_response = await self._client.repos.issues.list(self.repository)
|
||||
for issue in issue_response.data:
|
||||
if not issue.pull_request:
|
||||
issue_last = issue
|
||||
break
|
||||
|
||||
return IssuesPulls(
|
||||
issues_count=issues_count,
|
||||
issue_last=issue_last,
|
||||
pulls_count=pulls_count,
|
||||
pull_last=pull_last,
|
||||
)
|
||||
|
||||
|
||||
class RepositoryCommitDataUpdateCoordinator(
|
||||
GitHubBaseDataUpdateCoordinator[GitHubCommitModel]
|
||||
):
|
||||
"""Data update coordinator for repository commit."""
|
||||
|
||||
@staticmethod
|
||||
def _parse_response(
|
||||
response: GitHubResponseModel[GitHubCommitModel | None],
|
||||
) -> GitHubCommitModel | None:
|
||||
"""Parse the response from GitHub API."""
|
||||
return response.data[0] if response.data else None
|
||||
|
||||
async def fetch_data(self) -> GitHubCommitModel | None:
|
||||
"""Get the latest data from GitHub."""
|
||||
return await self._client.repos.list_commits(
|
||||
self.repository, **{"params": {"per_page": 1}, "etag": self._etag}
|
||||
)
|
||||
|
||||
|
||||
class DataUpdateCoordinators(TypedDict):
|
||||
"""Custom data update coordinators for the GitHub integration."""
|
||||
|
||||
information: RepositoryInformationDataUpdateCoordinator
|
||||
release: RepositoryReleaseDataUpdateCoordinator
|
||||
issue: RepositoryIssueDataUpdateCoordinator
|
||||
commit: RepositoryCommitDataUpdateCoordinator
|
||||
return response.data["data"]["repository"]
|
||||
|
||||
@@ -13,7 +13,7 @@ from homeassistant.helpers.aiohttp_client import (
|
||||
)
|
||||
|
||||
from .const import CONF_ACCESS_TOKEN, DOMAIN
|
||||
from .coordinator import DataUpdateCoordinators
|
||||
from .coordinator import GitHubDataUpdateCoordinator
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
@@ -35,11 +35,10 @@ async def async_get_config_entry_diagnostics(
|
||||
else:
|
||||
data["rate_limit"] = rate_limit_response.data.as_dict
|
||||
|
||||
repositories: dict[str, DataUpdateCoordinators] = hass.data[DOMAIN]
|
||||
repositories: dict[str, GitHubDataUpdateCoordinator] = hass.data[DOMAIN]
|
||||
data["repositories"] = {}
|
||||
|
||||
for repository, coordinators in repositories.items():
|
||||
info = coordinators["information"].data
|
||||
data["repositories"][repository] = info.as_dict if info else None
|
||||
for repository, coordinator in repositories.items():
|
||||
data["repositories"][repository] = coordinator.data
|
||||
|
||||
return data
|
||||
|
||||
@@ -19,19 +19,14 @@ from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import (
|
||||
CoordinatorKeyType,
|
||||
DataUpdateCoordinators,
|
||||
GitHubBaseDataUpdateCoordinator,
|
||||
)
|
||||
from .coordinator import GitHubDataUpdateCoordinator
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaseEntityDescriptionMixin:
|
||||
"""Mixin for required GitHub base description keys."""
|
||||
|
||||
coordinator_key: CoordinatorKeyType
|
||||
value_fn: Callable[[Any], StateType]
|
||||
value_fn: Callable[[dict[str, Any]], StateType]
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -40,8 +35,8 @@ class BaseEntityDescription(SensorEntityDescription):
|
||||
|
||||
icon: str = "mdi:github"
|
||||
entity_registry_enabled_default: bool = False
|
||||
attr_fn: Callable[[Any], Mapping[str, Any] | None] = lambda data: None
|
||||
avabl_fn: Callable[[Any], bool] = lambda data: True
|
||||
attr_fn: Callable[[dict[str, Any]], Mapping[str, Any] | None] = lambda data: None
|
||||
avabl_fn: Callable[[dict[str, Any]], bool] = lambda data: True
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -57,8 +52,7 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = (
|
||||
native_unit_of_measurement="Stars",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda data: data.stargazers_count,
|
||||
coordinator_key="information",
|
||||
value_fn=lambda data: data["stargazers_count"],
|
||||
),
|
||||
GitHubSensorEntityDescription(
|
||||
key="subscribers_count",
|
||||
@@ -67,9 +61,7 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = (
|
||||
native_unit_of_measurement="Watchers",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
# The API returns a watcher_count, but subscribers_count is more accurate
|
||||
value_fn=lambda data: data.subscribers_count,
|
||||
coordinator_key="information",
|
||||
value_fn=lambda data: data["watchers"]["total"],
|
||||
),
|
||||
GitHubSensorEntityDescription(
|
||||
key="forks_count",
|
||||
@@ -78,8 +70,7 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = (
|
||||
native_unit_of_measurement="Forks",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda data: data.forks_count,
|
||||
coordinator_key="information",
|
||||
value_fn=lambda data: data["forks_count"],
|
||||
),
|
||||
GitHubSensorEntityDescription(
|
||||
key="issues_count",
|
||||
@@ -87,8 +78,7 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = (
|
||||
native_unit_of_measurement="Issues",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda data: data.issues_count,
|
||||
coordinator_key="issue",
|
||||
value_fn=lambda data: data["issue"]["total"],
|
||||
),
|
||||
GitHubSensorEntityDescription(
|
||||
key="pulls_count",
|
||||
@@ -96,50 +86,46 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = (
|
||||
native_unit_of_measurement="Pull Requests",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda data: data.pulls_count,
|
||||
coordinator_key="issue",
|
||||
value_fn=lambda data: data["pull_request"]["total"],
|
||||
),
|
||||
GitHubSensorEntityDescription(
|
||||
coordinator_key="commit",
|
||||
key="latest_commit",
|
||||
name="Latest Commit",
|
||||
value_fn=lambda data: data.commit.message.splitlines()[0][:255],
|
||||
value_fn=lambda data: data["default_branch_ref"]["commit"]["message"][:255],
|
||||
attr_fn=lambda data: {
|
||||
"sha": data.sha,
|
||||
"url": data.html_url,
|
||||
"sha": data["default_branch_ref"]["commit"]["sha"],
|
||||
"url": data["default_branch_ref"]["commit"]["url"],
|
||||
},
|
||||
),
|
||||
GitHubSensorEntityDescription(
|
||||
coordinator_key="release",
|
||||
key="latest_release",
|
||||
name="Latest Release",
|
||||
entity_registry_enabled_default=True,
|
||||
value_fn=lambda data: data.name[:255],
|
||||
avabl_fn=lambda data: data["release"] is not None,
|
||||
value_fn=lambda data: data["release"]["name"][:255],
|
||||
attr_fn=lambda data: {
|
||||
"url": data.html_url,
|
||||
"tag": data.tag_name,
|
||||
"url": data["release"]["url"],
|
||||
"tag": data["release"]["tag"],
|
||||
},
|
||||
),
|
||||
GitHubSensorEntityDescription(
|
||||
coordinator_key="issue",
|
||||
key="latest_issue",
|
||||
name="Latest Issue",
|
||||
value_fn=lambda data: data.issue_last.title[:255],
|
||||
avabl_fn=lambda data: data.issue_last is not None,
|
||||
avabl_fn=lambda data: data["issue"]["issues"],
|
||||
value_fn=lambda data: data["issue"]["issues"][0]["title"][:255],
|
||||
attr_fn=lambda data: {
|
||||
"url": data.issue_last.html_url,
|
||||
"number": data.issue_last.number,
|
||||
"url": data["issue"]["issues"][0]["url"],
|
||||
"number": data["issue"]["issues"][0]["number"],
|
||||
},
|
||||
),
|
||||
GitHubSensorEntityDescription(
|
||||
coordinator_key="issue",
|
||||
key="latest_pull_request",
|
||||
name="Latest Pull Request",
|
||||
value_fn=lambda data: data.pull_last.title[:255],
|
||||
avabl_fn=lambda data: data.pull_last is not None,
|
||||
avabl_fn=lambda data: data["pull_request"]["pull_requests"],
|
||||
value_fn=lambda data: data["pull_request"]["pull_requests"][0]["title"][:255],
|
||||
attr_fn=lambda data: {
|
||||
"url": data.pull_last.html_url,
|
||||
"number": data.pull_last.number,
|
||||
"url": data["pull_request"]["pull_requests"][0]["url"],
|
||||
"number": data["pull_request"]["pull_requests"][0]["number"],
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -151,43 +137,41 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up GitHub sensor based on a config entry."""
|
||||
repositories: dict[str, DataUpdateCoordinators] = hass.data[DOMAIN]
|
||||
repositories: dict[str, GitHubDataUpdateCoordinator] = hass.data[DOMAIN]
|
||||
async_add_entities(
|
||||
(
|
||||
GitHubSensorEntity(coordinators, description)
|
||||
GitHubSensorEntity(coordinator, description)
|
||||
for description in SENSOR_DESCRIPTIONS
|
||||
for coordinators in repositories.values()
|
||||
for coordinator in repositories.values()
|
||||
),
|
||||
update_before_add=True,
|
||||
)
|
||||
|
||||
|
||||
class GitHubSensorEntity(CoordinatorEntity, SensorEntity):
|
||||
class GitHubSensorEntity(CoordinatorEntity[dict[str, Any]], SensorEntity):
|
||||
"""Defines a GitHub sensor entity."""
|
||||
|
||||
_attr_attribution = "Data provided by the GitHub API"
|
||||
|
||||
coordinator: GitHubBaseDataUpdateCoordinator
|
||||
coordinator: GitHubDataUpdateCoordinator
|
||||
entity_description: GitHubSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinators: DataUpdateCoordinators,
|
||||
coordinator: GitHubDataUpdateCoordinator,
|
||||
entity_description: GitHubSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
coordinator = coordinators[entity_description.coordinator_key]
|
||||
_information = coordinators["information"].data
|
||||
|
||||
super().__init__(coordinator=coordinator)
|
||||
|
||||
self.entity_description = entity_description
|
||||
self._attr_name = f"{_information.full_name} {entity_description.name}"
|
||||
self._attr_unique_id = f"{_information.id}_{entity_description.key}"
|
||||
self._attr_name = (
|
||||
f"{coordinator.data.get('full_name')} {entity_description.name}"
|
||||
)
|
||||
self._attr_unique_id = f"{coordinator.data.get('id')}_{entity_description.key}"
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, coordinator.repository)},
|
||||
name=_information.full_name,
|
||||
name=coordinator.data.get("full_name"),
|
||||
manufacturer="GitHub",
|
||||
configuration_url=f"https://github.com/{coordinator.repository}",
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
|
||||
Reference in New Issue
Block a user