From 016abda12e10950c8fdfe536a1dd90d8ff84ce4b Mon Sep 17 00:00:00 2001 From: Ruslan Sayfutdinov Date: Sat, 22 May 2021 09:15:30 +0100 Subject: [PATCH] Pylint plugin to check that relative imports are used (#50937) * Pylint plugin to check that relative imports are used * Fix existing sites * Update description message * Fix typo --- homeassistant/auth/__init__.py | 3 +- homeassistant/components/adguard/__init__.py | 23 ++++---- .../components/azure_devops/__init__.py | 9 +--- homeassistant/components/blink/__init__.py | 13 ++--- .../components/color_extractor/__init__.py | 8 +-- homeassistant/components/somfy/__init__.py | 3 +- homeassistant/components/spotify/__init__.py | 2 +- .../components/twentemilieu/__init__.py | 15 +++--- pylint/plugins/hass_constructor.py | 4 +- pylint/plugins/hass_imports.py | 52 +++++++++++++++++++ pyproject.toml | 1 + 11 files changed, 89 insertions(+), 44 deletions(-) create mode 100644 pylint/plugins/hass_imports.py diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index 931c2f4c11a..519582ea48c 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -9,13 +9,12 @@ from typing import Any, Dict, Mapping, Optional, Tuple, cast import jwt from homeassistant import data_entry_flow -from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult from homeassistant.util import dt as dt_util from . import auth_store, models -from .const import GROUP_ID_ADMIN +from .const import ACCESS_TOKEN_EXPIRATION, GROUP_ID_ADMIN from .mfa_modules import MultiFactorAuthModule, auth_mfa_module_from_config from .providers import AuthProvider, LoginFlow, auth_provider_from_config diff --git a/homeassistant/components/adguard/__init__.py b/homeassistant/components/adguard/__init__.py index 0a4a79b65f5..8f0c73a8a64 100644 --- a/homeassistant/components/adguard/__init__.py +++ b/homeassistant/components/adguard/__init__.py @@ -6,17 +6,6 @@ import logging from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError import voluptuous as vol -from homeassistant.components.adguard.const import ( - CONF_FORCE, - DATA_ADGUARD_CLIENT, - DATA_ADGUARD_VERSION, - DOMAIN, - SERVICE_ADD_URL, - SERVICE_DISABLE_URL, - SERVICE_ENABLE_URL, - SERVICE_REFRESH, - SERVICE_REMOVE_URL, -) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_HOST, @@ -34,6 +23,18 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity import DeviceInfo, Entity +from .const import ( + CONF_FORCE, + DATA_ADGUARD_CLIENT, + DATA_ADGUARD_VERSION, + DOMAIN, + SERVICE_ADD_URL, + SERVICE_DISABLE_URL, + SERVICE_ENABLE_URL, + SERVICE_REFRESH, + SERVICE_REMOVE_URL, +) + _LOGGER = logging.getLogger(__name__) SERVICE_URL_SCHEMA = vol.Schema({vol.Required(CONF_URL): cv.url}) diff --git a/homeassistant/components/azure_devops/__init__.py b/homeassistant/components/azure_devops/__init__.py index ba9020e3e88..5e3971adcc4 100644 --- a/homeassistant/components/azure_devops/__init__.py +++ b/homeassistant/components/azure_devops/__init__.py @@ -6,18 +6,13 @@ import logging from aioazuredevops.client import DevOpsClient import aiohttp -from homeassistant.components.azure_devops.const import ( - CONF_ORG, - CONF_PAT, - CONF_PROJECT, - DATA_AZURE_DEVOPS_CLIENT, - DOMAIN, -) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers.entity import DeviceInfo, Entity +from .const import CONF_ORG, CONF_PAT, CONF_PROJECT, DATA_AZURE_DEVOPS_CLIENT, DOMAIN + _LOGGER = logging.getLogger(__name__) PLATFORMS = ["sensor"] diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py index ce47fcf7908..5845c61c330 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -7,7 +7,13 @@ from blinkpy.blinkpy import Blink import voluptuous as vol from homeassistant.components import persistent_notification -from homeassistant.components.blink.const import ( +from homeassistant.config_entries import SOURCE_REAUTH +from homeassistant.const import CONF_FILENAME, CONF_NAME, CONF_PIN, CONF_SCAN_INTERVAL +from homeassistant.core import callback +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import config_validation as cv + +from .const import ( DEFAULT_SCAN_INTERVAL, DOMAIN, PLATFORMS, @@ -15,11 +21,6 @@ from homeassistant.components.blink.const import ( SERVICE_SAVE_VIDEO, SERVICE_SEND_PIN, ) -from homeassistant.config_entries import SOURCE_REAUTH -from homeassistant.const import CONF_FILENAME, CONF_NAME, CONF_PIN, CONF_SCAN_INTERVAL -from homeassistant.core import callback -from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/color_extractor/__init__.py b/homeassistant/components/color_extractor/__init__.py index ddd2ae967e4..b0ab5c2aba7 100644 --- a/homeassistant/components/color_extractor/__init__.py +++ b/homeassistant/components/color_extractor/__init__.py @@ -9,12 +9,6 @@ import async_timeout from colorthief import ColorThief import voluptuous as vol -from homeassistant.components.color_extractor.const import ( - ATTR_PATH, - ATTR_URL, - DOMAIN, - SERVICE_TURN_ON, -) from homeassistant.components.light import ( ATTR_RGB_COLOR, DOMAIN as LIGHT_DOMAIN, @@ -24,6 +18,8 @@ from homeassistant.components.light import ( from homeassistant.helpers import aiohttp_client import homeassistant.helpers.config_validation as cv +from .const import ATTR_PATH, ATTR_URL, DOMAIN, SERVICE_TURN_ON + _LOGGER = logging.getLogger(__name__) # Extend the existing light.turn_on service schema diff --git a/homeassistant/components/somfy/__init__.py b/homeassistant/components/somfy/__init__.py index e5c3015d2fa..f159b19c92a 100644 --- a/homeassistant/components/somfy/__init__.py +++ b/homeassistant/components/somfy/__init__.py @@ -6,7 +6,6 @@ import logging from pymfy.api.devices.category import Category import voluptuous as vol -from homeassistant.components.somfy import config_flow from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_OPTIMISTIC from homeassistant.core import HomeAssistant, callback @@ -21,7 +20,7 @@ from homeassistant.helpers.update_coordinator import ( DataUpdateCoordinator, ) -from . import api +from . import api, config_flow from .const import API, COORDINATOR, DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/spotify/__init__.py b/homeassistant/components/spotify/__init__.py index 3aab37c9392..8af58edd4b4 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -5,7 +5,6 @@ from spotipy import Spotify, SpotifyException import voluptuous as vol from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN -from homeassistant.components.spotify import config_flow from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_CREDENTIALS, CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.core import HomeAssistant @@ -17,6 +16,7 @@ from homeassistant.helpers.config_entry_oauth2_flow import ( ) from homeassistant.helpers.typing import ConfigType +from . import config_flow from .const import ( DATA_SPOTIFY_CLIENT, DATA_SPOTIFY_ME, diff --git a/homeassistant/components/twentemilieu/__init__.py b/homeassistant/components/twentemilieu/__init__.py index 94495cb83ce..81e0a040333 100644 --- a/homeassistant/components/twentemilieu/__init__.py +++ b/homeassistant/components/twentemilieu/__init__.py @@ -7,13 +7,6 @@ from datetime import timedelta from twentemilieu import TwenteMilieu import voluptuous as vol -from homeassistant.components.twentemilieu.const import ( - CONF_HOUSE_LETTER, - CONF_HOUSE_NUMBER, - CONF_POST_CODE, - DATA_UPDATE, - DOMAIN, -) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ID from homeassistant.core import HomeAssistant @@ -23,6 +16,14 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType +from .const import ( + CONF_HOUSE_LETTER, + CONF_HOUSE_NUMBER, + CONF_POST_CODE, + DATA_UPDATE, + DOMAIN, +) + SCAN_INTERVAL = timedelta(seconds=3600) SERVICE_UPDATE = "update" diff --git a/pylint/plugins/hass_constructor.py b/pylint/plugins/hass_constructor.py index 3a3012b9f69..f0f23ef4c95 100644 --- a/pylint/plugins/hass_constructor.py +++ b/pylint/plugins/hass_constructor.py @@ -1,5 +1,5 @@ """Plugin for constructor definitions.""" -from astroid import ClassDef, Const, FunctionDef +from astroid import Const, FunctionDef from pylint.checkers import BaseChecker from pylint.interfaces import IAstroidChecker from pylint.lint import PyLinter @@ -43,7 +43,7 @@ class HassConstructorFormatChecker(BaseChecker): # type: ignore[misc] return # Check that return type is specified and it is "None". - if not isinstance(node.returns, Const) or node.returns.value != None: + if not isinstance(node.returns, Const) or node.returns.value is not None: self.add_message("hass-constructor-return", node=node) diff --git a/pylint/plugins/hass_imports.py b/pylint/plugins/hass_imports.py new file mode 100644 index 00000000000..341abff202a --- /dev/null +++ b/pylint/plugins/hass_imports.py @@ -0,0 +1,52 @@ +"""Plugin for checking imports.""" +from __future__ import annotations + +from astroid import Import, ImportFrom, Module +from pylint.checkers import BaseChecker +from pylint.interfaces import IAstroidChecker +from pylint.lint import PyLinter + + +class HassImportsFormatChecker(BaseChecker): # type: ignore[misc] + """Checker for imports.""" + + __implements__ = IAstroidChecker + + name = "hass_imports" + priority = -1 + msgs = { + "W0011": ( + "Relative import should be used", + "hass-relative-import", + "Used when absolute import should be replaced with relative import", + ), + } + options = () + + def __init__(self, linter: PyLinter | None = None) -> None: + super().__init__(linter) + self.current_module: str | None = None + + def visit_module(self, node: Module) -> None: + """Called when a Import node is visited.""" + self.current_module = node.name + + def visit_import(self, node: Import) -> None: + """Called when a Import node is visited.""" + for module, _alias in node.names: + if module.startswith(f"{self.current_module}."): + self.add_message("hass-relative-import", node=node) + + def visit_importfrom(self, node: ImportFrom) -> None: + """Called when a ImportFrom node is visited.""" + if node.level is not None: + return + if node.modname == self.current_module or node.modname.startswith( + f"{self.current_module}." + ): + self.add_message("hass-relative-import", node=node) + + +def register(linter: PyLinter) -> None: + """Register the checker.""" + linter.register_checker(HassImportsFormatChecker(linter)) diff --git a/pyproject.toml b/pyproject.toml index 33af823fae4..f8d47624c8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ load-plugins = [ "pylint.extensions.typing", "pylint_strict_informational", "hass_constructor", + "hass_imports", "hass_logger", ] persistent = false