Enable strict typing for shopping_list (#107913)

This commit is contained in:
Marc Mueller 2024-01-14 09:38:53 +01:00 committed by GitHub
parent d4cb055d75
commit 88d7fc87c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 53 additions and 22 deletions

View file

@ -362,6 +362,7 @@ homeassistant.components.sensor.*
homeassistant.components.senz.* homeassistant.components.senz.*
homeassistant.components.sfr_box.* homeassistant.components.sfr_box.*
homeassistant.components.shelly.* homeassistant.components.shelly.*
homeassistant.components.shopping_list.*
homeassistant.components.simplepush.* homeassistant.components.simplepush.*
homeassistant.components.simplisafe.* homeassistant.components.simplisafe.*
homeassistant.components.siren.* homeassistant.components.siren.*

View file

@ -1,10 +1,13 @@
"""Support to manage a shopping list.""" """Support to manage a shopping list."""
from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from http import HTTPStatus from http import HTTPStatus
import logging import logging
from typing import Any, cast from typing import Any, cast
import uuid import uuid
from aiohttp import web
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
@ -12,7 +15,7 @@ from homeassistant.components import http, websocket_api
from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_NAME, Platform from homeassistant.const import ATTR_NAME, Platform
from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.core import Context, HomeAssistant, ServiceCall, callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.json import save_json from homeassistant.helpers.json import save_json
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
@ -197,9 +200,15 @@ class ShoppingData:
self.items: list[dict[str, JsonValueType]] = [] self.items: list[dict[str, JsonValueType]] = []
self._listeners: list[Callable[[], None]] = [] self._listeners: list[Callable[[], None]] = []
async def async_add(self, name, complete=False, context=None): async def async_add(
self, name: str | None, complete: bool = False, context: Context | None = None
) -> dict[str, JsonValueType]:
"""Add a shopping list item.""" """Add a shopping list item."""
item = {"name": name, "id": uuid.uuid4().hex, "complete": complete} item: dict[str, JsonValueType] = {
"name": name,
"id": uuid.uuid4().hex,
"complete": complete,
}
self.items.append(item) self.items.append(item)
await self.hass.async_add_executor_job(self.save) await self.hass.async_add_executor_job(self.save)
self._async_notify() self._async_notify()
@ -211,7 +220,7 @@ class ShoppingData:
return item return item
async def async_remove( async def async_remove(
self, item_id: str, context=None self, item_id: str, context: Context | None = None
) -> dict[str, JsonValueType] | None: ) -> dict[str, JsonValueType] | None:
"""Remove a shopping list item.""" """Remove a shopping list item."""
removed = await self.async_remove_items( removed = await self.async_remove_items(
@ -220,7 +229,7 @@ class ShoppingData:
return next(iter(removed), None) return next(iter(removed), None)
async def async_remove_items( async def async_remove_items(
self, item_ids: set[str], context=None self, item_ids: set[str], context: Context | None = None
) -> list[dict[str, JsonValueType]]: ) -> list[dict[str, JsonValueType]]:
"""Remove a shopping list item.""" """Remove a shopping list item."""
items_dict: dict[str, dict[str, JsonValueType]] = {} items_dict: dict[str, dict[str, JsonValueType]] = {}
@ -248,7 +257,9 @@ class ShoppingData:
) )
return removed return removed
async def async_update(self, item_id, info, context=None): async def async_update(
self, item_id: str | None, info: dict[str, Any], context: Context | None = None
) -> dict[str, JsonValueType]:
"""Update a shopping list item.""" """Update a shopping list item."""
item = next((itm for itm in self.items if itm["id"] == item_id), None) item = next((itm for itm in self.items if itm["id"] == item_id), None)
@ -266,7 +277,7 @@ class ShoppingData:
) )
return item return item
async def async_clear_completed(self, context=None): async def async_clear_completed(self, context: Context | None = None) -> None:
"""Clear completed items.""" """Clear completed items."""
self.items = [itm for itm in self.items if not itm["complete"]] self.items = [itm for itm in self.items if not itm["complete"]]
await self.hass.async_add_executor_job(self.save) await self.hass.async_add_executor_job(self.save)
@ -277,7 +288,9 @@ class ShoppingData:
context=context, context=context,
) )
async def async_update_list(self, info, context=None): async def async_update_list(
self, info: dict[str, JsonValueType], context: Context | None = None
) -> list[dict[str, JsonValueType]]:
"""Update all items in the list.""" """Update all items in the list."""
for item in self.items: for item in self.items:
item.update(info) item.update(info)
@ -291,7 +304,9 @@ class ShoppingData:
return self.items return self.items
@callback @callback
def async_reorder(self, item_ids, context=None): def async_reorder(
self, item_ids: list[str], context: Context | None = None
) -> None:
"""Reorder items.""" """Reorder items."""
# The array for sorted items. # The array for sorted items.
new_items = [] new_items = []
@ -346,9 +361,11 @@ class ShoppingData:
{"action": "reorder"}, {"action": "reorder"},
) )
async def async_sort(self, reverse=False, context=None): async def async_sort(
self, reverse: bool = False, context: Context | None = None
) -> None:
"""Sort items by name.""" """Sort items by name."""
self.items = sorted(self.items, key=lambda item: item["name"], reverse=reverse) self.items = sorted(self.items, key=lambda item: item["name"], reverse=reverse) # type: ignore[arg-type,return-value]
self.hass.async_add_executor_job(self.save) self.hass.async_add_executor_job(self.save)
self._async_notify() self._async_notify()
self.hass.bus.async_fire( self.hass.bus.async_fire(
@ -376,7 +393,7 @@ class ShoppingData:
def async_add_listener(self, cb: Callable[[], None]) -> Callable[[], None]: def async_add_listener(self, cb: Callable[[], None]) -> Callable[[], None]:
"""Add a listener to notify when data is updated.""" """Add a listener to notify when data is updated."""
def unsub(): def unsub() -> None:
self._listeners.remove(cb) self._listeners.remove(cb)
self._listeners.append(cb) self._listeners.append(cb)
@ -395,7 +412,7 @@ class ShoppingListView(http.HomeAssistantView):
name = "api:shopping_list" name = "api:shopping_list"
@callback @callback
def get(self, request): def get(self, request: web.Request) -> web.Response:
"""Retrieve shopping list items.""" """Retrieve shopping list items."""
return self.json(request.app["hass"].data[DOMAIN].items) return self.json(request.app["hass"].data[DOMAIN].items)
@ -406,12 +423,13 @@ class UpdateShoppingListItemView(http.HomeAssistantView):
url = "/api/shopping_list/item/{item_id}" url = "/api/shopping_list/item/{item_id}"
name = "api:shopping_list:item:id" name = "api:shopping_list:item:id"
async def post(self, request, item_id): async def post(self, request: web.Request, item_id: str) -> web.Response:
"""Update a shopping list item.""" """Update a shopping list item."""
data = await request.json() data = await request.json()
hass: HomeAssistant = request.app["hass"]
try: try:
item = await request.app["hass"].data[DOMAIN].async_update(item_id, data) item = await hass.data[DOMAIN].async_update(item_id, data)
return self.json(item) return self.json(item)
except NoMatchingShoppingListItem: except NoMatchingShoppingListItem:
return self.json_message("Item not found", HTTPStatus.NOT_FOUND) return self.json_message("Item not found", HTTPStatus.NOT_FOUND)
@ -426,9 +444,10 @@ class CreateShoppingListItemView(http.HomeAssistantView):
name = "api:shopping_list:item" name = "api:shopping_list:item"
@RequestDataValidator(vol.Schema({vol.Required("name"): str})) @RequestDataValidator(vol.Schema({vol.Required("name"): str}))
async def post(self, request, data): async def post(self, request: web.Request, data: dict[str, str]) -> web.Response:
"""Create a new shopping list item.""" """Create a new shopping list item."""
item = await request.app["hass"].data[DOMAIN].async_add(data["name"]) hass: HomeAssistant = request.app["hass"]
item = await hass.data[DOMAIN].async_add(data["name"])
return self.json(item) return self.json(item)
@ -438,9 +457,9 @@ class ClearCompletedItemsView(http.HomeAssistantView):
url = "/api/shopping_list/clear_completed" url = "/api/shopping_list/clear_completed"
name = "api:shopping_list:clear_completed" name = "api:shopping_list:clear_completed"
async def post(self, request): async def post(self, request: web.Request) -> web.Response:
"""Retrieve if API is running.""" """Retrieve if API is running."""
hass = request.app["hass"] hass: HomeAssistant = request.app["hass"]
await hass.data[DOMAIN].async_clear_completed() await hass.data[DOMAIN].async_clear_completed()
return self.json_message("Cleared completed items.") return self.json_message("Cleared completed items.")

View file

@ -1,6 +1,7 @@
"""Intents for the Shopping List integration.""" """Intents for the Shopping List integration."""
from __future__ import annotations from __future__ import annotations
from homeassistant.core import HomeAssistant
from homeassistant.helpers import intent from homeassistant.helpers import intent
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -10,7 +11,7 @@ INTENT_ADD_ITEM = "HassShoppingListAddItem"
INTENT_LAST_ITEMS = "HassShoppingListLastItems" INTENT_LAST_ITEMS = "HassShoppingListLastItems"
async def async_setup_intents(hass): async def async_setup_intents(hass: HomeAssistant) -> None:
"""Set up the Shopping List intents.""" """Set up the Shopping List intents."""
intent.async_register(hass, AddItemIntent()) intent.async_register(hass, AddItemIntent())
intent.async_register(hass, ListTopItemsIntent()) intent.async_register(hass, ListTopItemsIntent())
@ -22,7 +23,7 @@ class AddItemIntent(intent.IntentHandler):
intent_type = INTENT_ADD_ITEM intent_type = INTENT_ADD_ITEM
slot_schema = {"item": cv.string} slot_schema = {"item": cv.string}
async def async_handle(self, intent_obj: intent.Intent): async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
"""Handle the intent.""" """Handle the intent."""
slots = self.async_validate_slots(intent_obj.slots) slots = self.async_validate_slots(intent_obj.slots)
item = slots["item"]["value"] item = slots["item"]["value"]
@ -39,7 +40,7 @@ class ListTopItemsIntent(intent.IntentHandler):
intent_type = INTENT_LAST_ITEMS intent_type = INTENT_LAST_ITEMS
slot_schema = {"item": cv.string} slot_schema = {"item": cv.string}
async def async_handle(self, intent_obj: intent.Intent): async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
"""Handle the intent.""" """Handle the intent."""
items = intent_obj.hass.data[DOMAIN].items[-5:] items = intent_obj.hass.data[DOMAIN].items[-5:]
response = intent_obj.create_response() response = intent_obj.create_response()

View file

@ -3381,6 +3381,16 @@ disallow_untyped_defs = true
warn_return_any = true warn_return_any = true
warn_unreachable = true warn_unreachable = true
[mypy-homeassistant.components.shopping_list.*]
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true
[mypy-homeassistant.components.simplepush.*] [mypy-homeassistant.components.simplepush.*]
check_untyped_defs = true check_untyped_defs = true
disallow_incomplete_defs = true disallow_incomplete_defs = true