core/script/hassfest/mypy_config.py

287 lines
11 KiB
Python
Raw Normal View History

"""Generate mypy config."""
from __future__ import annotations
import configparser
import io
import os
from pathlib import Path
from typing import Final
from .model import Config, Integration
# Modules which have type hints which known to be broken.
# If you are an author of component listed here, please fix these errors and
# remove your component from this list to enable type checks.
# Do your best to not add anything new here.
IGNORED_MODULES: Final[list[str]] = [
"homeassistant.components.blueprint.*",
"homeassistant.components.climacell.*",
"homeassistant.components.cloud.*",
"homeassistant.components.config.*",
"homeassistant.components.conversation.*",
"homeassistant.components.deconz.*",
"homeassistant.components.demo.*",
"homeassistant.components.denonavr.*",
"homeassistant.components.dhcp.*",
"homeassistant.components.doorbird.*",
"homeassistant.components.enphase_envoy.*",
"homeassistant.components.evohome.*",
"homeassistant.components.fireservicerota.*",
"homeassistant.components.firmata.*",
"homeassistant.components.flo.*",
"homeassistant.components.fortios.*",
"homeassistant.components.foscam.*",
"homeassistant.components.freebox.*",
"homeassistant.components.geniushub.*",
"homeassistant.components.glances.*",
"homeassistant.components.google_assistant.*",
"homeassistant.components.gree.*",
"homeassistant.components.growatt_server.*",
"homeassistant.components.habitica.*",
"homeassistant.components.harmony.*",
"homeassistant.components.hassio.*",
"homeassistant.components.here_travel_time.*",
"homeassistant.components.hisense_aehw4a1.*",
"homeassistant.components.home_connect.*",
"homeassistant.components.home_plus_control.*",
"homeassistant.components.homekit.*",
"homeassistant.components.homekit_controller.*",
"homeassistant.components.honeywell.*",
"homeassistant.components.humidifier.*",
"homeassistant.components.iaqualink.*",
"homeassistant.components.icloud.*",
"homeassistant.components.image.*",
"homeassistant.components.incomfort.*",
"homeassistant.components.influxdb.*",
"homeassistant.components.input_datetime.*",
"homeassistant.components.input_number.*",
"homeassistant.components.ipp.*",
"homeassistant.components.isy994.*",
"homeassistant.components.izone.*",
"homeassistant.components.kaiterra.*",
"homeassistant.components.keenetic_ndms2.*",
"homeassistant.components.kodi.*",
"homeassistant.components.konnected.*",
"homeassistant.components.kostal_plenticore.*",
"homeassistant.components.kulersky.*",
"homeassistant.components.litejet.*",
"homeassistant.components.litterrobot.*",
"homeassistant.components.lovelace.*",
"homeassistant.components.lutron_caseta.*",
"homeassistant.components.lyric.*",
"homeassistant.components.melcloud.*",
"homeassistant.components.meteo_france.*",
"homeassistant.components.metoffice.*",
"homeassistant.components.minecraft_server.*",
"homeassistant.components.mobile_app.*",
"homeassistant.components.motion_blinds.*",
"homeassistant.components.mullvad.*",
"homeassistant.components.ness_alarm.*",
"homeassistant.components.nest.legacy.*",
Netgear config flow (#54479) * Original work from Quentame * Small adjustments * Add properties and method_version * fix unknown name * add consider_home functionality * fix typo * fix key * swao setup order * use formatted mac * add tracked_list option * add options flow * add config flow * add config flow * clean up registries * only remove if no other integration has that device * tracked_list formatting * convert tracked list * add import * move imports * use new tracked list on update * use update_device instead of remove * add strings * initialize already known devices * Update router.py * Update router.py * Update router.py * small fixes * styling * fix typing * fix spelling * Update router.py * get model of router * add router device info * fix api * add listeners * update router device info * remove method version option * Update __init__.py * fix styling * ignore typing * remove typing * fix mypy config * Update mypy.ini * add options flow tests * Update .coveragerc * fix styling * Update homeassistant/components/netgear/__init__.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netgear/__init__.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netgear/__init__.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netgear/config_flow.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netgear/router.py Co-authored-by: J. Nick Koston <nick@koston.org> * add ConfigEntryNotReady * Update router.py * use entry.async_on_unload * Update homeassistant/components/netgear/device_tracker.py Co-authored-by: J. Nick Koston <nick@koston.org> * use cv.ensure_list_csv * add hostname property * Update device_tracker.py * fix typo * fix isort * add myself to codeowners * clean config flow * further clean config flow * deprecate old netgear discovery * split out _async_remove_untracked_registries * Update homeassistant/components/netgear/config_flow.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netgear/config_flow.py Co-authored-by: J. Nick Koston <nick@koston.org> * cleanup * fix rename * fix typo * remove URL option * fixes * add sensor platform * fixes * fix removing multiple entities * remove extra attributes * initialize sensors correctly * extra sensors disabled by default * fix styling and unused imports * fix tests * Update .coveragerc * fix requirements * remove tracked list * remove tracked registry editing * fix styling * fix discovery test * simplify unload * Update homeassistant/components/netgear/router.py Co-authored-by: J. Nick Koston <nick@koston.org> * add typing Co-authored-by: J. Nick Koston <nick@koston.org> * add typing Co-authored-by: J. Nick Koston <nick@koston.org> * add typing Co-authored-by: J. Nick Koston <nick@koston.org> * condense NetgearSensorEntities Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netgear/router.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netgear/router.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netgear/router.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netgear/router.py Co-authored-by: J. Nick Koston <nick@koston.org> * add typing * styling * add typing * use ForwardRefrence for typing * Update homeassistant/components/netgear/device_tracker.py Co-authored-by: J. Nick Koston <nick@koston.org> * add typing * Apply suggestions from code review Thanks! Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * process review comments * fix styling * fix devicename not available on all models * ensure DeviceName is not needed * Update homeassistant/components/netgear/config_flow.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/netgear/config_flow.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update __init__.py * fix styling Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2021-09-13 16:18:21 +00:00
"homeassistant.components.netgear.*",
"homeassistant.components.nightscout.*",
"homeassistant.components.nilu.*",
"homeassistant.components.nsw_fuel_station.*",
"homeassistant.components.nuki.*",
"homeassistant.components.nws.*",
"homeassistant.components.nzbget.*",
"homeassistant.components.omnilogic.*",
"homeassistant.components.onboarding.*",
"homeassistant.components.ondilo_ico.*",
"homeassistant.components.onvif.*",
"homeassistant.components.ovo_energy.*",
"homeassistant.components.ozw.*",
"homeassistant.components.philips_js.*",
"homeassistant.components.ping.*",
"homeassistant.components.pioneer.*",
"homeassistant.components.plaato.*",
"homeassistant.components.plex.*",
"homeassistant.components.plugwise.*",
"homeassistant.components.plum_lightpad.*",
"homeassistant.components.point.*",
"homeassistant.components.profiler.*",
"homeassistant.components.rachio.*",
"homeassistant.components.ring.*",
"homeassistant.components.ruckus_unleashed.*",
"homeassistant.components.screenlogic.*",
"homeassistant.components.search.*",
"homeassistant.components.sense.*",
"homeassistant.components.sharkiq.*",
"homeassistant.components.sma.*",
"homeassistant.components.smartthings.*",
"homeassistant.components.solaredge.*",
"homeassistant.components.somfy.*",
"homeassistant.components.somfy_mylink.*",
"homeassistant.components.sonos.*",
"homeassistant.components.spotify.*",
"homeassistant.components.stt.*",
"homeassistant.components.system_health.*",
"homeassistant.components.system_log.*",
"homeassistant.components.tado.*",
"homeassistant.components.telegram_bot.*",
"homeassistant.components.template.*",
"homeassistant.components.toon.*",
"homeassistant.components.unifi.*",
"homeassistant.components.upnp.*",
"homeassistant.components.vera.*",
"homeassistant.components.verisure.*",
"homeassistant.components.vizio.*",
"homeassistant.components.withings.*",
"homeassistant.components.xbox.*",
"homeassistant.components.xiaomi_aqara.*",
"homeassistant.components.xiaomi_miio.*",
"homeassistant.components.yeelight.*",
"homeassistant.components.zha.*",
"homeassistant.components.zwave.*",
]
HEADER: Final = """
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest
""".lstrip()
GENERAL_SETTINGS: Final[dict[str, str]] = {
"python_version": "3.8",
"show_error_codes": "true",
"follow_imports": "silent",
# Enable some checks globally.
"ignore_missing_imports": "true",
"strict_equality": "true",
"warn_incomplete_stub": "true",
"warn_redundant_casts": "true",
"warn_unused_configs": "true",
"warn_unused_ignores": "true",
}
# This is basically the list of checks which is enabled for "strict=true".
# "strict=false" in config files does not turn strict settings off if they've been
# set in a more general section (it instead means as if strict was not specified at
# all), so we need to list all checks manually to be able to flip them wholesale.
STRICT_SETTINGS: Final[list[str]] = [
"check_untyped_defs",
"disallow_incomplete_defs",
"disallow_subclassing_any",
"disallow_untyped_calls",
"disallow_untyped_decorators",
"disallow_untyped_defs",
"no_implicit_optional",
"warn_return_any",
"warn_unreachable",
# TODO: turn these on, address issues
# "disallow_any_generics",
# "no_implicit_reexport",
]
def generate_and_validate(config: Config) -> str:
"""Validate and generate mypy config."""
config_path = config.root / ".strict-typing"
with config_path.open() as fp:
lines = fp.readlines()
# Filter empty and commented lines.
strict_modules: list[str] = [
line.strip()
for line in lines
if line.strip() != "" and not line.startswith("#")
]
ignored_modules_set: set[str] = set(IGNORED_MODULES)
for module in strict_modules:
if (
not module.startswith("homeassistant.components.")
and module != "homeassistant.components"
):
config.add_error(
"mypy_config", f"Only components should be added: {module}"
)
if module in ignored_modules_set:
config.add_error(
"mypy_config", f"Module '{module}' is in ignored list in mypy_config.py"
)
# Validate that all modules exist.
all_modules = strict_modules + IGNORED_MODULES
for module in all_modules:
if module.endswith(".*"):
module_path = Path(module[:-2].replace(".", os.path.sep))
if not module_path.is_dir():
config.add_error("mypy_config", f"Module '{module} is not a folder")
else:
module = module.replace(".", os.path.sep)
module_path = Path(f"{module}.py")
if module_path.is_file():
continue
module_path = Path(module) / "__init__.py"
if not module_path.is_file():
config.add_error("mypy_config", f"Module '{module} doesn't exist")
# Don't generate mypy.ini if there're errors found because it will likely crash.
if any(err.plugin == "mypy_config" for err in config.errors):
return ""
mypy_config = configparser.ConfigParser()
general_section = "mypy"
mypy_config.add_section(general_section)
for key, value in GENERAL_SETTINGS.items():
mypy_config.set(general_section, key, value)
for key in STRICT_SETTINGS:
mypy_config.set(general_section, key, "true")
# By default strict checks are disabled for components.
components_section = "mypy-homeassistant.components.*"
mypy_config.add_section(components_section)
for key in STRICT_SETTINGS:
mypy_config.set(components_section, key, "false")
for strict_module in strict_modules:
strict_section = f"mypy-{strict_module}"
mypy_config.add_section(strict_section)
for key in STRICT_SETTINGS:
mypy_config.set(strict_section, key, "true")
# Disable strict checks for tests
tests_section = "mypy-tests.*"
mypy_config.add_section(tests_section)
for key in STRICT_SETTINGS:
mypy_config.set(tests_section, key, "false")
for ignored_module in IGNORED_MODULES:
ignored_section = f"mypy-{ignored_module}"
mypy_config.add_section(ignored_section)
mypy_config.set(ignored_section, "ignore_errors", "true")
with io.StringIO() as fp:
mypy_config.write(fp)
fp.seek(0)
return HEADER + fp.read().strip()
def validate(integrations: dict[str, Integration], config: Config) -> None:
"""Validate mypy config."""
config_path = config.root / "mypy.ini"
config.cache["mypy_config"] = content = generate_and_validate(config)
if any(err.plugin == "mypy_config" for err in config.errors):
return
with open(str(config_path)) as fp:
if fp.read().strip() != content:
config.add_error(
"mypy_config",
"File mypy.ini is not up to date. Run python3 -m script.hassfest",
fixable=True,
)
def generate(integrations: dict[str, Integration], config: Config) -> None:
"""Generate mypy config."""
config_path = config.root / "mypy.ini"
with open(str(config_path), "w") as fp:
fp.write(f"{config.cache['mypy_config']}\n")