Improve type annotations for the Brother integration (#49771)

This commit is contained in:
Maciej Bieniek 2021-04-29 16:59:31 +02:00 committed by GitHub
parent f7cf82be6d
commit 3210c086ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 200 additions and 155 deletions

View File

@ -6,6 +6,7 @@ homeassistant.components
homeassistant.components.automation.* homeassistant.components.automation.*
homeassistant.components.binary_sensor.* homeassistant.components.binary_sensor.*
homeassistant.components.bond.* homeassistant.components.bond.*
homeassistant.components.brother.*
homeassistant.components.calendar.* homeassistant.components.calendar.*
homeassistant.components.cover.* homeassistant.components.cover.*
homeassistant.components.device_automation.* homeassistant.components.device_automation.*

View File

@ -1,8 +1,11 @@
"""The Brother component.""" """The Brother component."""
from __future__ import annotations
from datetime import timedelta from datetime import timedelta
import logging import logging
from brother import Brother, SnmpError, UnsupportedModel from brother import Brother, DictToObj, SnmpError, UnsupportedModel
import pysnmp.hlapi.asyncio as SnmpEngine
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_TYPE from homeassistant.const import CONF_HOST, CONF_TYPE
@ -19,7 +22,7 @@ SCAN_INTERVAL = timedelta(seconds=30)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Brother from a config entry.""" """Set up Brother from a config entry."""
host = entry.data[CONF_HOST] host = entry.data[CONF_HOST]
kind = entry.data[CONF_TYPE] kind = entry.data[CONF_TYPE]
@ -41,7 +44,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
@ -57,7 +60,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
class BrotherDataUpdateCoordinator(DataUpdateCoordinator): class BrotherDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching Brother data from the printer.""" """Class to manage fetching Brother data from the printer."""
def __init__(self, hass, host, kind, snmp_engine): def __init__(
self, hass: HomeAssistant, host: str, kind: str, snmp_engine: SnmpEngine
) -> None:
"""Initialize.""" """Initialize."""
self.brother = Brother(host, kind=kind, snmp_engine=snmp_engine) self.brother = Brother(host, kind=kind, snmp_engine=snmp_engine)
@ -68,7 +73,7 @@ class BrotherDataUpdateCoordinator(DataUpdateCoordinator):
update_interval=SCAN_INTERVAL, update_interval=SCAN_INTERVAL,
) )
async def _async_update_data(self): async def _async_update_data(self) -> DictToObj:
"""Update data via library.""" """Update data via library."""
try: try:
data = await self.brother.async_update() data = await self.brother.async_update()

View File

@ -1,12 +1,17 @@
"""Adds config flow for Brother Printer.""" """Adds config flow for Brother Printer."""
from __future__ import annotations
import ipaddress import ipaddress
import re import re
from typing import Any
from brother import Brother, SnmpError, UnsupportedModel from brother import Brother, SnmpError, UnsupportedModel
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries, exceptions from homeassistant import config_entries, exceptions
from homeassistant.const import CONF_HOST, CONF_TYPE from homeassistant.const import CONF_HOST, CONF_TYPE
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.typing import DiscoveryInfoType
from .const import DOMAIN, PRINTER_TYPES from .const import DOMAIN, PRINTER_TYPES
from .utils import get_snmp_engine from .utils import get_snmp_engine
@ -19,14 +24,15 @@ DATA_SCHEMA = vol.Schema(
) )
def host_valid(host): def host_valid(host: str) -> bool:
"""Return True if hostname or IP address is valid.""" """Return True if hostname or IP address is valid."""
try: try:
if ipaddress.ip_address(host).version == (4 or 6): if ipaddress.ip_address(host).version in [4, 6]:
return True return True
except ValueError: except ValueError:
disallowed = re.compile(r"[^a-zA-Z\d\-]") disallowed = re.compile(r"[^a-zA-Z\d\-]")
return all(x and not disallowed.search(x) for x in host.split(".")) return all(x and not disallowed.search(x) for x in host.split("."))
return False
class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
@ -35,12 +41,14 @@ class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1 VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
def __init__(self): def __init__(self) -> None:
"""Initialize.""" """Initialize."""
self.brother = None self.brother: Brother = None
self.host = None self.host: str | None = None
async def async_step_user(self, user_input=None): async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step.""" """Handle the initial step."""
errors = {} errors = {}
@ -72,11 +80,10 @@ class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
step_id="user", data_schema=DATA_SCHEMA, errors=errors step_id="user", data_schema=DATA_SCHEMA, errors=errors
) )
async def async_step_zeroconf(self, discovery_info): async def async_step_zeroconf(
self, discovery_info: DiscoveryInfoType
) -> FlowResult:
"""Handle zeroconf discovery.""" """Handle zeroconf discovery."""
if discovery_info is None:
return self.async_abort(reason="cannot_connect")
if not discovery_info.get("name") or not discovery_info["name"].startswith( if not discovery_info.get("name") or not discovery_info["name"].startswith(
"Brother" "Brother"
): ):
@ -107,7 +114,9 @@ class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
) )
return await self.async_step_zeroconf_confirm() return await self.async_step_zeroconf_confirm()
async def async_step_zeroconf_confirm(self, user_input=None): async def async_step_zeroconf_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle a flow initiated by zeroconf.""" """Handle a flow initiated by zeroconf."""
if user_input is not None: if user_input is not None:
title = f"{self.brother.model} {self.brother.serial}" title = f"{self.brother.model} {self.brother.serial}"

View File

@ -1,5 +1,9 @@
"""Constants for Brother integration.""" """Constants for Brother integration."""
from homeassistant.const import ATTR_ICON, PERCENTAGE from __future__ import annotations
from typing import TypedDict
from homeassistant.const import PERCENTAGE
ATTR_BELT_UNIT_REMAINING_LIFE = "belt_unit_remaining_life" ATTR_BELT_UNIT_REMAINING_LIFE = "belt_unit_remaining_life"
ATTR_BLACK_DRUM_COUNTER = "black_drum_counter" ATTR_BLACK_DRUM_COUNTER = "black_drum_counter"
@ -18,9 +22,7 @@ ATTR_DRUM_COUNTER = "drum_counter"
ATTR_DRUM_REMAINING_LIFE = "drum_remaining_life" ATTR_DRUM_REMAINING_LIFE = "drum_remaining_life"
ATTR_DRUM_REMAINING_PAGES = "drum_remaining_pages" ATTR_DRUM_REMAINING_PAGES = "drum_remaining_pages"
ATTR_DUPLEX_COUNTER = "duplex_unit_pages_counter" ATTR_DUPLEX_COUNTER = "duplex_unit_pages_counter"
ATTR_ENABLED = "enabled"
ATTR_FUSER_REMAINING_LIFE = "fuser_remaining_life" ATTR_FUSER_REMAINING_LIFE = "fuser_remaining_life"
ATTR_LABEL = "label"
ATTR_LASER_REMAINING_LIFE = "laser_remaining_life" ATTR_LASER_REMAINING_LIFE = "laser_remaining_life"
ATTR_MAGENTA_DRUM_COUNTER = "magenta_drum_counter" ATTR_MAGENTA_DRUM_COUNTER = "magenta_drum_counter"
ATTR_MAGENTA_DRUM_REMAINING_LIFE = "magenta_drum_remaining_life" ATTR_MAGENTA_DRUM_REMAINING_LIFE = "magenta_drum_remaining_life"
@ -32,7 +34,6 @@ ATTR_PAGE_COUNTER = "page_counter"
ATTR_PF_KIT_1_REMAINING_LIFE = "pf_kit_1_remaining_life" ATTR_PF_KIT_1_REMAINING_LIFE = "pf_kit_1_remaining_life"
ATTR_PF_KIT_MP_REMAINING_LIFE = "pf_kit_mp_remaining_life" ATTR_PF_KIT_MP_REMAINING_LIFE = "pf_kit_mp_remaining_life"
ATTR_STATUS = "status" ATTR_STATUS = "status"
ATTR_UNIT = "unit"
ATTR_UPTIME = "uptime" ATTR_UPTIME = "uptime"
ATTR_YELLOW_DRUM_COUNTER = "yellow_drum_counter" ATTR_YELLOW_DRUM_COUNTER = "yellow_drum_counter"
ATTR_YELLOW_DRUM_REMAINING_LIFE = "yellow_drum_remaining_life" ATTR_YELLOW_DRUM_REMAINING_LIFE = "yellow_drum_remaining_life"
@ -50,7 +51,7 @@ PRINTER_TYPES = ["laser", "ink"]
SNMP = "snmp" SNMP = "snmp"
ATTRS_MAP = { ATTRS_MAP: dict[str, tuple[str, str]] = {
ATTR_DRUM_REMAINING_LIFE: (ATTR_DRUM_REMAINING_PAGES, ATTR_DRUM_COUNTER), ATTR_DRUM_REMAINING_LIFE: (ATTR_DRUM_REMAINING_PAGES, ATTR_DRUM_COUNTER),
ATTR_BLACK_DRUM_REMAINING_LIFE: ( ATTR_BLACK_DRUM_REMAINING_LIFE: (
ATTR_BLACK_DRUM_REMAINING_PAGES, ATTR_BLACK_DRUM_REMAINING_PAGES,
@ -70,149 +71,158 @@ ATTRS_MAP = {
), ),
} }
SENSOR_TYPES = { SENSOR_TYPES: dict[str, SensorDescription] = {
ATTR_STATUS: { ATTR_STATUS: {
ATTR_ICON: "mdi:printer", "icon": "mdi:printer",
ATTR_LABEL: ATTR_STATUS.title(), "label": ATTR_STATUS.title(),
ATTR_UNIT: None, "unit": None,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_PAGE_COUNTER: { ATTR_PAGE_COUNTER: {
ATTR_ICON: "mdi:file-document-outline", "icon": "mdi:file-document-outline",
ATTR_LABEL: ATTR_PAGE_COUNTER.replace("_", " ").title(), "label": ATTR_PAGE_COUNTER.replace("_", " ").title(),
ATTR_UNIT: UNIT_PAGES, "unit": UNIT_PAGES,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_BW_COUNTER: { ATTR_BW_COUNTER: {
ATTR_ICON: "mdi:file-document-outline", "icon": "mdi:file-document-outline",
ATTR_LABEL: ATTR_BW_COUNTER.replace("_", " ").title(), "label": ATTR_BW_COUNTER.replace("_", " ").title(),
ATTR_UNIT: UNIT_PAGES, "unit": UNIT_PAGES,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_COLOR_COUNTER: { ATTR_COLOR_COUNTER: {
ATTR_ICON: "mdi:file-document-outline", "icon": "mdi:file-document-outline",
ATTR_LABEL: ATTR_COLOR_COUNTER.replace("_", " ").title(), "label": ATTR_COLOR_COUNTER.replace("_", " ").title(),
ATTR_UNIT: UNIT_PAGES, "unit": UNIT_PAGES,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_DUPLEX_COUNTER: { ATTR_DUPLEX_COUNTER: {
ATTR_ICON: "mdi:file-document-outline", "icon": "mdi:file-document-outline",
ATTR_LABEL: ATTR_DUPLEX_COUNTER.replace("_", " ").title(), "label": ATTR_DUPLEX_COUNTER.replace("_", " ").title(),
ATTR_UNIT: UNIT_PAGES, "unit": UNIT_PAGES,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_DRUM_REMAINING_LIFE: { ATTR_DRUM_REMAINING_LIFE: {
ATTR_ICON: "mdi:chart-donut", "icon": "mdi:chart-donut",
ATTR_LABEL: ATTR_DRUM_REMAINING_LIFE.replace("_", " ").title(), "label": ATTR_DRUM_REMAINING_LIFE.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_BLACK_DRUM_REMAINING_LIFE: { ATTR_BLACK_DRUM_REMAINING_LIFE: {
ATTR_ICON: "mdi:chart-donut", "icon": "mdi:chart-donut",
ATTR_LABEL: ATTR_BLACK_DRUM_REMAINING_LIFE.replace("_", " ").title(), "label": ATTR_BLACK_DRUM_REMAINING_LIFE.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_CYAN_DRUM_REMAINING_LIFE: { ATTR_CYAN_DRUM_REMAINING_LIFE: {
ATTR_ICON: "mdi:chart-donut", "icon": "mdi:chart-donut",
ATTR_LABEL: ATTR_CYAN_DRUM_REMAINING_LIFE.replace("_", " ").title(), "label": ATTR_CYAN_DRUM_REMAINING_LIFE.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_MAGENTA_DRUM_REMAINING_LIFE: { ATTR_MAGENTA_DRUM_REMAINING_LIFE: {
ATTR_ICON: "mdi:chart-donut", "icon": "mdi:chart-donut",
ATTR_LABEL: ATTR_MAGENTA_DRUM_REMAINING_LIFE.replace("_", " ").title(), "label": ATTR_MAGENTA_DRUM_REMAINING_LIFE.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_YELLOW_DRUM_REMAINING_LIFE: { ATTR_YELLOW_DRUM_REMAINING_LIFE: {
ATTR_ICON: "mdi:chart-donut", "icon": "mdi:chart-donut",
ATTR_LABEL: ATTR_YELLOW_DRUM_REMAINING_LIFE.replace("_", " ").title(), "label": ATTR_YELLOW_DRUM_REMAINING_LIFE.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_BELT_UNIT_REMAINING_LIFE: { ATTR_BELT_UNIT_REMAINING_LIFE: {
ATTR_ICON: "mdi:current-ac", "icon": "mdi:current-ac",
ATTR_LABEL: ATTR_BELT_UNIT_REMAINING_LIFE.replace("_", " ").title(), "label": ATTR_BELT_UNIT_REMAINING_LIFE.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_FUSER_REMAINING_LIFE: { ATTR_FUSER_REMAINING_LIFE: {
ATTR_ICON: "mdi:water-outline", "icon": "mdi:water-outline",
ATTR_LABEL: ATTR_FUSER_REMAINING_LIFE.replace("_", " ").title(), "label": ATTR_FUSER_REMAINING_LIFE.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_LASER_REMAINING_LIFE: { ATTR_LASER_REMAINING_LIFE: {
ATTR_ICON: "mdi:spotlight-beam", "icon": "mdi:spotlight-beam",
ATTR_LABEL: ATTR_LASER_REMAINING_LIFE.replace("_", " ").title(), "label": ATTR_LASER_REMAINING_LIFE.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_PF_KIT_1_REMAINING_LIFE: { ATTR_PF_KIT_1_REMAINING_LIFE: {
ATTR_ICON: "mdi:printer-3d", "icon": "mdi:printer-3d",
ATTR_LABEL: ATTR_PF_KIT_1_REMAINING_LIFE.replace("_", " ").title(), "label": ATTR_PF_KIT_1_REMAINING_LIFE.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_PF_KIT_MP_REMAINING_LIFE: { ATTR_PF_KIT_MP_REMAINING_LIFE: {
ATTR_ICON: "mdi:printer-3d", "icon": "mdi:printer-3d",
ATTR_LABEL: ATTR_PF_KIT_MP_REMAINING_LIFE.replace("_", " ").title(), "label": ATTR_PF_KIT_MP_REMAINING_LIFE.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_BLACK_TONER_REMAINING: { ATTR_BLACK_TONER_REMAINING: {
ATTR_ICON: "mdi:printer-3d-nozzle", "icon": "mdi:printer-3d-nozzle",
ATTR_LABEL: ATTR_BLACK_TONER_REMAINING.replace("_", " ").title(), "label": ATTR_BLACK_TONER_REMAINING.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_CYAN_TONER_REMAINING: { ATTR_CYAN_TONER_REMAINING: {
ATTR_ICON: "mdi:printer-3d-nozzle", "icon": "mdi:printer-3d-nozzle",
ATTR_LABEL: ATTR_CYAN_TONER_REMAINING.replace("_", " ").title(), "label": ATTR_CYAN_TONER_REMAINING.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_MAGENTA_TONER_REMAINING: { ATTR_MAGENTA_TONER_REMAINING: {
ATTR_ICON: "mdi:printer-3d-nozzle", "icon": "mdi:printer-3d-nozzle",
ATTR_LABEL: ATTR_MAGENTA_TONER_REMAINING.replace("_", " ").title(), "label": ATTR_MAGENTA_TONER_REMAINING.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_YELLOW_TONER_REMAINING: { ATTR_YELLOW_TONER_REMAINING: {
ATTR_ICON: "mdi:printer-3d-nozzle", "icon": "mdi:printer-3d-nozzle",
ATTR_LABEL: ATTR_YELLOW_TONER_REMAINING.replace("_", " ").title(), "label": ATTR_YELLOW_TONER_REMAINING.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_BLACK_INK_REMAINING: { ATTR_BLACK_INK_REMAINING: {
ATTR_ICON: "mdi:printer-3d-nozzle", "icon": "mdi:printer-3d-nozzle",
ATTR_LABEL: ATTR_BLACK_INK_REMAINING.replace("_", " ").title(), "label": ATTR_BLACK_INK_REMAINING.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_CYAN_INK_REMAINING: { ATTR_CYAN_INK_REMAINING: {
ATTR_ICON: "mdi:printer-3d-nozzle", "icon": "mdi:printer-3d-nozzle",
ATTR_LABEL: ATTR_CYAN_INK_REMAINING.replace("_", " ").title(), "label": ATTR_CYAN_INK_REMAINING.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_MAGENTA_INK_REMAINING: { ATTR_MAGENTA_INK_REMAINING: {
ATTR_ICON: "mdi:printer-3d-nozzle", "icon": "mdi:printer-3d-nozzle",
ATTR_LABEL: ATTR_MAGENTA_INK_REMAINING.replace("_", " ").title(), "label": ATTR_MAGENTA_INK_REMAINING.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_YELLOW_INK_REMAINING: { ATTR_YELLOW_INK_REMAINING: {
ATTR_ICON: "mdi:printer-3d-nozzle", "icon": "mdi:printer-3d-nozzle",
ATTR_LABEL: ATTR_YELLOW_INK_REMAINING.replace("_", " ").title(), "label": ATTR_YELLOW_INK_REMAINING.replace("_", " ").title(),
ATTR_UNIT: PERCENTAGE, "unit": PERCENTAGE,
ATTR_ENABLED: True, "enabled": True,
}, },
ATTR_UPTIME: { ATTR_UPTIME: {
ATTR_ICON: None, "icon": None,
ATTR_LABEL: ATTR_UPTIME.title(), "label": ATTR_UPTIME.title(),
ATTR_UNIT: None, "unit": None,
ATTR_ENABLED: False, "enabled": False,
}, },
} }
class SensorDescription(TypedDict):
"""Sensor description class."""
icon: str | None
label: str
unit: str | None
enabled: bool

View File

@ -1,14 +1,18 @@
"""Support for the Brother service.""" """Support for the Brother service."""
from __future__ import annotations
from typing import Any
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import DEVICE_CLASS_TIMESTAMP from homeassistant.const import DEVICE_CLASS_TIMESTAMP
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import BrotherDataUpdateCoordinator
from .const import ( from .const import (
ATTR_ENABLED,
ATTR_ICON,
ATTR_LABEL,
ATTR_MANUFACTURER, ATTR_MANUFACTURER,
ATTR_UNIT,
ATTR_UPTIME, ATTR_UPTIME,
ATTRS_MAP, ATTRS_MAP,
DATA_CONFIG_ENTRY, DATA_CONFIG_ENTRY,
@ -20,9 +24,11 @@ ATTR_COUNTER = "counter"
ATTR_REMAINING_PAGES = "remaining_pages" ATTR_REMAINING_PAGES = "remaining_pages"
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Add Brother entities from a config_entry.""" """Add Brother entities from a config_entry."""
coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id] coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id]
sensors = [] sensors = []
@ -43,36 +49,42 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class BrotherPrinterSensor(CoordinatorEntity, SensorEntity): class BrotherPrinterSensor(CoordinatorEntity, SensorEntity):
"""Define an Brother Printer sensor.""" """Define an Brother Printer sensor."""
def __init__(self, coordinator, kind, device_info): def __init__(
self,
coordinator: BrotherDataUpdateCoordinator,
kind: str,
device_info: dict[str, Any],
) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator)
self._name = f"{coordinator.data.model} {SENSOR_TYPES[kind][ATTR_LABEL]}" self._description = SENSOR_TYPES[kind]
self._name = f"{coordinator.data.model} {self._description['label']}"
self._unique_id = f"{coordinator.data.serial.lower()}_{kind}" self._unique_id = f"{coordinator.data.serial.lower()}_{kind}"
self._device_info = device_info self._device_info = device_info
self.kind = kind self.kind = kind
self._attrs = {} self._attrs: dict[str, Any] = {}
@property @property
def name(self): def name(self) -> str:
"""Return the name.""" """Return the name."""
return self._name return self._name
@property @property
def state(self): def state(self) -> Any:
"""Return the state.""" """Return the state."""
if self.kind == ATTR_UPTIME: if self.kind == ATTR_UPTIME:
return getattr(self.coordinator.data, self.kind).isoformat() return getattr(self.coordinator.data, self.kind).isoformat()
return getattr(self.coordinator.data, self.kind) return getattr(self.coordinator.data, self.kind)
@property @property
def device_class(self): def device_class(self) -> str | None:
"""Return the class of this sensor.""" """Return the class of this sensor."""
if self.kind == ATTR_UPTIME: if self.kind == ATTR_UPTIME:
return DEVICE_CLASS_TIMESTAMP return DEVICE_CLASS_TIMESTAMP
return None return None
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes.""" """Return the state attributes."""
remaining_pages, drum_counter = ATTRS_MAP.get(self.kind, (None, None)) remaining_pages, drum_counter = ATTRS_MAP.get(self.kind, (None, None))
if remaining_pages and drum_counter: if remaining_pages and drum_counter:
@ -83,26 +95,26 @@ class BrotherPrinterSensor(CoordinatorEntity, SensorEntity):
return self._attrs return self._attrs
@property @property
def icon(self): def icon(self) -> str | None:
"""Return the icon.""" """Return the icon."""
return SENSOR_TYPES[self.kind][ATTR_ICON] return self._description["icon"]
@property @property
def unique_id(self): def unique_id(self) -> str:
"""Return a unique_id for this entity.""" """Return a unique_id for this entity."""
return self._unique_id return self._unique_id
@property @property
def unit_of_measurement(self): def unit_of_measurement(self) -> str | None:
"""Return the unit the value is expressed in.""" """Return the unit the value is expressed in."""
return SENSOR_TYPES[self.kind][ATTR_UNIT] return self._description["unit"]
@property @property
def device_info(self): def device_info(self) -> dict[str, Any]:
"""Return the device info.""" """Return the device info."""
return self._device_info return self._device_info
@property @property
def entity_registry_enabled_default(self): def entity_registry_enabled_default(self) -> bool:
"""Return if the entity should be enabled when first added to the entity registry.""" """Return if the entity should be enabled when first added to the entity registry."""
return SENSOR_TYPES[self.kind][ATTR_ENABLED] return self._description["enabled"]

View File

@ -5,7 +5,7 @@ import pysnmp.hlapi.asyncio as hlapi
from pysnmp.hlapi.asyncio.cmdgen import lcd from pysnmp.hlapi.asyncio.cmdgen import lcd
from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import callback from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.helpers import singleton from homeassistant.helpers import singleton
from .const import DOMAIN, SNMP from .const import DOMAIN, SNMP
@ -14,13 +14,13 @@ _LOGGER = logging.getLogger(__name__)
@singleton.singleton("snmp_engine") @singleton.singleton("snmp_engine")
def get_snmp_engine(hass): def get_snmp_engine(hass: HomeAssistant) -> hlapi.SnmpEngine:
"""Get SNMP engine.""" """Get SNMP engine."""
_LOGGER.debug("Creating SNMP engine") _LOGGER.debug("Creating SNMP engine")
snmp_engine = hlapi.SnmpEngine() snmp_engine = hlapi.SnmpEngine()
@callback @callback
def shutdown_listener(ev): def shutdown_listener(ev: Event) -> None:
if hass.data.get(DOMAIN): if hass.data.get(DOMAIN):
_LOGGER.debug("Unconfiguring SNMP engine") _LOGGER.debug("Unconfiguring SNMP engine")
lcd.unconfigure(hass.data[DOMAIN][SNMP], None) lcd.unconfigure(hass.data[DOMAIN][SNMP], None)

View File

@ -35,7 +35,7 @@ warn_return_any = false
warn_unreachable = false warn_unreachable = false
warn_unused_ignores = false warn_unused_ignores = false
[mypy-homeassistant.components,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.knx.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.recorder.purge,homeassistant.components.recorder.repack,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sonos.media_player,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zeroconf.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*] [mypy-homeassistant.components,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.brother.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.knx.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.recorder.purge,homeassistant.components.recorder.repack,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sonos.media_player,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zeroconf.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*]
check_untyped_defs = true check_untyped_defs = true
disallow_incomplete_defs = true disallow_incomplete_defs = true
disallow_subclassing_any = true disallow_subclassing_any = true

View File

@ -40,8 +40,8 @@ async def test_create_entry_with_hostname(hass):
assert result["data"][CONF_TYPE] == CONFIG[CONF_TYPE] assert result["data"][CONF_TYPE] == CONFIG[CONF_TYPE]
async def test_create_entry_with_ip_address(hass): async def test_create_entry_with_ipv4_address(hass):
"""Test that the user step works with printer IP address.""" """Test that the user step works with printer IPv4 address."""
with patch( with patch(
"brother.Brother._get_data", "brother.Brother._get_data",
return_value=json.loads(load_fixture("brother_printer_data.json")), return_value=json.loads(load_fixture("brother_printer_data.json")),
@ -58,6 +58,24 @@ async def test_create_entry_with_ip_address(hass):
assert result["data"][CONF_TYPE] == "laser" assert result["data"][CONF_TYPE] == "laser"
async def test_create_entry_with_ipv6_address(hass):
"""Test that the user step works with printer IPv6 address."""
with patch(
"brother.Brother._get_data",
return_value=json.loads(load_fixture("brother_printer_data.json")),
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data={CONF_HOST: "2001:db8::1428:57ab", CONF_TYPE: "laser"},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "HL-L2340DW 0123456789"
assert result["data"][CONF_HOST] == "2001:db8::1428:57ab"
assert result["data"][CONF_TYPE] == "laser"
async def test_invalid_hostname(hass): async def test_invalid_hostname(hass):
"""Test invalid hostname in user_input.""" """Test invalid hostname in user_input."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -118,16 +136,6 @@ async def test_device_exists_abort(hass):
assert result["reason"] == "already_configured" assert result["reason"] == "already_configured"
async def test_zeroconf_no_data(hass):
"""Test we abort if zeroconf provides no data."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_ZEROCONF}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "cannot_connect"
async def test_zeroconf_not_brother_printer_error(hass): async def test_zeroconf_not_brother_printer_error(hass):
"""Test we abort zeroconf flow if printer isn't Brother.""" """Test we abort zeroconf flow if printer isn't Brother."""
with patch( with patch(