Skip to content

Commit

Permalink
Refactor integration_blueprint to belgiantrain: update constants, dat…
Browse files Browse the repository at this point in the history
…a models, and entity classes
  • Loading branch information
tjorim committed Jan 4, 2025
1 parent 04f5dea commit 1d456ba
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 194 deletions.
38 changes: 23 additions & 15 deletions .devcontainer.json → .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/python
{
"name": "ludeeus/integration_blueprint",
"name": "Home Assistant Dev Container",
"image": "mcr.microsoft.com/devcontainers/python:3.12",
"postCreateCommand": "scripts/setup",
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers-extra/features/apt-packages:1": {
"packages": [
"ffmpeg",
"libturbojpeg0",
"libpcap-dev"
]
},
"ghcr.io/devcontainers-extra/features/ruff:1": {},
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/python:1": {}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
8123
],
Expand All @@ -11,14 +27,15 @@
"onAutoForward": "notify"
}
},
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "scripts/setup",
// Configure tool-specific properties.
"customizations": {
"vscode": {
"extensions": [
"charliermarsh.ruff",
"github.vscode-pull-request-github",
"ms-python.python",
"ms-python.vscode-pylance",
"ryanluker.vscode-coverage-gutters"
"ms-python.python"
],
"settings": {
"files.eol": "\n",
Expand All @@ -36,14 +53,5 @@
}
}
},
"remoteUser": "vscode",
"features": {
"ghcr.io/devcontainers-extra/features/apt-packages:1": {
"packages": [
"ffmpeg",
"libturbojpeg0",
"libpcap-dev"
]
}
}
"remoteUser": "vscode"
}
66 changes: 56 additions & 10 deletions custom_components/belgiantrain/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,79 @@

from __future__ import annotations

from homeassistant.config_entries import ConfigEntry
from datetime import timedelta
from typing import TYPE_CHECKING

from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.loader import async_get_loaded_integration

from .api import BelgiantrainApiClient
from .const import DOMAIN, LOGGER
from .coordinator import BelgiantrainDataUpdateCoordinator
from .data import BelgiantrainData

if TYPE_CHECKING:
from homeassistant.core import HomeAssistant

from .data import BelgiantrainConfigEntry

# TODO List the platforms that you want to support.
# For your initial PR, limit it to 1 platform.
PLATFORMS: list[Platform] = [Platform.LIGHT]

# TODO Create ConfigEntry type alias with API object
# TODO Rename type alias and update all entry annotations
type New_NameConfigEntry = ConfigEntry[MyApi] # noqa: F821
PLATFORMS: list[Platform] = [
Platform.SENSOR,
Platform.BINARY_SENSOR,
Platform.SWITCH,
]


# TODO Update entry annotation
async def async_setup_entry(hass: HomeAssistant, entry: New_NameConfigEntry) -> bool:
"""Set up SNCB/NMBS from a config entry."""
# https://developers.home-assistant.io/docs/config_entries_index/#setting-up-an-entry
async def async_setup_entry(
hass: HomeAssistant,
entry: BelgiantrainConfigEntry,
) -> bool:
"""Set up the SNCB/NMBS integration from a config entry using UI."""
# TODO 1. Create API instance
# TODO 2. Validate the API connection (and authentication)
# TODO 3. Store an API object for your platforms to access
# entry.runtime_data = MyAPI(...)
coordinator = BelgiantrainDataUpdateCoordinator(
hass=hass,
logger=LOGGER,
name=DOMAIN,
update_interval=timedelta(hours=1),
)
entry.runtime_data = BelgiantrainData(
client=BelgiantrainApiClient(
session=async_get_clientsession(hass),
),
integration=async_get_loaded_integration(hass, entry.domain),
coordinator=coordinator,
)

# https://developers.home-assistant.io/docs/integration_fetching_data#coordinated-single-api-poll-for-data-for-all-entities
await coordinator.async_config_entry_first_refresh()

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(async_reload_entry))

return True


# TODO Update entry annotation
async def async_unload_entry(hass: HomeAssistant, entry: New_NameConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant,
entry: BelgiantrainConfigEntry,
) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)


async def async_reload_entry(
hass: HomeAssistant,
entry: BelgiantrainConfigEntry,
) -> None:
"""Reload config entry."""
await async_unload_entry(hass, entry)
await async_setup_entry(hass, entry)
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@
import async_timeout


class IntegrationBlueprintApiClientError(Exception):
class BelgiantrainApiClientError(Exception):
"""Exception to indicate a general API error."""


class IntegrationBlueprintApiClientCommunicationError(
IntegrationBlueprintApiClientError,
class BelgiantrainApiClientCommunicationError(
BelgiantrainApiClientError,
):
"""Exception to indicate a communication error."""


class IntegrationBlueprintApiClientAuthenticationError(
IntegrationBlueprintApiClientError,
class BelgiantrainApiClientAuthenticationError(
BelgiantrainApiClientError,
):
"""Exception to indicate an authentication error."""

Expand All @@ -29,13 +29,13 @@ def _verify_response_or_raise(response: aiohttp.ClientResponse) -> None:
"""Verify that the response is valid."""
if response.status in (401, 403):
msg = "Invalid credentials"
raise IntegrationBlueprintApiClientAuthenticationError(
raise BelgiantrainApiClientAuthenticationError(
msg,
)
response.raise_for_status()


class IntegrationBlueprintApiClient:
class BelgiantrainApiClient:
"""Sample API Client."""

def __init__(
Expand Down Expand Up @@ -86,16 +86,16 @@ async def _api_wrapper(

except TimeoutError as exception:
msg = f"Timeout error fetching information - {exception}"
raise IntegrationBlueprintApiClientCommunicationError(
raise BelgiantrainApiClientCommunicationError(
msg,
) from exception
except (aiohttp.ClientError, socket.gaierror) as exception:
msg = f"Error fetching information - {exception}"
raise IntegrationBlueprintApiClientCommunicationError(
raise BelgiantrainApiClientCommunicationError(
msg,
) from exception
except Exception as exception: # pylint: disable=broad-except
msg = f"Something really wrong happened! - {exception}"
raise IntegrationBlueprintApiClientError(
raise BelgiantrainApiClientError(
msg,
) from exception
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Binary sensor platform for integration_blueprint."""
"""Binary sensor platform for belgiantrain."""

from __future__ import annotations

Expand All @@ -10,45 +10,45 @@
BinarySensorEntityDescription,
)

from .entity import IntegrationBlueprintEntity
from . import BelgiantrainEntity

if TYPE_CHECKING:
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .coordinator import BlueprintDataUpdateCoordinator
from .data import IntegrationBlueprintConfigEntry
from . import BelgiantrainDataUpdateCoordinator
from .data import BelgiantrainConfigEntry

ENTITY_DESCRIPTIONS = (
BinarySensorEntityDescription(
key="integration_blueprint",
name="Integration Blueprint Binary Sensor",
key="belgiantrain",
name="Belgiantrain Binary Sensor",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
),
)


async def async_setup_entry(
hass: HomeAssistant, # noqa: ARG001 Unused function argument: `hass`
entry: IntegrationBlueprintConfigEntry,
entry: BelgiantrainConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the binary_sensor platform."""
async_add_entities(
IntegrationBlueprintBinarySensor(
BelgiantrainBinarySensor(
coordinator=entry.runtime_data.coordinator,
entity_description=entity_description,
)
for entity_description in ENTITY_DESCRIPTIONS
)


class IntegrationBlueprintBinarySensor(IntegrationBlueprintEntity, BinarySensorEntity):
"""integration_blueprint binary_sensor class."""
class BelgiantrainBinarySensor(BelgiantrainEntity, BinarySensorEntity):
"""belgiantrain binary_sensor class."""

def __init__(
self,
coordinator: BlueprintDataUpdateCoordinator,
coordinator: BelgiantrainDataUpdateCoordinator,
entity_description: BinarySensorEntityDescription,
) -> None:
"""Initialize the binary_sensor class."""
Expand Down
5 changes: 5 additions & 0 deletions custom_components/belgiantrain/const.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
"""Constants for the SNCB/NMBS integration."""

from logging import Logger, getLogger

LOGGER: Logger = getLogger(__package__)

DOMAIN = "belgiantrain"
ATTRIBUTION = "Data provided by http://jsonplaceholder.typicode.com/"
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""DataUpdateCoordinator for integration_blueprint."""
"""DataUpdateCoordinator for belgiantrain."""

from __future__ import annotations

Expand All @@ -7,26 +7,26 @@
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .api import (
IntegrationBlueprintApiClientAuthenticationError,
IntegrationBlueprintApiClientError,
from . import (
BelgiantrainApiClientAuthenticationError,
BelgiantrainApiClientError,
)

if TYPE_CHECKING:
from .data import IntegrationBlueprintConfigEntry
from . import BelgiantrainConfigEntry


# https://developers.home-assistant.io/docs/integration_fetching_data#coordinated-single-api-poll-for-data-for-all-entities
class BlueprintDataUpdateCoordinator(DataUpdateCoordinator):
class BelgiantrainDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching data from the API."""

config_entry: IntegrationBlueprintConfigEntry
config_entry: BelgiantrainConfigEntry

async def _async_update_data(self) -> Any:
"""Update data via library."""
try:
return await self.config_entry.runtime_data.client.async_get_data()
except IntegrationBlueprintApiClientAuthenticationError as exception:
except BelgiantrainApiClientAuthenticationError as exception:
raise ConfigEntryAuthFailed(exception) from exception
except IntegrationBlueprintApiClientError as exception:
except BelgiantrainApiClientError as exception:
raise UpdateFailed(exception) from exception
27 changes: 27 additions & 0 deletions custom_components/belgiantrain/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Custom types for belgiantrain."""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from homeassistant.config_entries import ConfigEntry
from homeassistant.loader import Integration

from . import BelgiantrainApiClient
from .coordinator import BelgiantrainDataUpdateCoordinator


# TODO Create ConfigEntry type alias with API object
# TODO Rename type alias and update all entry annotations
type BelgiantrainConfigEntry = ConfigEntry[BelgiantrainData]


@dataclass
class BelgiantrainData:
"""Data for the belgiantrain integration."""

client: BelgiantrainApiClient
coordinator: BelgiantrainDataUpdateCoordinator
integration: Integration
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
"""BlueprintEntity class."""
"""BelgiantrainEntity class."""

from __future__ import annotations

from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import ATTRIBUTION
from .coordinator import BlueprintDataUpdateCoordinator
from . import BelgiantrainDataUpdateCoordinator


class IntegrationBlueprintEntity(CoordinatorEntity[BlueprintDataUpdateCoordinator]):
"""BlueprintEntity class."""
class BelgiantrainEntity(CoordinatorEntity[BelgiantrainDataUpdateCoordinator]):
"""BelgiantrainEntity class."""

_attr_attribution = ATTRIBUTION

def __init__(self, coordinator: BlueprintDataUpdateCoordinator) -> None:
def __init__(self, coordinator: BelgiantrainDataUpdateCoordinator) -> None:
"""Initialize."""
super().__init__(coordinator)
self._attr_unique_id = coordinator.config_entry.entry_id
Expand Down
Loading

0 comments on commit 1d456ba

Please sign in to comment.