Skip to content

Commit 3095207

Browse files
committed
Implement get on CaseInsensitiveDict
get was previously provided by the parent class which had to raise KeyError for missing values. Since try/except is only cheap for the non-exception case the performance was not good when the key was missing similar to python/cpython#106665 but in the HA case we call this even more frequently
1 parent ac4171a commit 3095207

File tree

2 files changed

+28
-15
lines changed

2 files changed

+28
-15
lines changed

async_upnp_client/ssdp_listener.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@
4141
def valid_search_headers(headers: CaseInsensitiveDict) -> bool:
4242
"""Validate if this search is usable."""
4343
# pylint: disable=invalid-name
44-
udn = headers.get("_udn") # type: Optional[str]
45-
st = headers.get("st") # type: Optional[str]
46-
location = headers.get("location", "") # type: str
44+
udn = headers.get_lower("_udn") # type: Optional[str]
45+
st = headers.get_lower("st") # type: Optional[str]
46+
location = headers.get_lower("location", "") # type: str
4747
return bool(
4848
udn
4949
and st
@@ -60,10 +60,10 @@ def valid_search_headers(headers: CaseInsensitiveDict) -> bool:
6060
def valid_advertisement_headers(headers: CaseInsensitiveDict) -> bool:
6161
"""Validate if this advertisement is usable for connecting to a device."""
6262
# pylint: disable=invalid-name
63-
udn = headers.get("_udn") # type: Optional[str]
64-
nt = headers.get("nt") # type: Optional[str]
65-
nts = headers.get("nts") # type: Optional[str]
66-
location = headers.get("location", "") # type: str
63+
udn = headers.get_lower("_udn") # type: Optional[str]
64+
nt = headers.get_lower("nt") # type: Optional[str]
65+
nts = headers.get_lower("nts") # type: Optional[str]
66+
location = headers.get_lower("location", "") # type: str
6767
return bool(
6868
udn
6969
and nt
@@ -81,15 +81,15 @@ def valid_advertisement_headers(headers: CaseInsensitiveDict) -> bool:
8181
def valid_byebye_headers(headers: CaseInsensitiveDict) -> bool:
8282
"""Validate if this advertisement has required headers for byebye."""
8383
# pylint: disable=invalid-name
84-
udn = headers.get("_udn") # type: Optional[str]
85-
nt = headers.get("nt") # type: Optional[str]
86-
nts = headers.get("nts") # type: Optional[str]
84+
udn = headers.get_lower("_udn") # type: Optional[str]
85+
nt = headers.get_lower("nt") # type: Optional[str]
86+
nts = headers.get_lower("nts") # type: Optional[str]
8787
return bool(udn and nt and nts)
8888

8989

9090
def extract_valid_to(headers: CaseInsensitiveDict) -> datetime:
9191
"""Extract/create valid to."""
92-
cache_control = headers.get("cache-control", "")
92+
cache_control = headers.get_lower("cache-control", "")
9393
match = CACHE_CONTROL_RE.search(cache_control)
9494
if match:
9595
max_age = int(match[1])
@@ -247,7 +247,7 @@ def ip_version_from_location(location: str) -> Optional[int]:
247247

248248
def location_changed(ssdp_device: SsdpDevice, headers: CaseInsensitiveDict) -> bool:
249249
"""Test if location changed for device."""
250-
new_location = headers.get("location", "")
250+
new_location = headers.get_lower("location", "")
251251
if not new_location:
252252
return False
253253

async_upnp_client/utils.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ def as_lower_dict(self) -> Dict[str, Any]:
4141
"""Return the underlying dict in lowercase."""
4242
return {k.lower(): v for k, v in self._data.items()}
4343

44-
def get_lower(self, lower_key: str) -> Any:
44+
def get_lower(self, lower_key: str, default: Any = None) -> Any:
4545
"""Get a lower case key."""
4646
data_key = self._case_map.get(lower_key, _SENTINEL)
4747
if data_key is not _SENTINEL:
48-
return self._data[data_key]
49-
return None
48+
return self._data.get(data_key, default)
49+
return default
5050

5151
def replace(self, new_data: abcMapping) -> None:
5252
"""Replace the underlying dict."""
@@ -56,6 +56,19 @@ def replace(self, new_data: abcMapping) -> None:
5656
self._data = {**new_data}
5757
self._case_map = {k.lower(): k for k in self._data}
5858

59+
def get(self, key: str, default: Any = None) -> Any:
60+
"""Get item with default.
61+
62+
This implementation is case insensitive and avoids
63+
calling __getitem__ which would raise KeyError and
64+
cause unnecessary exception handling.
65+
"""
66+
case_map = self._case_map
67+
data_key = case_map.get(key, case_map.get(key.lower(), _SENTINEL))
68+
if data_key is not _SENTINEL:
69+
return self._data.get(data_key, default)
70+
return default
71+
5972
def __setitem__(self, key: str, value: Any) -> None:
6073
"""Set item."""
6174
lower_key = key.lower()

0 commit comments

Comments
 (0)