diff --git a/homeassistant/core.py b/homeassistant/core.py index 36eb211a6ab..50f62702a59 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -571,7 +571,7 @@ class Context: parent_id: Optional[str] = attr.ib(default=None) id: str = attr.ib(factory=uuid_util.random_uuid_hex) - def as_dict(self) -> dict: + def as_dict(self) -> Dict[str, Optional[str]]: """Return a dictionary representation of the context.""" return {"id": self.id, "parent_id": self.parent_id, "user_id": self.user_id} @@ -612,7 +612,7 @@ class Event: # The only event type that shares context are the TIME_CHANGED return hash((self.event_type, self.context.id, self.time_fired)) - def as_dict(self) -> Dict: + def as_dict(self) -> Dict[str, Any]: """Create a dict representation of this Event. Async friendly. @@ -682,7 +682,7 @@ class EventBus: def async_fire( self, event_type: str, - event_data: Optional[Dict] = None, + event_data: Optional[Dict[str, Any]] = None, origin: EventOrigin = EventOrigin.local, context: Optional[Context] = None, time_fired: Optional[datetime.datetime] = None, @@ -844,7 +844,7 @@ class State: self, entity_id: str, state: str, - attributes: Optional[Mapping] = None, + attributes: Optional[Mapping[str, Any]] = None, last_changed: Optional[datetime.datetime] = None, last_updated: Optional[datetime.datetime] = None, context: Optional[Context] = None, @@ -1091,7 +1091,7 @@ class StateMachine: self, entity_id: str, new_state: str, - attributes: Optional[Dict] = None, + attributes: Optional[Mapping[str, Any]] = None, force_update: bool = False, context: Optional[Context] = None, ) -> None: @@ -1140,7 +1140,7 @@ class StateMachine: self, entity_id: str, new_state: str, - attributes: Optional[Dict] = None, + attributes: Optional[Mapping[str, Any]] = None, force_update: bool = False, context: Optional[Context] = None, ) -> None: diff --git a/homeassistant/helpers/entity_values.py b/homeassistant/helpers/entity_values.py index de48219a8d1..7f44e8b2768 100644 --- a/homeassistant/helpers/entity_values.py +++ b/homeassistant/helpers/entity_values.py @@ -6,18 +6,20 @@ from typing import Any, Dict, Optional, Pattern from homeassistant.core import split_entity_id +# mypy: disallow-any-generics + class EntityValues: """Class to store entity id based values.""" def __init__( self, - exact: Optional[Dict] = None, - domain: Optional[Dict] = None, - glob: Optional[Dict] = None, + exact: Optional[Dict[str, Dict[str, str]]] = None, + domain: Optional[Dict[str, Dict[str, str]]] = None, + glob: Optional[Dict[str, Dict[str, str]]] = None, ) -> None: """Initialize an EntityConfigDict.""" - self._cache: Dict[str, Dict] = {} + self._cache: Dict[str, Dict[str, str]] = {} self._exact = exact self._domain = domain @@ -30,7 +32,7 @@ class EntityValues: self._glob = compiled - def get(self, entity_id: str) -> Dict: + def get(self, entity_id: str) -> Dict[str, str]: """Get config for an entity id.""" if entity_id in self._cache: return self._cache[entity_id] diff --git a/homeassistant/loader.py b/homeassistant/loader.py index fc0355c0892..215f552a908 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -20,6 +20,7 @@ from typing import ( List, Optional, Set, + TypedDict, TypeVar, Union, cast, @@ -34,7 +35,11 @@ from homeassistant.generated.zeroconf import HOMEKIT, ZEROCONF if TYPE_CHECKING: from homeassistant.core import HomeAssistant -CALLABLE_T = TypeVar("CALLABLE_T", bound=Callable) # pylint: disable=invalid-name +# mypy: disallow-any-generics + +CALLABLE_T = TypeVar( # pylint: disable=invalid-name + "CALLABLE_T", bound=Callable[..., Any] +) _LOGGER = logging.getLogger(__name__) @@ -54,12 +59,38 @@ _UNDEF = object() # Internal; not helpers.typing.UNDEFINED due to circular depe MAX_LOAD_CONCURRENTLY = 4 -def manifest_from_legacy_module(domain: str, module: ModuleType) -> Dict: +class Manifest(TypedDict, total=False): + """ + Integration manifest. + + Note that none of the attributes are marked Optional here. However, some of them may be optional in manifest.json + in the sense that they can be omitted altogether. But when present, they should not have null values in it. + """ + + name: str + disabled: str + domain: str + dependencies: List[str] + after_dependencies: List[str] + requirements: List[str] + config_flow: bool + documentation: str + issue_tracker: str + quality_scale: str + mqtt: List[str] + ssdp: List[Dict[str, str]] + zeroconf: List[Union[str, Dict[str, str]]] + dhcp: List[Dict[str, str]] + homekit: Dict[str, List[str]] + is_built_in: bool + codeowners: List[str] + + +def manifest_from_legacy_module(domain: str, module: ModuleType) -> Manifest: """Generate a manifest from a legacy module.""" return { "domain": domain, "name": domain, - "documentation": None, "requirements": getattr(module, "REQUIREMENTS", []), "dependencies": getattr(module, "DEPENDENCIES", []), "codeowners": [], @@ -205,10 +236,10 @@ async def async_get_homekit(hass: "HomeAssistant") -> Dict[str, str]: return homekit -async def async_get_ssdp(hass: "HomeAssistant") -> Dict[str, List]: +async def async_get_ssdp(hass: "HomeAssistant") -> Dict[str, List[Dict[str, str]]]: """Return cached list of ssdp mappings.""" - ssdp: Dict[str, List] = SSDP.copy() + ssdp: Dict[str, List[Dict[str, str]]] = SSDP.copy() integrations = await async_get_custom_components(hass) for integration in integrations.values(): @@ -220,10 +251,10 @@ async def async_get_ssdp(hass: "HomeAssistant") -> Dict[str, List]: return ssdp -async def async_get_mqtt(hass: "HomeAssistant") -> Dict[str, List]: +async def async_get_mqtt(hass: "HomeAssistant") -> Dict[str, List[str]]: """Return cached list of MQTT mappings.""" - mqtt: Dict[str, List] = MQTT.copy() + mqtt: Dict[str, List[str]] = MQTT.copy() integrations = await async_get_custom_components(hass) for integration in integrations.values(): @@ -288,7 +319,7 @@ class Integration: hass: "HomeAssistant", pkg_path: str, file_path: pathlib.Path, - manifest: Dict[str, Any], + manifest: Manifest, ): """Initialize an integration.""" self.hass = hass @@ -309,77 +340,77 @@ class Integration: @property def name(self) -> str: """Return name.""" - return cast(str, self.manifest["name"]) + return self.manifest["name"] @property def disabled(self) -> Optional[str]: """Return reason integration is disabled.""" - return cast(Optional[str], self.manifest.get("disabled")) + return self.manifest.get("disabled") @property def domain(self) -> str: """Return domain.""" - return cast(str, self.manifest["domain"]) + return self.manifest["domain"] @property def dependencies(self) -> List[str]: """Return dependencies.""" - return cast(List[str], self.manifest.get("dependencies", [])) + return self.manifest.get("dependencies", []) @property def after_dependencies(self) -> List[str]: """Return after_dependencies.""" - return cast(List[str], self.manifest.get("after_dependencies", [])) + return self.manifest.get("after_dependencies", []) @property def requirements(self) -> List[str]: """Return requirements.""" - return cast(List[str], self.manifest.get("requirements", [])) + return self.manifest.get("requirements", []) @property def config_flow(self) -> bool: """Return config_flow.""" - return cast(bool, self.manifest.get("config_flow", False)) + return self.manifest.get("config_flow") or False @property def documentation(self) -> Optional[str]: """Return documentation.""" - return cast(str, self.manifest.get("documentation")) + return self.manifest.get("documentation") @property def issue_tracker(self) -> Optional[str]: """Return issue tracker link.""" - return cast(str, self.manifest.get("issue_tracker")) + return self.manifest.get("issue_tracker") @property def quality_scale(self) -> Optional[str]: """Return Integration Quality Scale.""" - return cast(str, self.manifest.get("quality_scale")) + return self.manifest.get("quality_scale") @property - def mqtt(self) -> Optional[list]: + def mqtt(self) -> Optional[List[str]]: """Return Integration MQTT entries.""" - return cast(List[dict], self.manifest.get("mqtt")) + return self.manifest.get("mqtt") @property - def ssdp(self) -> Optional[list]: + def ssdp(self) -> Optional[List[Dict[str, str]]]: """Return Integration SSDP entries.""" - return cast(List[dict], self.manifest.get("ssdp")) + return self.manifest.get("ssdp") @property - def zeroconf(self) -> Optional[list]: + def zeroconf(self) -> Optional[List[Union[str, Dict[str, str]]]]: """Return Integration zeroconf entries.""" - return cast(List[str], self.manifest.get("zeroconf")) + return self.manifest.get("zeroconf") @property - def dhcp(self) -> Optional[list]: + def dhcp(self) -> Optional[List[Dict[str, str]]]: """Return Integration dhcp entries.""" - return cast(List[str], self.manifest.get("dhcp")) + return self.manifest.get("dhcp") @property - def homekit(self) -> Optional[dict]: + def homekit(self) -> Optional[Dict[str, List[str]]]: """Return Integration homekit entries.""" - return cast(Dict[str, List], self.manifest.get("homekit")) + return self.manifest.get("homekit") @property def is_built_in(self) -> bool: diff --git a/homeassistant/requirements.py b/homeassistant/requirements.py index f26c8fa8dfc..6ea2074ddf4 100644 --- a/homeassistant/requirements.py +++ b/homeassistant/requirements.py @@ -9,6 +9,8 @@ from homeassistant.helpers.typing import UNDEFINED, UndefinedType from homeassistant.loader import Integration, IntegrationNotFound, async_get_integration import homeassistant.util.package as pkg_util +# mypy: disallow-any-generics + DATA_PIP_LOCK = "pip_lock" DATA_PKG_CACHE = "pkg_cache" DATA_INTEGRATIONS_WITH_REQS = "integrations_with_reqs" @@ -24,7 +26,7 @@ DISCOVERY_INTEGRATIONS: Dict[str, Iterable[str]] = { class RequirementsNotFound(HomeAssistantError): """Raised when a component is not found.""" - def __init__(self, domain: str, requirements: List) -> None: + def __init__(self, domain: str, requirements: List[str]) -> None: """Initialize a component not found error.""" super().__init__(f"Requirements for {domain} not found: {requirements}.") self.domain = domain @@ -124,7 +126,7 @@ async def async_process_requirements( if pkg_util.is_installed(req): continue - def _install(req: str, kwargs: Dict) -> bool: + def _install(req: str, kwargs: Dict[str, Any]) -> bool: """Install requirement.""" return pkg_util.install_package(req, **kwargs) diff --git a/homeassistant/runner.py b/homeassistant/runner.py index 54d20a7deff..6f13a47be81 100644 --- a/homeassistant/runner.py +++ b/homeassistant/runner.py @@ -9,6 +9,8 @@ from homeassistant import bootstrap from homeassistant.core import callback from homeassistant.helpers.frame import warn_use +# mypy: disallow-any-generics + # # Python 3.8 has significantly less workers by default # than Python 3.7. In order to be consistent between @@ -81,7 +83,7 @@ class HassEventLoopPolicy(asyncio.DefaultEventLoopPolicy): # type: ignore[valid @callback -def _async_loop_exception_handler(_: Any, context: Dict) -> None: +def _async_loop_exception_handler(_: Any, context: Dict[str, Any]) -> None: """Handle all exception inside the core loop.""" kwargs = {} exception = context.get("exception") diff --git a/homeassistant/util/process.py b/homeassistant/util/process.py index fb2d6dec58e..6f8bafda7a7 100644 --- a/homeassistant/util/process.py +++ b/homeassistant/util/process.py @@ -1,9 +1,17 @@ """Util to handle processes.""" +from __future__ import annotations + import subprocess +from typing import Any + +# mypy: disallow-any-generics -def kill_subprocess(process: subprocess.Popen) -> None: +def kill_subprocess( + # pylint: disable=unsubscriptable-object # https://github.com/PyCQA/pylint/issues/4034 + process: subprocess.Popen[Any], +) -> None: """Force kill a subprocess and wait for it to exit.""" process.kill() process.communicate() diff --git a/homeassistant/util/unit_system.py b/homeassistant/util/unit_system.py index 9e0d0c2c979..5cba1bfeb19 100644 --- a/homeassistant/util/unit_system.py +++ b/homeassistant/util/unit_system.py @@ -1,6 +1,6 @@ """Unit system helper class and methods.""" from numbers import Number -from typing import Optional +from typing import Dict, Optional from homeassistant.const import ( CONF_UNIT_SYSTEM_IMPERIAL, @@ -31,6 +31,8 @@ from homeassistant.util import ( volume as volume_util, ) +# mypy: disallow-any-generics + LENGTH_UNITS = distance_util.VALID_UNITS MASS_UNITS = [MASS_POUNDS, MASS_OUNCES, MASS_KILOGRAMS, MASS_GRAMS] @@ -135,7 +137,7 @@ class UnitSystem: # type ignore: https://github.com/python/mypy/issues/7207 return volume_util.convert(volume, from_unit, self.volume_unit) # type: ignore - def as_dict(self) -> dict: + def as_dict(self) -> Dict[str, str]: """Convert the unit system to a dictionary.""" return { LENGTH: self.length_unit,