105 lines
2.9 KiB
Python
105 lines
2.9 KiB
Python
"""Utility methods."""
|
|
|
|
from collections.abc import Callable
|
|
from importlib.metadata import PackageNotFoundError, version
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
# netifaces lib is from netifaces2
|
|
import netifaces
|
|
|
|
# Cache for version to avoid repeated file reading
|
|
_version_cache: Optional[str] = None
|
|
_esphome_version_cache: Optional[str] = None
|
|
|
|
|
|
def get_version() -> str:
|
|
"""
|
|
Read the version from version.txt file.
|
|
|
|
This function reads the content safely without risk of code injection,
|
|
as it only reads raw text and performs no evaluation.
|
|
|
|
Returns:
|
|
str: The version from version.txt or 'unknown' if the file
|
|
does not exist or cannot be read.
|
|
"""
|
|
global _version_cache
|
|
|
|
if _version_cache is not None:
|
|
return _version_cache
|
|
|
|
version_file = Path(__file__).parent.parent / "version.txt"
|
|
|
|
try:
|
|
# Sicher lesen: nur Rohtext, keine Evaluierung
|
|
file_version = version_file.read_text(encoding="utf-8").strip()
|
|
_version_cache = file_version if file_version else "unknown"
|
|
except (FileNotFoundError, PermissionError, OSError):
|
|
_version_cache = "unknown"
|
|
|
|
return _version_cache
|
|
|
|
|
|
def get_esphome_version() -> str:
|
|
"""
|
|
Read the version of the installed aioesphomeapi package.
|
|
|
|
This function uses importlib.metadata to safely retrieve the version
|
|
of an installed Python package without executing any code from the
|
|
package itself.
|
|
|
|
Returns:
|
|
str: The version of aioesphomeapi (e.g., '42.7.0'), or 'unknown'
|
|
if the package is not installed or the version cannot be read.
|
|
"""
|
|
global _esphome_version_cache
|
|
|
|
if _esphome_version_cache is not None:
|
|
return _esphome_version_cache
|
|
|
|
try:
|
|
_esphome_version_cache = version("aioesphomeapi")
|
|
except PackageNotFoundError:
|
|
_esphome_version_cache = "unknown"
|
|
|
|
return _esphome_version_cache
|
|
|
|
|
|
def call_all(*callables: Optional[Callable[[], None]]) -> None:
|
|
for item in filter(None, callables):
|
|
item()
|
|
|
|
|
|
def get_default_interface():
|
|
"""Return the default network interface name, or None if not found."""
|
|
default_gateway = netifaces.default_gateway()
|
|
|
|
if not default_gateway:
|
|
print("No default gateway found")
|
|
return None
|
|
|
|
# default_gateway is e.g. {InterfaceType.AF_INET: ('192.168.33.1', 'wlp0s20f3')}
|
|
gateway_info = default_gateway.get(netifaces.AF_INET)
|
|
if not gateway_info:
|
|
print("No default IPv4 gateway found")
|
|
return None
|
|
|
|
# gateway_info is a tuple: (gateway_ip, interface_name)
|
|
interface_name = gateway_info[1]
|
|
# print(f"Default interface: {interface_name}")
|
|
return interface_name
|
|
|
|
|
|
def get_default_ipv4(interface: str):
|
|
if not interface:
|
|
return None
|
|
|
|
addresses = netifaces.ifaddresses(interface)
|
|
ipv4_info = addresses.get(netifaces.AF_INET) # type: ignore
|
|
|
|
if not ipv4_info:
|
|
return None
|
|
|
|
return ipv4_info[0]["addr"]
|