mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-04-26 16:46:23 +00:00
Remove unnecessary cache (#40)
The cache was useful trick when the property accesses caused I/O, which is unnecessary now as dev.update() does explicitly cache results until its called again.
This commit is contained in:
parent
5ff299664e
commit
c90465c5dd
@ -71,8 +71,8 @@ class SmartBulb(SmartDevice):
|
|||||||
|
|
||||||
LIGHT_SERVICE = "smartlife.iot.smartbulb.lightingservice"
|
LIGHT_SERVICE = "smartlife.iot.smartbulb.lightingservice"
|
||||||
|
|
||||||
def __init__(self, host: str, *, cache_ttl: int = 3) -> None:
|
def __init__(self, host: str) -> None:
|
||||||
SmartDevice.__init__(self, host=host, cache_ttl=cache_ttl)
|
super().__init__(host=host)
|
||||||
self.emeter_type = "smartlife.iot.common.emeter"
|
self.emeter_type = "smartlife.iot.common.emeter"
|
||||||
self._device_type = DeviceType.Bulb
|
self._device_type = DeviceType.Bulb
|
||||||
self._light_state = None
|
self._light_state = None
|
||||||
|
@ -14,8 +14,7 @@ http://www.apache.org/licenses/LICENSE-2.0
|
|||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
from collections import defaultdict
|
from datetime import datetime
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
@ -103,7 +102,7 @@ def requires_update(f):
|
|||||||
class SmartDevice:
|
class SmartDevice:
|
||||||
"""Base class for all supported device types."""
|
"""Base class for all supported device types."""
|
||||||
|
|
||||||
def __init__(self, host: str, *, cache_ttl: int = 3) -> None:
|
def __init__(self, host: str) -> None:
|
||||||
"""Create a new SmartDevice instance.
|
"""Create a new SmartDevice instance.
|
||||||
|
|
||||||
:param str host: host name or ip address on which the device listens
|
:param str host: host name or ip address on which the device listens
|
||||||
@ -113,48 +112,10 @@ class SmartDevice:
|
|||||||
|
|
||||||
self.protocol = TPLinkSmartHomeProtocol()
|
self.protocol = TPLinkSmartHomeProtocol()
|
||||||
self.emeter_type = "emeter"
|
self.emeter_type = "emeter"
|
||||||
self.cache_ttl = timedelta(seconds=cache_ttl)
|
_LOGGER.debug("Initializing %s", self.host)
|
||||||
_LOGGER.debug("Initializing %s with cache ttl %s", self.host, self.cache_ttl)
|
|
||||||
self.cache = defaultdict(lambda: defaultdict(lambda: None)) # type: ignore
|
|
||||||
self._device_type = DeviceType.Unknown
|
self._device_type = DeviceType.Unknown
|
||||||
self._sys_info: Optional[Dict] = None
|
self._sys_info: Optional[Dict] = None
|
||||||
|
|
||||||
def _result_from_cache(self, target, cmd) -> Optional[Dict]:
|
|
||||||
"""Return query result from cache if still fresh.
|
|
||||||
|
|
||||||
Only results from commands starting with `get_` are considered cacheable.
|
|
||||||
|
|
||||||
:param target: Target system
|
|
||||||
:param cmd: Command
|
|
||||||
:rtype: query result or None if expired.
|
|
||||||
"""
|
|
||||||
_LOGGER.debug("Checking cache for %s %s", target, cmd)
|
|
||||||
if cmd not in self.cache[target]:
|
|
||||||
return None
|
|
||||||
|
|
||||||
cached = self.cache[target][cmd]
|
|
||||||
if cached and cached["last_updated"] is not None:
|
|
||||||
if cached[
|
|
||||||
"last_updated"
|
|
||||||
] + self.cache_ttl > datetime.utcnow() and cmd.startswith("get_"):
|
|
||||||
_LOGGER.debug("Got cached %s %s", target, cmd)
|
|
||||||
return self.cache[target][cmd]
|
|
||||||
else:
|
|
||||||
_LOGGER.debug("Invalidating the cache for %s cmd %s", target, cmd)
|
|
||||||
for cache_entry in self.cache[target].values():
|
|
||||||
cache_entry["last_updated"] = datetime.utcfromtimestamp(0)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _insert_to_cache(self, target: str, cmd: str, response: Dict) -> None:
|
|
||||||
"""Add response for a given command to the cache.
|
|
||||||
|
|
||||||
:param target: Target system
|
|
||||||
:param cmd: Command
|
|
||||||
:param response: Response to be cached
|
|
||||||
"""
|
|
||||||
self.cache[target][cmd] = response.copy()
|
|
||||||
self.cache[target][cmd]["last_updated"] = datetime.utcnow()
|
|
||||||
|
|
||||||
async def _query_helper(
|
async def _query_helper(
|
||||||
self, target: str, cmd: str, arg: Optional[Dict] = None, child_ids=None
|
self, target: str, cmd: str, arg: Optional[Dict] = None, child_ids=None
|
||||||
) -> Any:
|
) -> Any:
|
||||||
@ -172,11 +133,7 @@ class SmartDevice:
|
|||||||
request = {"context": {"child_ids": child_ids}, target: {cmd: arg}}
|
request = {"context": {"child_ids": child_ids}, target: {cmd: arg}}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = self._result_from_cache(target, cmd)
|
response = await self.protocol.query(host=self.host, request=request)
|
||||||
if response is None:
|
|
||||||
_LOGGER.debug("Got no result from cache, querying the device.")
|
|
||||||
response = await self.protocol.query(host=self.host, request=request)
|
|
||||||
self._insert_to_cache(target, cmd, response)
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise SmartDeviceException(f"Communication error on {target}:{cmd}") from ex
|
raise SmartDeviceException(f"Communication error on {target}:{cmd}") from ex
|
||||||
|
|
||||||
|
@ -35,8 +35,8 @@ class SmartPlug(SmartDevice):
|
|||||||
and should be handled by the user of the library.
|
and should be handled by the user of the library.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, host: str, *, cache_ttl: int = 3) -> None:
|
def __init__(self, host: str) -> None:
|
||||||
SmartDevice.__init__(self, host, cache_ttl=cache_ttl)
|
super().__init__(host)
|
||||||
self.emeter_type = "emeter"
|
self.emeter_type = "emeter"
|
||||||
self._device_type = DeviceType.Plug
|
self._device_type = DeviceType.Plug
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ class SmartStrip(SmartDevice):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def __init__(self, host: str, *, cache_ttl: int = 3) -> None:
|
def __init__(self, host: str, *, cache_ttl: int = 3) -> None:
|
||||||
SmartDevice.__init__(self, host=host, cache_ttl=cache_ttl)
|
super().__init__(host=host)
|
||||||
self.emeter_type = "emeter"
|
self.emeter_type = "emeter"
|
||||||
self._device_type = DeviceType.Strip
|
self._device_type = DeviceType.Strip
|
||||||
self.plugs: List[SmartStripPlug] = []
|
self.plugs: List[SmartStripPlug] = []
|
||||||
@ -78,12 +78,7 @@ class SmartStrip(SmartDevice):
|
|||||||
_LOGGER.debug("Initializing %s child sockets", len(children))
|
_LOGGER.debug("Initializing %s child sockets", len(children))
|
||||||
for child in children:
|
for child in children:
|
||||||
self.plugs.append(
|
self.plugs.append(
|
||||||
SmartStripPlug(
|
SmartStripPlug(self.host, parent=self, child_id=child["id"])
|
||||||
self.host,
|
|
||||||
parent=self,
|
|
||||||
child_id=child["id"],
|
|
||||||
cache_ttl=self.cache_ttl.total_seconds(),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def turn_on(self):
|
async def turn_on(self):
|
||||||
@ -232,10 +227,8 @@ class SmartStripPlug(SmartPlug):
|
|||||||
on the parent device before accessing the properties.
|
on the parent device before accessing the properties.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, host: str, parent: "SmartStrip", child_id: str) -> None:
|
||||||
self, host: str, parent: "SmartStrip", child_id: str, *, cache_ttl: int = 3
|
super().__init__(host)
|
||||||
) -> None:
|
|
||||||
super().__init__(host, cache_ttl=cache_ttl)
|
|
||||||
|
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.child_id = child_id
|
self.child_id = child_id
|
||||||
|
@ -116,8 +116,7 @@ def dev(request):
|
|||||||
with open(file) as f:
|
with open(file) as f:
|
||||||
sysinfo = json.load(f)
|
sysinfo = json.load(f)
|
||||||
model = basename(file)
|
model = basename(file)
|
||||||
params = {"host": "123.123.123.123", "cache_ttl": 0}
|
p = device_for_file(model)(host="123.123.123.123")
|
||||||
p = device_for_file(model)(**params)
|
|
||||||
p.protocol = FakeTransportProtocol(sysinfo)
|
p.protocol = FakeTransportProtocol(sysinfo)
|
||||||
loop.run_until_complete(p.update())
|
loop.run_until_complete(p.update())
|
||||||
yield p
|
yield p
|
||||||
|
@ -471,40 +471,6 @@ async def test_all_binary_states(dev):
|
|||||||
assert state == state_map[index]
|
assert state == state_map[index]
|
||||||
|
|
||||||
|
|
||||||
# def test_cache(dev):
|
|
||||||
# from datetime import timedelta
|
|
||||||
|
|
||||||
# dev.cache_ttl = timedelta(seconds=3)
|
|
||||||
# with patch.object(
|
|
||||||
# FakeTransportProtocol, "query", wraps=dev.protocol.query
|
|
||||||
# ) as query_mock:
|
|
||||||
# CHECK_COUNT = 1
|
|
||||||
# # Smartstrip calls sysinfo in its __init__ to request children, so
|
|
||||||
# # the even first get call here will get its results from the cache.
|
|
||||||
# if dev.is_strip:
|
|
||||||
# CHECK_COUNT = 0
|
|
||||||
|
|
||||||
# dev.sys_info
|
|
||||||
# assert query_mock.call_count == CHECK_COUNT
|
|
||||||
# dev.sys_info
|
|
||||||
# assert query_mock.call_count == CHECK_COUNT
|
|
||||||
|
|
||||||
|
|
||||||
# def test_cache_invalidates(dev):
|
|
||||||
# from datetime import timedelta
|
|
||||||
|
|
||||||
# dev.cache_ttl = timedelta(seconds=0)
|
|
||||||
|
|
||||||
# with patch.object(
|
|
||||||
# FakeTransportProtocol, "query", wraps=dev.protocol.query
|
|
||||||
# ) as query_mock:
|
|
||||||
# dev.sys_info
|
|
||||||
# assert query_mock.call_count == 1
|
|
||||||
# dev.sys_info
|
|
||||||
# assert query_mock.call_count == 2
|
|
||||||
# # assert query_mock.called_once()
|
|
||||||
|
|
||||||
|
|
||||||
async def test_representation(dev):
|
async def test_representation(dev):
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user