1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-20 02:48:57 +00:00

Add strict typing, shared constants, and fix OPNsense name casing (#151599)

This commit is contained in:
Kevin McCormack
2025-09-04 05:20:16 -04:00
committed by GitHub
parent 8d945d89de
commit eae1fe4a56
7 changed files with 63 additions and 35 deletions

View File

@@ -383,6 +383,7 @@ homeassistant.components.openai_conversation.*
homeassistant.components.openexchangerates.* homeassistant.components.openexchangerates.*
homeassistant.components.opensky.* homeassistant.components.opensky.*
homeassistant.components.openuv.* homeassistant.components.openuv.*
homeassistant.components.opnsense.*
homeassistant.components.opower.* homeassistant.components.opower.*
homeassistant.components.oralb.* homeassistant.components.oralb.*
homeassistant.components.otbr.* homeassistant.components.otbr.*

View File

@@ -1,4 +1,4 @@
"""Support for OPNSense Routers.""" """Support for OPNsense Routers."""
import logging import logging
@@ -12,15 +12,16 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.discovery import load_platform from homeassistant.helpers.discovery import load_platform
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from .const import (
CONF_API_SECRET,
CONF_INTERFACE_CLIENT,
CONF_TRACKER_INTERFACES,
DOMAIN,
OPNSENSE_DATA,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_API_SECRET = "api_secret"
CONF_TRACKER_INTERFACE = "tracker_interfaces"
DOMAIN = "opnsense"
OPNSENSE_DATA = DOMAIN
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
{ {
DOMAIN: vol.Schema( DOMAIN: vol.Schema(
@@ -29,7 +30,7 @@ CONFIG_SCHEMA = vol.Schema(
vol.Required(CONF_API_KEY): cv.string, vol.Required(CONF_API_KEY): cv.string,
vol.Required(CONF_API_SECRET): cv.string, vol.Required(CONF_API_SECRET): cv.string,
vol.Optional(CONF_VERIFY_SSL, default=False): cv.boolean, vol.Optional(CONF_VERIFY_SSL, default=False): cv.boolean,
vol.Optional(CONF_TRACKER_INTERFACE, default=[]): vol.All( vol.Optional(CONF_TRACKER_INTERFACES, default=[]): vol.All(
cv.ensure_list, [cv.string] cv.ensure_list, [cv.string]
), ),
} }
@@ -47,7 +48,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
api_key = conf[CONF_API_KEY] api_key = conf[CONF_API_KEY]
api_secret = conf[CONF_API_SECRET] api_secret = conf[CONF_API_SECRET]
verify_ssl = conf[CONF_VERIFY_SSL] verify_ssl = conf[CONF_VERIFY_SSL]
tracker_interfaces = conf[CONF_TRACKER_INTERFACE] tracker_interfaces = conf[CONF_TRACKER_INTERFACES]
interfaces_client = diagnostics.InterfaceClient( interfaces_client = diagnostics.InterfaceClient(
api_key, api_secret, url, verify_ssl, timeout=20 api_key, api_secret, url, verify_ssl, timeout=20
@@ -72,8 +73,8 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
return False return False
hass.data[OPNSENSE_DATA] = { hass.data[OPNSENSE_DATA] = {
"interfaces": interfaces_client, CONF_INTERFACE_CLIENT: interfaces_client,
CONF_TRACKER_INTERFACE: tracker_interfaces, CONF_TRACKER_INTERFACES: tracker_interfaces,
} }
load_platform(hass, Platform.DEVICE_TRACKER, DOMAIN, tracker_interfaces, config) load_platform(hass, Platform.DEVICE_TRACKER, DOMAIN, tracker_interfaces, config)

View File

@@ -0,0 +1,8 @@
"""Constants for OPNsense component."""
DOMAIN = "opnsense"
OPNSENSE_DATA = DOMAIN
CONF_API_SECRET = "api_secret"
CONF_INTERFACE_CLIENT = "interface_client"
CONF_TRACKER_INTERFACES = "tracker_interfaces"

View File

@@ -1,34 +1,41 @@
"""Device tracker support for OPNSense routers.""" """Device tracker support for OPNsense routers."""
from __future__ import annotations from typing import Any, NewType
from pyopnsense import diagnostics
from homeassistant.components.device_tracker import DeviceScanner from homeassistant.components.device_tracker import DeviceScanner
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from . import CONF_TRACKER_INTERFACE, OPNSENSE_DATA from .const import CONF_INTERFACE_CLIENT, CONF_TRACKER_INTERFACES, OPNSENSE_DATA
DeviceDetails = NewType("DeviceDetails", dict[str, Any])
DeviceDetailsByMAC = NewType("DeviceDetailsByMAC", dict[str, DeviceDetails])
async def async_get_scanner( async def async_get_scanner(
hass: HomeAssistant, config: ConfigType hass: HomeAssistant, config: ConfigType
) -> OPNSenseDeviceScanner: ) -> DeviceScanner | None:
"""Configure the OPNSense device_tracker.""" """Configure the OPNsense device_tracker."""
interface_client = hass.data[OPNSENSE_DATA]["interfaces"] return OPNsenseDeviceScanner(
return OPNSenseDeviceScanner( hass.data[OPNSENSE_DATA][CONF_INTERFACE_CLIENT],
interface_client, hass.data[OPNSENSE_DATA][CONF_TRACKER_INTERFACE] hass.data[OPNSENSE_DATA][CONF_TRACKER_INTERFACES],
) )
class OPNSenseDeviceScanner(DeviceScanner): class OPNsenseDeviceScanner(DeviceScanner):
"""Class which queries a router running OPNsense.""" """This class queries a router running OPNsense."""
def __init__(self, client, interfaces): def __init__(
self, client: diagnostics.InterfaceClient, interfaces: list[str]
) -> None:
"""Initialize the scanner.""" """Initialize the scanner."""
self.last_results = {} self.last_results: dict[str, Any] = {}
self.client = client self.client = client
self.interfaces = interfaces self.interfaces = interfaces
def _get_mac_addrs(self, devices): def _get_mac_addrs(self, devices: list[DeviceDetails]) -> DeviceDetailsByMAC | dict:
"""Create dict with mac address keys from list of devices.""" """Create dict with mac address keys from list of devices."""
out_devices = {} out_devices = {}
for device in devices: for device in devices:
@@ -36,30 +43,31 @@ class OPNSenseDeviceScanner(DeviceScanner):
out_devices[device["mac"]] = device out_devices[device["mac"]] = device
return out_devices return out_devices
def scan_devices(self): def scan_devices(self) -> list[str]:
"""Scan for new devices and return a list with found device IDs.""" """Scan for new devices and return a list with found device IDs."""
self.update_info() self.update_info()
return list(self.last_results) return list(self.last_results)
def get_device_name(self, device): def get_device_name(self, device: str) -> str | None:
"""Return the name of the given device or None if we don't know.""" """Return the name of the given device or None if we don't know."""
if device not in self.last_results: if device not in self.last_results:
return None return None
return self.last_results[device].get("hostname") or None return self.last_results[device].get("hostname") or None
def update_info(self): def update_info(self) -> bool:
"""Ensure the information from the OPNSense router is up to date. """Ensure the information from the OPNsense router is up to date.
Return boolean if scanning successful. Return boolean if scanning successful.
""" """
devices = self.client.get_arp() devices = self.client.get_arp()
self.last_results = self._get_mac_addrs(devices) self.last_results = self._get_mac_addrs(devices)
return True
def get_extra_attributes(self, device): def get_extra_attributes(self, device: str) -> dict[Any, Any]:
"""Return the extra attrs of the given device.""" """Return the extra attrs of the given device."""
if device not in self.last_results: if device not in self.last_results:
return None return {}
if not (mfg := self.last_results[device].get("manufacturer")): mfg = self.last_results[device].get("manufacturer")
if not mfg:
return {} return {}
return {"manufacturer": mfg} return {"manufacturer": mfg}

View File

@@ -1,6 +1,6 @@
{ {
"domain": "opnsense", "domain": "opnsense",
"name": "OPNSense", "name": "OPNsense",
"codeowners": ["@mtreinish"], "codeowners": ["@mtreinish"],
"documentation": "https://www.home-assistant.io/integrations/opnsense", "documentation": "https://www.home-assistant.io/integrations/opnsense",
"iot_class": "local_polling", "iot_class": "local_polling",

View File

@@ -4736,7 +4736,7 @@
} }
}, },
"opnsense": { "opnsense": {
"name": "OPNSense", "name": "OPNsense",
"integration_type": "hub", "integration_type": "hub",
"config_flow": false, "config_flow": false,
"iot_class": "local_polling" "iot_class": "local_polling"

10
mypy.ini generated
View File

@@ -3586,6 +3586,16 @@ disallow_untyped_defs = true
warn_return_any = true warn_return_any = true
warn_unreachable = true warn_unreachable = true
[mypy-homeassistant.components.opnsense.*]
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true
[mypy-homeassistant.components.opower.*] [mypy-homeassistant.components.opower.*]
check_untyped_defs = true check_untyped_defs = true
disallow_incomplete_defs = true disallow_incomplete_defs = true