Skip to content

Commit

Permalink
Code format updates (#166)
Browse files Browse the repository at this point in the history
* Code format updates

* usa alias for all
  • Loading branch information
ludeeus authored Jun 8, 2024
1 parent 384b980 commit 1c1fc8a
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 110 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,8 @@ jobs:
- name: "Install requirements"
run: python3 -m pip install -r requirements.txt

- name: "Run"
- name: "Lint"
run: python3 -m ruff check .

- name: "Format"
run: python3 -m ruff format . --check
37 changes: 7 additions & 30 deletions .ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,16 @@ target-version = "py312"

[lint]
select = [
"B007", # Loop control variable {name} not used within loop body
"B014", # Exception handler with duplicate exception
"C", # complexity
"D", # docstrings
"E", # pycodestyle
"F", # pyflakes/autoflake
"ICN001", # import concentions; {name} should be imported as {asname}
"PGH004", # Use specific rule codes when using noqa
"PLC0414", # Useless import alias. Import alias does not rename original package.
"SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass
"SIM117", # Merge with-statements that use the same scope
"SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys()
"SIM201", # Use {left} != {right} instead of not {left} == {right}
"SIM212", # Use {a} if {a} else {b} instead of {b} if not {a} else {a}
"SIM300", # Yoda conditions. Use 'age == 42' instead of '42 == age'.
"SIM401", # Use get from dict with default instead of an if block
"T20", # flake8-print
"TRY004", # Prefer TypeError exception for invalid type
"RUF006", # Store a reference to the return value of asyncio.create_task
"UP", # pyupgrade
"W", # pycodestyle
"ALL",
]

ignore = [
"D202", # No blank lines allowed after function docstring
"D203", # 1 blank line required before class docstring
"D213", # Multi-line docstring summary should start at the second line
"D404", # First word of the docstring should not be This
"D406", # Section name should end with a newline
"D407", # Section name underlining
"D411", # Missing blank line before section
"E501", # line too long
"E731", # do not assign a lambda expression, use a def
"ANN101", # Missing type annotation for `self` in method
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed
"D203", # no-blank-line-before-class (incompatible with formatter)
"D212", # multi-line-summary-first-line (incompatible with formatter)
"COM812", # incompatible with formatter
"ISC001", # incompatible with formatter
]

[lint.flake8-pytest-style]
Expand Down
43 changes: 31 additions & 12 deletions custom_components/integration_blueprint/__init__.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
"""Custom integration to integrate integration_blueprint with Home Assistant.
"""
Custom integration to integrate integration_blueprint with Home Assistant.
For more details about this integration, please refer to
https://github.com/ludeeus/integration_blueprint
"""

from __future__ import annotations

from homeassistant.config_entries import ConfigEntry
from typing import TYPE_CHECKING

from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, 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 IntegrationBlueprintApiClient
from .const import DOMAIN
from .coordinator import BlueprintDataUpdateCoordinator
from .data import IntegrationBlueprintData

if TYPE_CHECKING:
from homeassistant.core import HomeAssistant

from .data import IntegrationBlueprintConfigEntry

PLATFORMS: list[Platform] = [
Platform.SENSOR,
Expand All @@ -22,17 +30,24 @@


# https://developers.home-assistant.io/docs/config_entries_index/#setting-up-an-entry
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant,
entry: IntegrationBlueprintConfigEntry,
) -> bool:
"""Set up this integration using UI."""
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = coordinator = BlueprintDataUpdateCoordinator(
coordinator = BlueprintDataUpdateCoordinator(
hass=hass,
)
entry.runtime_data = IntegrationBlueprintData(
client=IntegrationBlueprintApiClient(
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
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()

Expand All @@ -42,14 +57,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant,
entry: IntegrationBlueprintConfigEntry,
) -> bool:
"""Handle removal of an entry."""
if unloaded := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unloaded
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)


async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
async def async_reload_entry(
hass: HomeAssistant,
entry: IntegrationBlueprintConfigEntry,
) -> None:
"""Reload config entry."""
await async_unload_entry(hass, entry)
await async_setup_entry(hass, entry)
40 changes: 26 additions & 14 deletions custom_components/integration_blueprint/api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""Sample API Client."""

from __future__ import annotations

import socket
from typing import Any

import aiohttp
import async_timeout
Expand All @@ -12,17 +14,27 @@ class IntegrationBlueprintApiClientError(Exception):


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


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


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(
msg,
)
response.raise_for_status()


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

Expand All @@ -37,13 +49,14 @@ def __init__(
self._password = password
self._session = session

async def async_get_data(self) -> any:
async def async_get_data(self) -> Any:
"""Get data from the API."""
return await self._api_wrapper(
method="get", url="https://jsonplaceholder.typicode.com/posts/1"
method="get",
url="https://jsonplaceholder.typicode.com/posts/1",
)

async def async_set_title(self, value: str) -> any:
async def async_set_title(self, value: str) -> Any:
"""Get data from the API."""
return await self._api_wrapper(
method="patch",
Expand All @@ -58,7 +71,7 @@ async def _api_wrapper(
url: str,
data: dict | None = None,
headers: dict | None = None,
) -> any:
) -> Any:
"""Get information from the API."""
try:
async with async_timeout.timeout(10):
Expand All @@ -68,22 +81,21 @@ async def _api_wrapper(
headers=headers,
json=data,
)
if response.status in (401, 403):
raise IntegrationBlueprintApiClientAuthenticationError(
"Invalid credentials",
)
response.raise_for_status()
_verify_response_or_raise(response)
return await response.json()

except TimeoutError as exception:
msg = f"Timeout error fetching information - {exception}"
raise IntegrationBlueprintApiClientCommunicationError(
"Timeout error fetching information",
msg,
) from exception
except (aiohttp.ClientError, socket.gaierror) as exception:
msg = f"Error fetching information - {exception}"
raise IntegrationBlueprintApiClientCommunicationError(
"Error fetching information",
msg,
) from exception
except Exception as exception: # pylint: disable=broad-except
msg = f"Something really wrong happened! - {exception}"
raise IntegrationBlueprintApiClientError(
"Something really wrong happened!"
msg,
) from exception
23 changes: 17 additions & 6 deletions custom_components/integration_blueprint/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
"""Binary sensor platform for integration_blueprint."""

from __future__ import annotations

from typing import TYPE_CHECKING

from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)

from .const import DOMAIN
from .coordinator import BlueprintDataUpdateCoordinator
from .entity import IntegrationBlueprintEntity

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

from .coordinator import BlueprintDataUpdateCoordinator
from .data import IntegrationBlueprintConfigEntry

ENTITY_DESCRIPTIONS = (
BinarySensorEntityDescription(
key="integration_blueprint",
Expand All @@ -20,12 +28,15 @@
)


async def async_setup_entry(hass, entry, async_add_devices):
async def async_setup_entry(
hass: HomeAssistant, # noqa: ARG001 Unused function argument: `hass`
entry: IntegrationBlueprintConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the binary_sensor platform."""
coordinator = hass.data[DOMAIN][entry.entry_id]
async_add_devices(
async_add_entities(
IntegrationBlueprintBinarySensor(
coordinator=coordinator,
coordinator=entry.runtime_data.coordinator,
entity_description=entity_description,
)
for entity_description in ENTITY_DESCRIPTIONS
Expand Down
13 changes: 6 additions & 7 deletions custom_components/integration_blueprint/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
from __future__ import annotations

import voluptuous as vol
from homeassistant import config_entries
from homeassistant import config_entries, data_entry_flow
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import selector
from homeassistant.helpers.aiohttp_client import async_create_clientsession


from .api import (
IntegrationBlueprintApiClient,
IntegrationBlueprintApiClientAuthenticationError,
Expand All @@ -26,7 +25,7 @@ class BlueprintFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_user(
self,
user_input: dict | None = None,
) -> config_entries.FlowResult:
) -> data_entry_flow.FlowResult:
"""Handle a flow initialized by the user."""
_errors = {}
if user_input is not None:
Expand Down Expand Up @@ -56,18 +55,18 @@ async def async_step_user(
{
vol.Required(
CONF_USERNAME,
default=(user_input or {}).get(CONF_USERNAME),
default=(user_input or {}).get(CONF_USERNAME, vol.UNDEFINED),
): selector.TextSelector(
selector.TextSelectorConfig(
type=selector.TextSelectorType.TEXT
type=selector.TextSelectorType.TEXT,
),
),
vol.Required(CONF_PASSWORD): selector.TextSelector(
selector.TextSelectorConfig(
type=selector.TextSelectorType.PASSWORD
type=selector.TextSelectorType.PASSWORD,
),
),
}
},
),
errors=_errors,
)
Expand Down
2 changes: 0 additions & 2 deletions custom_components/integration_blueprint/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,5 @@

LOGGER: Logger = getLogger(__package__)

NAME = "Integration blueprint"
DOMAIN = "integration_blueprint"
VERSION = "0.0.0"
ATTRIBUTION = "Data provided by http://jsonplaceholder.typicode.com/"
25 changes: 12 additions & 13 deletions custom_components/integration_blueprint/coordinator.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,47 @@
"""DataUpdateCoordinator for integration_blueprint."""

from __future__ import annotations

from datetime import timedelta
from typing import TYPE_CHECKING, Any

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import (
DataUpdateCoordinator,
UpdateFailed,
)
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .api import (
IntegrationBlueprintApiClient,
IntegrationBlueprintApiClientAuthenticationError,
IntegrationBlueprintApiClientError,
)
from .const import DOMAIN, LOGGER

if TYPE_CHECKING:
from homeassistant.core import HomeAssistant

from .data import IntegrationBlueprintConfigEntry


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

config_entry: ConfigEntry
config_entry: IntegrationBlueprintConfigEntry

def __init__(
self,
hass: HomeAssistant,
client: IntegrationBlueprintApiClient,
) -> None:
"""Initialize."""
self.client = client
super().__init__(
hass=hass,
logger=LOGGER,
name=DOMAIN,
update_interval=timedelta(minutes=5),
update_interval=timedelta(hours=1),
)

async def _async_update_data(self):
async def _async_update_data(self) -> Any:
"""Update data via library."""
try:
return await self.client.async_get_data()
return await self.config_entry.runtime_data.client.async_get_data()
except IntegrationBlueprintApiClientAuthenticationError as exception:
raise ConfigEntryAuthFailed(exception) from exception
except IntegrationBlueprintApiClientError as exception:
Expand Down
Loading

0 comments on commit 1c1fc8a

Please sign in to comment.