Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ on:
type: boolean

env:
CACHE_VERSION: 2
CACHE_VERSION: 3
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 1
HA_SHORT_VERSION: "2025.7"
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.12
rev: v0.12.0
hooks:
- id: ruff-check
args:
Expand Down
12 changes: 4 additions & 8 deletions homeassistant/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ def validate_python() -> None:

def ensure_config_path(config_dir: str) -> None:
"""Validate the configuration directory."""
# pylint: disable-next=import-outside-toplevel
from . import config as config_util
from . import config as config_util # noqa: PLC0415

lib_dir = os.path.join(config_dir, "deps")

Expand Down Expand Up @@ -80,8 +79,7 @@ def ensure_config_path(config_dir: str) -> None:

def get_arguments() -> argparse.Namespace:
"""Get parsed passed in arguments."""
# pylint: disable-next=import-outside-toplevel
from . import config as config_util
from . import config as config_util # noqa: PLC0415

parser = argparse.ArgumentParser(
description="Home Assistant: Observe, Control, Automate.",
Expand Down Expand Up @@ -177,8 +175,7 @@ def main() -> int:
validate_os()

if args.script is not None:
# pylint: disable-next=import-outside-toplevel
from . import scripts
from . import scripts # noqa: PLC0415

return scripts.run(args.script)

Expand All @@ -188,8 +185,7 @@ def main() -> int:

ensure_config_path(config_dir)

# pylint: disable-next=import-outside-toplevel
from . import config, runner
from . import config, runner # noqa: PLC0415

safe_mode = config.safe_mode_enabled(config_dir)

Expand Down
8 changes: 4 additions & 4 deletions homeassistant/auth/mfa_modules/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,28 +52,28 @@

def _generate_secret() -> str:
"""Generate a secret."""
import pyotp # pylint: disable=import-outside-toplevel
import pyotp # noqa: PLC0415

return str(pyotp.random_base32())


def _generate_random() -> int:
"""Generate a 32 digit number."""
import pyotp # pylint: disable=import-outside-toplevel
import pyotp # noqa: PLC0415

return int(pyotp.random_base32(length=32, chars=list("1234567890")))


def _generate_otp(secret: str, count: int) -> str:
"""Generate one time password."""
import pyotp # pylint: disable=import-outside-toplevel
import pyotp # noqa: PLC0415

return str(pyotp.HOTP(secret).at(count))


def _verify_otp(secret: str, otp: str, count: int) -> bool:
"""Verify one time password."""
import pyotp # pylint: disable=import-outside-toplevel
import pyotp # noqa: PLC0415

return bool(pyotp.HOTP(secret).verify(otp, count))

Expand Down
10 changes: 5 additions & 5 deletions homeassistant/auth/mfa_modules/totp.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

def _generate_qr_code(data: str) -> str:
"""Generate a base64 PNG string represent QR Code image of data."""
import pyqrcode # pylint: disable=import-outside-toplevel
import pyqrcode # noqa: PLC0415

qr_code = pyqrcode.create(data)

Expand All @@ -59,7 +59,7 @@ def _generate_qr_code(data: str) -> str:

def _generate_secret_and_qr_code(username: str) -> tuple[str, str, str]:
"""Generate a secret, url, and QR code."""
import pyotp # pylint: disable=import-outside-toplevel
import pyotp # noqa: PLC0415

ota_secret = pyotp.random_base32()
url = pyotp.totp.TOTP(ota_secret).provisioning_uri(
Expand Down Expand Up @@ -107,7 +107,7 @@ async def _async_save(self) -> None:

def _add_ota_secret(self, user_id: str, secret: str | None = None) -> str:
"""Create a ota_secret for user."""
import pyotp # pylint: disable=import-outside-toplevel
import pyotp # noqa: PLC0415

ota_secret: str = secret or pyotp.random_base32()

Expand Down Expand Up @@ -163,7 +163,7 @@ async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool

def _validate_2fa(self, user_id: str, code: str) -> bool:
"""Validate two factor authentication code."""
import pyotp # pylint: disable=import-outside-toplevel
import pyotp # noqa: PLC0415

if (ota_secret := self._users.get(user_id)) is None: # type: ignore[union-attr]
# even we cannot find user, we still do verify
Expand Down Expand Up @@ -196,7 +196,7 @@ async def async_step_init(
Return self.async_show_form(step_id='init') if user_input is None.
Return self.async_create_entry(data={'result': result}) if finish.
"""
import pyotp # pylint: disable=import-outside-toplevel
import pyotp # noqa: PLC0415

errors: dict[str, str] = {}

Expand Down
9 changes: 4 additions & 5 deletions homeassistant/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ async def create_hass() -> core.HomeAssistant:

def open_hass_ui(hass: core.HomeAssistant) -> None:
"""Open the UI."""
import webbrowser # pylint: disable=import-outside-toplevel
import webbrowser # noqa: PLC0415

if hass.config.api is None or "frontend" not in hass.config.components:
_LOGGER.warning("Cannot launch the UI because frontend not loaded")
Expand Down Expand Up @@ -561,8 +561,7 @@ async def async_enable_logging(

if not log_no_color:
try:
# pylint: disable-next=import-outside-toplevel
from colorlog import ColoredFormatter
from colorlog import ColoredFormatter # noqa: PLC0415

# basicConfig must be called after importing colorlog in order to
# ensure that the handlers it sets up wraps the correct streams.
Expand Down Expand Up @@ -606,7 +605,7 @@ async def async_enable_logging(
)
threading.excepthook = lambda args: logging.getLogger().exception(
"Uncaught thread exception",
exc_info=( # type: ignore[arg-type]
exc_info=( # type: ignore[arg-type] # noqa: LOG014
args.exc_type,
args.exc_value,
args.exc_traceback,
Expand Down Expand Up @@ -1060,5 +1059,5 @@ async def _async_setup_multi_components(
_LOGGER.error(
"Error setting up integration %s - received exception",
domain,
exc_info=(type(result), result, result.__traceback__),
exc_info=(type(result), result, result.__traceback__), # noqa: LOG014
)
6 changes: 3 additions & 3 deletions homeassistant/components/airly/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ async def async_step_user(
)
self._abort_if_unique_id_configured()
try:
location_point_valid = await test_location(
location_point_valid = await check_location(
websession,
user_input["api_key"],
user_input["latitude"],
user_input["longitude"],
)
if not location_point_valid:
location_nearest_valid = await test_location(
location_nearest_valid = await check_location(
websession,
user_input["api_key"],
user_input["latitude"],
Expand Down Expand Up @@ -88,7 +88,7 @@ async def async_step_user(
)


async def test_location(
async def check_location(
client: ClientSession,
api_key: str,
latitude: float,
Expand Down
5 changes: 2 additions & 3 deletions homeassistant/components/automation/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

def _blueprint_in_use(hass: HomeAssistant, blueprint_path: str) -> bool:
"""Return True if any automation references the blueprint."""
from . import automations_with_blueprint # pylint: disable=import-outside-toplevel
from . import automations_with_blueprint # noqa: PLC0415

return len(automations_with_blueprint(hass, blueprint_path)) > 0

Expand All @@ -28,8 +28,7 @@ async def _reload_blueprint_automations(
@callback
def async_get_blueprints(hass: HomeAssistant) -> blueprint.DomainBlueprints:
"""Get automation blueprints."""
# pylint: disable-next=import-outside-toplevel
from .config import AUTOMATION_BLUEPRINT_SCHEMA
from .config import AUTOMATION_BLUEPRINT_SCHEMA # noqa: PLC0415

return blueprint.DomainBlueprints(
hass,
Expand Down
6 changes: 4 additions & 2 deletions homeassistant/components/backup/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
if not with_hassio:
reader_writer = CoreBackupReaderWriter(hass)
else:
# pylint: disable-next=import-outside-toplevel, hass-component-root-import
from homeassistant.components.hassio.backup import SupervisorBackupReaderWriter
# pylint: disable-next=hass-component-root-import
from homeassistant.components.hassio.backup import ( # noqa: PLC0415
SupervisorBackupReaderWriter,
)

reader_writer = SupervisorBackupReaderWriter(hass)

Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/control4/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ class Control4RuntimeData:
type Control4ConfigEntry = ConfigEntry[Control4RuntimeData]


async def call_c4_api_retry(func, *func_args):
async def call_c4_api_retry(func, *func_args): # noqa: RET503
"""Call C4 API function and retry on failure."""
# Ruff doesn't understand this loop - the exception is always raised after the retries
for i in range(API_RETRY_TIMES): # noqa: RET503
for i in range(API_RETRY_TIMES):
try:
return await func(*func_args)
except client_exceptions.ClientError as exception:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/conversation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
)

# Temporary migration. We can remove this in 2024.10
from homeassistant.components.assist_pipeline import ( # pylint: disable=import-outside-toplevel
from homeassistant.components.assist_pipeline import ( # noqa: PLC0415
async_migrate_engine,
)

Expand Down
3 changes: 1 addition & 2 deletions homeassistant/components/downloader/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ def do_download() -> None:
_LOGGER.debug("%s -> %s", url, final_path)

with open(final_path, "wb") as fil:
for chunk in req.iter_content(1024):
fil.write(chunk)
fil.writelines(req.iter_content(1024))

_LOGGER.debug("Downloading of %s done", url)
service.hass.bus.fire(
Expand Down
26 changes: 13 additions & 13 deletions homeassistant/components/eheimdigital/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Generic, TypeVar, override
from typing import Any, override

from eheimdigital.classic_vario import EheimDigitalClassicVario
from eheimdigital.device import EheimDigitalDevice
Expand Down Expand Up @@ -30,16 +30,16 @@

PARALLEL_UPDATES = 0

_DeviceT_co = TypeVar("_DeviceT_co", bound=EheimDigitalDevice, covariant=True)


@dataclass(frozen=True, kw_only=True)
class EheimDigitalNumberDescription(NumberEntityDescription, Generic[_DeviceT_co]):
class EheimDigitalNumberDescription[_DeviceT: EheimDigitalDevice](
NumberEntityDescription
):
"""Class describing EHEIM Digital sensor entities."""

value_fn: Callable[[_DeviceT_co], float | None]
set_value_fn: Callable[[_DeviceT_co, float], Awaitable[None]]
uom_fn: Callable[[_DeviceT_co], str] | None = None
value_fn: Callable[[_DeviceT], float | None]
set_value_fn: Callable[[_DeviceT, float], Awaitable[None]]
uom_fn: Callable[[_DeviceT], str] | None = None


CLASSICVARIO_DESCRIPTIONS: tuple[
Expand Down Expand Up @@ -136,7 +136,7 @@ def async_setup_device_entities(
device_address: dict[str, EheimDigitalDevice],
) -> None:
"""Set up the number entities for one or multiple devices."""
entities: list[EheimDigitalNumber[EheimDigitalDevice]] = []
entities: list[EheimDigitalNumber[Any]] = []
for device in device_address.values():
if isinstance(device, EheimDigitalClassicVario):
entities.extend(
Expand All @@ -163,18 +163,18 @@ def async_setup_device_entities(
async_setup_device_entities(coordinator.hub.devices)


class EheimDigitalNumber(
EheimDigitalEntity[_DeviceT_co], NumberEntity, Generic[_DeviceT_co]
class EheimDigitalNumber[_DeviceT: EheimDigitalDevice](
EheimDigitalEntity[_DeviceT], NumberEntity
):
"""Represent a EHEIM Digital number entity."""

entity_description: EheimDigitalNumberDescription[_DeviceT_co]
entity_description: EheimDigitalNumberDescription[_DeviceT]

def __init__(
self,
coordinator: EheimDigitalUpdateCoordinator,
device: _DeviceT_co,
description: EheimDigitalNumberDescription[_DeviceT_co],
device: _DeviceT,
description: EheimDigitalNumberDescription[_DeviceT],
) -> None:
"""Initialize an EHEIM Digital number entity."""
super().__init__(coordinator, device)
Expand Down
24 changes: 12 additions & 12 deletions homeassistant/components/eheimdigital/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Generic, TypeVar, override
from typing import Any, override

from eheimdigital.classic_vario import EheimDigitalClassicVario
from eheimdigital.device import EheimDigitalDevice
Expand All @@ -17,15 +17,15 @@

PARALLEL_UPDATES = 0

_DeviceT_co = TypeVar("_DeviceT_co", bound=EheimDigitalDevice, covariant=True)


@dataclass(frozen=True, kw_only=True)
class EheimDigitalSelectDescription(SelectEntityDescription, Generic[_DeviceT_co]):
class EheimDigitalSelectDescription[_DeviceT: EheimDigitalDevice](
SelectEntityDescription
):
"""Class describing EHEIM Digital select entities."""

value_fn: Callable[[_DeviceT_co], str | None]
set_value_fn: Callable[[_DeviceT_co, str], Awaitable[None]]
value_fn: Callable[[_DeviceT], str | None]
set_value_fn: Callable[[_DeviceT, str], Awaitable[None]]


CLASSICVARIO_DESCRIPTIONS: tuple[
Expand Down Expand Up @@ -59,7 +59,7 @@ def async_setup_device_entities(
device_address: dict[str, EheimDigitalDevice],
) -> None:
"""Set up the number entities for one or multiple devices."""
entities: list[EheimDigitalSelect[EheimDigitalDevice]] = []
entities: list[EheimDigitalSelect[Any]] = []
for device in device_address.values():
if isinstance(device, EheimDigitalClassicVario):
entities.extend(
Expand All @@ -75,18 +75,18 @@ def async_setup_device_entities(
async_setup_device_entities(coordinator.hub.devices)


class EheimDigitalSelect(
EheimDigitalEntity[_DeviceT_co], SelectEntity, Generic[_DeviceT_co]
class EheimDigitalSelect[_DeviceT: EheimDigitalDevice](
EheimDigitalEntity[_DeviceT], SelectEntity
):
"""Represent an EHEIM Digital select entity."""

entity_description: EheimDigitalSelectDescription[_DeviceT_co]
entity_description: EheimDigitalSelectDescription[_DeviceT]

def __init__(
self,
coordinator: EheimDigitalUpdateCoordinator,
device: _DeviceT_co,
description: EheimDigitalSelectDescription[_DeviceT_co],
device: _DeviceT,
description: EheimDigitalSelectDescription[_DeviceT],
) -> None:
"""Initialize an EHEIM Digital select entity."""
super().__init__(coordinator, device)
Expand Down
Loading
Loading