mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Refactor type-hint pylint plugin (#72692)
* Cleanup unused variable * Adjust tests * Refactor _METHOD_MATCH dict * Remove duplicate function * Early exit * Undo object hint * METHOD > FUNCTION * Add comment * Remove extend * Remove break * Extract __any_platform__ * Add tests * Cache _PLATFORMS * Adjust tests * Review comments * mypy * shorthand
This commit is contained in:
parent
5dc4c89acc
commit
cf17169b0e
@ -13,18 +13,19 @@ from homeassistant.const import Platform
|
||||
|
||||
UNDEFINED = object()
|
||||
|
||||
_PLATFORMS: set[str] = {platform.value for platform in Platform}
|
||||
|
||||
|
||||
@dataclass
|
||||
class TypeHintMatch:
|
||||
"""Class for pattern matching."""
|
||||
|
||||
module_filter: re.Pattern
|
||||
function_name: str
|
||||
arg_types: dict[int, str]
|
||||
return_type: list[str] | str | None
|
||||
return_type: list[str] | str | None | object
|
||||
|
||||
|
||||
_TYPE_HINT_MATCHERS: dict[str, re.Pattern] = {
|
||||
_TYPE_HINT_MATCHERS: dict[str, re.Pattern[str]] = {
|
||||
# a_or_b matches items such as "DiscoveryInfoType | None"
|
||||
"a_or_b": re.compile(r"^(\w+) \| (\w+)$"),
|
||||
# x_of_y matches items such as "Awaitable[None]"
|
||||
@ -35,373 +36,333 @@ _TYPE_HINT_MATCHERS: dict[str, re.Pattern] = {
|
||||
"x_of_y_of_z_comma_a": re.compile(r"^(\w+)\[(\w+)\[(.*?]*), (.*?]*)\]\]$"),
|
||||
}
|
||||
|
||||
_MODULE_FILTERS: dict[str, re.Pattern] = {
|
||||
# init matches only in the package root (__init__.py)
|
||||
"init": re.compile(r"^homeassistant\.components\.\w+$"),
|
||||
# any_platform matches any platform in the package root ({platform}.py)
|
||||
"any_platform": re.compile(
|
||||
f"^homeassistant\\.components\\.\\w+\\.({'|'.join([platform.value for platform in Platform])})$"
|
||||
),
|
||||
# application_credentials matches only in the package root (application_credentials.py)
|
||||
"application_credentials": re.compile(
|
||||
r"^homeassistant\.components\.\w+\.(application_credentials)$"
|
||||
),
|
||||
# backup matches only in the package root (backup.py)
|
||||
"backup": re.compile(r"^homeassistant\.components\.\w+\.(backup)$"),
|
||||
# cast matches only in the package root (cast.py)
|
||||
"cast": re.compile(r"^homeassistant\.components\.\w+\.(cast)$"),
|
||||
# config_flow matches only in the package root (config_flow.py)
|
||||
"config_flow": re.compile(r"^homeassistant\.components\.\w+\.(config_flow)$"),
|
||||
# device_action matches only in the package root (device_action.py)
|
||||
"device_action": re.compile(r"^homeassistant\.components\.\w+\.(device_action)$"),
|
||||
# device_condition matches only in the package root (device_condition.py)
|
||||
"device_condition": re.compile(
|
||||
r"^homeassistant\.components\.\w+\.(device_condition)$"
|
||||
),
|
||||
# device_tracker matches only in the package root (device_tracker.py)
|
||||
"device_tracker": re.compile(r"^homeassistant\.components\.\w+\.(device_tracker)$"),
|
||||
# device_trigger matches only in the package root (device_trigger.py)
|
||||
"device_trigger": re.compile(r"^homeassistant\.components\.\w+\.(device_trigger)$"),
|
||||
# diagnostics matches only in the package root (diagnostics.py)
|
||||
"diagnostics": re.compile(r"^homeassistant\.components\.\w+\.(diagnostics)$"),
|
||||
_MODULE_REGEX: re.Pattern[str] = re.compile(r"^homeassistant\.components\.\w+(\.\w+)?$")
|
||||
|
||||
_FUNCTION_MATCH: dict[str, list[TypeHintMatch]] = {
|
||||
"__init__": [
|
||||
TypeHintMatch(
|
||||
function_name="setup",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_setup",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_setup_entry",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigEntry",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_remove_entry",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigEntry",
|
||||
},
|
||||
return_type=None,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_unload_entry",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigEntry",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_migrate_entry",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigEntry",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
],
|
||||
"__any_platform__": [
|
||||
TypeHintMatch(
|
||||
function_name="setup_platform",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
2: "AddEntitiesCallback",
|
||||
3: "DiscoveryInfoType | None",
|
||||
},
|
||||
return_type=None,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_setup_platform",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
2: "AddEntitiesCallback",
|
||||
3: "DiscoveryInfoType | None",
|
||||
},
|
||||
return_type=None,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_setup_entry",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigEntry",
|
||||
2: "AddEntitiesCallback",
|
||||
},
|
||||
return_type=None,
|
||||
),
|
||||
],
|
||||
"application_credentials": [
|
||||
TypeHintMatch(
|
||||
function_name="async_get_auth_implementation",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "str",
|
||||
2: "ClientCredential",
|
||||
},
|
||||
return_type="AbstractOAuth2Implementation",
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_get_authorization_server",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
},
|
||||
return_type="AuthorizationServer",
|
||||
),
|
||||
],
|
||||
"backup": [
|
||||
TypeHintMatch(
|
||||
function_name="async_pre_backup",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
},
|
||||
return_type=None,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_post_backup",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
},
|
||||
return_type=None,
|
||||
),
|
||||
],
|
||||
"cast": [
|
||||
TypeHintMatch(
|
||||
function_name="async_get_media_browser_root_object",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "str",
|
||||
},
|
||||
return_type="list[BrowseMedia]",
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_browse_media",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "str",
|
||||
2: "str",
|
||||
3: "str",
|
||||
},
|
||||
return_type=["BrowseMedia", "BrowseMedia | None"],
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_play_media",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "str",
|
||||
2: "Chromecast",
|
||||
3: "str",
|
||||
4: "str",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
],
|
||||
"config_flow": [
|
||||
TypeHintMatch(
|
||||
function_name="_async_has_devices",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
],
|
||||
"device_action": [
|
||||
TypeHintMatch(
|
||||
function_name="async_validate_action_config",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="ConfigType",
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_call_action_from_config",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
2: "TemplateVarsType",
|
||||
3: "Context | None",
|
||||
},
|
||||
return_type=None,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_get_action_capabilities",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="dict[str, Schema]",
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_get_actions",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "str",
|
||||
},
|
||||
return_type=["list[dict[str, str]]", "list[dict[str, Any]]"],
|
||||
),
|
||||
],
|
||||
"device_condition": [
|
||||
TypeHintMatch(
|
||||
function_name="async_validate_condition_config",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="ConfigType",
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_condition_from_config",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="ConditionCheckerType",
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_get_condition_capabilities",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="dict[str, Schema]",
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_get_conditions",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "str",
|
||||
},
|
||||
return_type=["list[dict[str, str]]", "list[dict[str, Any]]"],
|
||||
),
|
||||
],
|
||||
"device_tracker": [
|
||||
TypeHintMatch(
|
||||
function_name="setup_scanner",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
2: "Callable[..., None]",
|
||||
3: "DiscoveryInfoType | None",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_setup_scanner",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
2: "Callable[..., Awaitable[None]]",
|
||||
3: "DiscoveryInfoType | None",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="get_scanner",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type=["DeviceScanner", "DeviceScanner | None"],
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_get_scanner",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type=["DeviceScanner", "DeviceScanner | None"],
|
||||
),
|
||||
],
|
||||
"device_trigger": [
|
||||
TypeHintMatch(
|
||||
function_name="async_validate_condition_config",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="ConfigType",
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_attach_trigger",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
2: "AutomationActionType",
|
||||
3: "AutomationTriggerInfo",
|
||||
},
|
||||
return_type="CALLBACK_TYPE",
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_get_trigger_capabilities",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="dict[str, Schema]",
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_get_triggers",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "str",
|
||||
},
|
||||
return_type=["list[dict[str, str]]", "list[dict[str, Any]]"],
|
||||
),
|
||||
],
|
||||
"diagnostics": [
|
||||
TypeHintMatch(
|
||||
function_name="async_get_config_entry_diagnostics",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigEntry",
|
||||
},
|
||||
return_type=UNDEFINED,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="async_get_device_diagnostics",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigEntry",
|
||||
2: "DeviceEntry",
|
||||
},
|
||||
return_type=UNDEFINED,
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
_METHOD_MATCH: list[TypeHintMatch] = [
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["init"],
|
||||
function_name="setup",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["init"],
|
||||
function_name="async_setup",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["init"],
|
||||
function_name="async_setup_entry",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigEntry",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["init"],
|
||||
function_name="async_remove_entry",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigEntry",
|
||||
},
|
||||
return_type=None,
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["init"],
|
||||
function_name="async_unload_entry",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigEntry",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["init"],
|
||||
function_name="async_migrate_entry",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigEntry",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["any_platform"],
|
||||
function_name="setup_platform",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
2: "AddEntitiesCallback",
|
||||
3: "DiscoveryInfoType | None",
|
||||
},
|
||||
return_type=None,
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["any_platform"],
|
||||
function_name="async_setup_platform",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
2: "AddEntitiesCallback",
|
||||
3: "DiscoveryInfoType | None",
|
||||
},
|
||||
return_type=None,
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["any_platform"],
|
||||
function_name="async_setup_entry",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigEntry",
|
||||
2: "AddEntitiesCallback",
|
||||
},
|
||||
return_type=None,
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["application_credentials"],
|
||||
function_name="async_get_auth_implementation",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "str",
|
||||
2: "ClientCredential",
|
||||
},
|
||||
return_type="AbstractOAuth2Implementation",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["application_credentials"],
|
||||
function_name="async_get_authorization_server",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
},
|
||||
return_type="AuthorizationServer",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["backup"],
|
||||
function_name="async_pre_backup",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
},
|
||||
return_type=None,
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["backup"],
|
||||
function_name="async_post_backup",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
},
|
||||
return_type=None,
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["cast"],
|
||||
function_name="async_get_media_browser_root_object",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "str",
|
||||
},
|
||||
return_type="list[BrowseMedia]",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["cast"],
|
||||
function_name="async_browse_media",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "str",
|
||||
2: "str",
|
||||
3: "str",
|
||||
},
|
||||
return_type=["BrowseMedia", "BrowseMedia | None"],
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["cast"],
|
||||
function_name="async_play_media",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "str",
|
||||
2: "Chromecast",
|
||||
3: "str",
|
||||
4: "str",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["config_flow"],
|
||||
function_name="_async_has_devices",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["device_action"],
|
||||
function_name="async_validate_action_config",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="ConfigType",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["device_action"],
|
||||
function_name="async_call_action_from_config",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
2: "TemplateVarsType",
|
||||
3: "Context | None",
|
||||
},
|
||||
return_type=None,
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["device_action"],
|
||||
function_name="async_get_action_capabilities",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="dict[str, Schema]",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["device_action"],
|
||||
function_name="async_get_actions",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "str",
|
||||
},
|
||||
return_type=["list[dict[str, str]]", "list[dict[str, Any]]"],
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["device_condition"],
|
||||
function_name="async_validate_condition_config",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="ConfigType",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["device_condition"],
|
||||
function_name="async_condition_from_config",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="ConditionCheckerType",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["device_condition"],
|
||||
function_name="async_get_condition_capabilities",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="dict[str, Schema]",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["device_condition"],
|
||||
function_name="async_get_conditions",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "str",
|
||||
},
|
||||
return_type=["list[dict[str, str]]", "list[dict[str, Any]]"],
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["device_tracker"],
|
||||
function_name="setup_scanner",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
2: "Callable[..., None]",
|
||||
3: "DiscoveryInfoType | None",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["device_tracker"],
|
||||
function_name="async_setup_scanner",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
2: "Callable[..., Awaitable[None]]",
|
||||
3: "DiscoveryInfoType | None",
|
||||
},
|
||||
return_type="bool",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["device_tracker"],
|
||||
function_name="get_scanner",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type=["DeviceScanner", "DeviceScanner | None"],
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["device_tracker"],
|
||||
function_name="async_get_scanner",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type=["DeviceScanner", "DeviceScanner | None"],
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["device_trigger"],
|
||||
function_name="async_validate_condition_config",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="ConfigType",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["device_trigger"],
|
||||
function_name="async_attach_trigger",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
2: "AutomationActionType",
|
||||
3: "AutomationTriggerInfo",
|
||||
},
|
||||
return_type="CALLBACK_TYPE",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["device_trigger"],
|
||||
function_name="async_get_trigger_capabilities",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigType",
|
||||
},
|
||||
return_type="dict[str, Schema]",
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["device_trigger"],
|
||||
function_name="async_get_triggers",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "str",
|
||||
},
|
||||
return_type=["list[dict[str, str]]", "list[dict[str, Any]]"],
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["diagnostics"],
|
||||
function_name="async_get_config_entry_diagnostics",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigEntry",
|
||||
},
|
||||
return_type=UNDEFINED,
|
||||
),
|
||||
TypeHintMatch(
|
||||
module_filter=_MODULE_FILTERS["diagnostics"],
|
||||
function_name="async_get_device_diagnostics",
|
||||
arg_types={
|
||||
0: "HomeAssistant",
|
||||
1: "ConfigEntry",
|
||||
2: "DeviceEntry",
|
||||
},
|
||||
return_type=UNDEFINED,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def _is_valid_type(expected_type: list[str] | str | None, node: astroid.NodeNG) -> bool:
|
||||
def _is_valid_type(
|
||||
expected_type: list[str] | str | None | object, node: astroid.NodeNG
|
||||
) -> bool:
|
||||
"""Check the argument node against the expected type."""
|
||||
if expected_type is UNDEFINED:
|
||||
return True
|
||||
@ -416,6 +377,8 @@ def _is_valid_type(expected_type: list[str] | str | None, node: astroid.NodeNG)
|
||||
if expected_type is None or expected_type == "None":
|
||||
return isinstance(node, astroid.Const) and node.value is None
|
||||
|
||||
assert isinstance(expected_type, str)
|
||||
|
||||
# Const occurs when the type is an Ellipsis
|
||||
if expected_type == "...":
|
||||
return isinstance(node, astroid.Const) and node.value == Ellipsis
|
||||
@ -487,6 +450,17 @@ def _has_valid_annotations(
|
||||
return False
|
||||
|
||||
|
||||
def _get_module_platform(module_name: str) -> str | None:
|
||||
"""Called when a Module node is visited."""
|
||||
if not (module_match := _MODULE_REGEX.match(module_name)):
|
||||
# Ensure `homeassistant.components.<component>`
|
||||
# Or `homeassistant.components.<component>.<platform>`
|
||||
return None
|
||||
|
||||
platform = module_match.groups()[0]
|
||||
return platform.lstrip(".") if platform else "__init__"
|
||||
|
||||
|
||||
class HassTypeHintChecker(BaseChecker): # type: ignore[misc]
|
||||
"""Checker for setup type hints."""
|
||||
|
||||
@ -510,38 +484,31 @@ class HassTypeHintChecker(BaseChecker): # type: ignore[misc]
|
||||
|
||||
def __init__(self, linter: PyLinter | None = None) -> None:
|
||||
super().__init__(linter)
|
||||
self.current_package: str | None = None
|
||||
self.module: str | None = None
|
||||
self._function_matchers: list[TypeHintMatch] = []
|
||||
|
||||
def visit_module(self, node: astroid.Module) -> None:
|
||||
"""Called when a Module node is visited."""
|
||||
self.module = node.name
|
||||
if node.package:
|
||||
self.current_package = node.name
|
||||
else:
|
||||
# Strip name of the current module
|
||||
self.current_package = node.name[: node.name.rfind(".")]
|
||||
self._function_matchers = []
|
||||
|
||||
if (module_platform := _get_module_platform(node.name)) is None:
|
||||
return
|
||||
|
||||
if module_platform in _PLATFORMS:
|
||||
self._function_matchers.extend(_FUNCTION_MATCH["__any_platform__"])
|
||||
|
||||
if matches := _FUNCTION_MATCH.get(module_platform):
|
||||
self._function_matchers.extend(matches)
|
||||
|
||||
def visit_functiondef(self, node: astroid.FunctionDef) -> None:
|
||||
"""Called when a FunctionDef node is visited."""
|
||||
for match in _METHOD_MATCH:
|
||||
self._visit_functiondef(node, match)
|
||||
for match in self._function_matchers:
|
||||
if node.name != match.function_name or node.is_method():
|
||||
continue
|
||||
self._check_function(node, match)
|
||||
|
||||
def visit_asyncfunctiondef(self, node: astroid.AsyncFunctionDef) -> None:
|
||||
"""Called when an AsyncFunctionDef node is visited."""
|
||||
for match in _METHOD_MATCH:
|
||||
self._visit_functiondef(node, match)
|
||||
|
||||
def _visit_functiondef(
|
||||
self, node: astroid.FunctionDef, match: TypeHintMatch
|
||||
) -> None:
|
||||
if node.name != match.function_name:
|
||||
return
|
||||
if node.is_method():
|
||||
return
|
||||
if not match.module_filter.match(self.module):
|
||||
return
|
||||
visit_asyncfunctiondef = visit_functiondef
|
||||
|
||||
def _check_function(self, node: astroid.FunctionDef, match: TypeHintMatch) -> None:
|
||||
# Check that at least one argument is annotated.
|
||||
annotations = _get_all_annotations(node)
|
||||
if node.returns is None and not _has_valid_annotations(annotations):
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Tests for pylint hass_enforce_type_hints plugin."""
|
||||
# pylint:disable=protected-access
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from types import ModuleType
|
||||
@ -14,6 +15,30 @@ import pytest
|
||||
from . import assert_adds_messages, assert_no_messages
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("module_name", "expected_platform", "in_platforms"),
|
||||
[
|
||||
("homeassistant", None, False),
|
||||
("homeassistant.components", None, False),
|
||||
("homeassistant.components.pylint_test", "__init__", False),
|
||||
("homeassistant.components.pylint_test.config_flow", "config_flow", False),
|
||||
("homeassistant.components.pylint_test.light", "light", True),
|
||||
("homeassistant.components.pylint_test.light.v1", None, False),
|
||||
],
|
||||
)
|
||||
def test_regex_get_module_platform(
|
||||
hass_enforce_type_hints: ModuleType,
|
||||
module_name: str,
|
||||
expected_platform: str | None,
|
||||
in_platforms: bool,
|
||||
) -> None:
|
||||
"""Test _get_module_platform regex."""
|
||||
platform = hass_enforce_type_hints._get_module_platform(module_name)
|
||||
|
||||
assert platform == expected_platform
|
||||
assert (platform in hass_enforce_type_hints._PLATFORMS) == in_platforms
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("string", "expected_x", "expected_y", "expected_z", "expected_a"),
|
||||
[
|
||||
@ -95,7 +120,11 @@ def test_ignore_not_annotations(
|
||||
hass_enforce_type_hints: ModuleType, type_hint_checker: BaseChecker, code: str
|
||||
) -> None:
|
||||
"""Ensure that _is_valid_type is not run if there are no annotations."""
|
||||
func_node = astroid.extract_node(code)
|
||||
func_node = astroid.extract_node(
|
||||
code,
|
||||
"homeassistant.components.pylint_test",
|
||||
)
|
||||
type_hint_checker.visit_module(func_node.parent)
|
||||
|
||||
with patch.object(
|
||||
hass_enforce_type_hints, "_is_valid_type", return_value=True
|
||||
@ -131,7 +160,11 @@ def test_dont_ignore_partial_annotations(
|
||||
hass_enforce_type_hints: ModuleType, type_hint_checker: BaseChecker, code: str
|
||||
) -> None:
|
||||
"""Ensure that _is_valid_type is run if there is at least one annotation."""
|
||||
func_node = astroid.extract_node(code)
|
||||
func_node = astroid.extract_node(
|
||||
code,
|
||||
"homeassistant.components.pylint_test",
|
||||
)
|
||||
type_hint_checker.visit_module(func_node.parent)
|
||||
|
||||
with patch.object(
|
||||
hass_enforce_type_hints, "_is_valid_type", return_value=True
|
||||
@ -144,7 +177,6 @@ def test_invalid_discovery_info(
|
||||
linter: UnittestLinter, type_hint_checker: BaseChecker
|
||||
) -> None:
|
||||
"""Ensure invalid hints are rejected for discovery_info."""
|
||||
type_hint_checker.module = "homeassistant.components.pylint_test.device_tracker"
|
||||
func_node, discovery_info_node = astroid.extract_node(
|
||||
"""
|
||||
async def async_setup_scanner( #@
|
||||
@ -154,8 +186,10 @@ def test_invalid_discovery_info(
|
||||
discovery_info: dict[str, Any] | None = None, #@
|
||||
) -> bool:
|
||||
pass
|
||||
"""
|
||||
""",
|
||||
"homeassistant.components.pylint_test.device_tracker",
|
||||
)
|
||||
type_hint_checker.visit_module(func_node.parent)
|
||||
|
||||
with assert_adds_messages(
|
||||
linter,
|
||||
@ -176,7 +210,6 @@ def test_valid_discovery_info(
|
||||
linter: UnittestLinter, type_hint_checker: BaseChecker
|
||||
) -> None:
|
||||
"""Ensure valid hints are accepted for discovery_info."""
|
||||
type_hint_checker.module = "homeassistant.components.pylint_test.device_tracker"
|
||||
func_node = astroid.extract_node(
|
||||
"""
|
||||
async def async_setup_scanner( #@
|
||||
@ -186,8 +219,10 @@ def test_valid_discovery_info(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> bool:
|
||||
pass
|
||||
"""
|
||||
""",
|
||||
"homeassistant.components.pylint_test.device_tracker",
|
||||
)
|
||||
type_hint_checker.visit_module(func_node.parent)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
type_hint_checker.visit_asyncfunctiondef(func_node)
|
||||
@ -197,7 +232,6 @@ def test_invalid_list_dict_str_any(
|
||||
linter: UnittestLinter, type_hint_checker: BaseChecker
|
||||
) -> None:
|
||||
"""Ensure invalid hints are rejected for discovery_info."""
|
||||
type_hint_checker.module = "homeassistant.components.pylint_test.device_trigger"
|
||||
func_node = astroid.extract_node(
|
||||
"""
|
||||
async def async_get_triggers( #@
|
||||
@ -205,8 +239,10 @@ def test_invalid_list_dict_str_any(
|
||||
device_id: str
|
||||
) -> list:
|
||||
pass
|
||||
"""
|
||||
""",
|
||||
"homeassistant.components.pylint_test.device_trigger",
|
||||
)
|
||||
type_hint_checker.visit_module(func_node.parent)
|
||||
|
||||
with assert_adds_messages(
|
||||
linter,
|
||||
@ -227,7 +263,6 @@ def test_valid_list_dict_str_any(
|
||||
linter: UnittestLinter, type_hint_checker: BaseChecker
|
||||
) -> None:
|
||||
"""Ensure valid hints are accepted for discovery_info."""
|
||||
type_hint_checker.module = "homeassistant.components.pylint_test.device_trigger"
|
||||
func_node = astroid.extract_node(
|
||||
"""
|
||||
async def async_get_triggers( #@
|
||||
@ -235,8 +270,10 @@ def test_valid_list_dict_str_any(
|
||||
device_id: str
|
||||
) -> list[dict[str, Any]]:
|
||||
pass
|
||||
"""
|
||||
""",
|
||||
"homeassistant.components.pylint_test.device_trigger",
|
||||
)
|
||||
type_hint_checker.visit_module(func_node.parent)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
type_hint_checker.visit_asyncfunctiondef(func_node)
|
||||
|
Loading…
x
Reference in New Issue
Block a user