mirror of
https://github.com/home-assistant/core.git
synced 2025-12-24 21:06:19 +00:00
Add internal util.snakecase, use instead of stringcase (#156775)
Co-authored-by: Franck Nijhof <git@frenck.dev> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -3,11 +3,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Any, cast
|
||||
|
||||
from stringcase import snakecase
|
||||
|
||||
from homeassistant.components.device_tracker import (
|
||||
DOMAIN as DEVICE_TRACKER_DOMAIN,
|
||||
ScannerEntity,
|
||||
@@ -18,6 +15,7 @@ from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util import snakecase
|
||||
|
||||
from . import Router
|
||||
from .const import (
|
||||
@@ -156,22 +154,6 @@ def async_add_new_entities(
|
||||
async_add_entities(new_entities, True)
|
||||
|
||||
|
||||
def _better_snakecase(text: str) -> str:
|
||||
# Awaiting https://github.com/okunishinishi/python-stringcase/pull/18
|
||||
if text == text.upper():
|
||||
# All uppercase to all lowercase to get http for HTTP, not h_t_t_p
|
||||
text = text.lower()
|
||||
else:
|
||||
# Three or more consecutive uppercase with middle part lowercased
|
||||
# to get http_response for HTTPResponse, not h_t_t_p_response
|
||||
text = re.sub(
|
||||
r"([A-Z])([A-Z]+)([A-Z](?:[^A-Z]|$))",
|
||||
lambda match: f"{match.group(1)}{match.group(2).lower()}{match.group(3)}",
|
||||
text,
|
||||
)
|
||||
return cast(str, snakecase(text))
|
||||
|
||||
|
||||
class HuaweiLteScannerEntity(HuaweiLteBaseEntity, ScannerEntity):
|
||||
"""Huawei LTE router scanner entity."""
|
||||
|
||||
@@ -235,7 +217,7 @@ class HuaweiLteScannerEntity(HuaweiLteBaseEntity, ScannerEntity):
|
||||
self._ip_address = (host.get("IpAddress") or "").split(";", 2)[0] or None
|
||||
self._hostname = host.get("HostName")
|
||||
self._extra_state_attributes = {
|
||||
_better_snakecase(k): v
|
||||
snakecase(k): v
|
||||
for k, v in host.items()
|
||||
if k
|
||||
in {
|
||||
|
||||
@@ -6,11 +6,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/huawei_lte",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["huawei_lte_api.Session"],
|
||||
"requirements": [
|
||||
"huawei-lte-api==1.11.0",
|
||||
"stringcase==1.2.0",
|
||||
"url-normalize==2.2.1"
|
||||
],
|
||||
"requirements": ["huawei-lte-api==1.11.0", "url-normalize==2.2.1"],
|
||||
"ssdp": [
|
||||
{
|
||||
"deviceType": "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
|
||||
|
||||
@@ -13,8 +13,8 @@ rules:
|
||||
status: todo
|
||||
comment: See if we can catch more specific exceptions in get_device_info.
|
||||
dependency-transparency:
|
||||
status: todo
|
||||
comment: stringcase is not built and published to PyPI from a public CI pipeline. huawei-lte-api is from https://gitlab.salamek.cz/Mirrors/huawei-lte-api, see https://github.com/Salamek/huawei-lte-api/issues/253
|
||||
status: done
|
||||
comment: huawei-lte-api is from https://gitlab.salamek.cz/Mirrors/huawei-lte-api, see https://github.com/Salamek/huawei-lte-api/issues/253
|
||||
docs-actions: done
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
@@ -82,5 +82,4 @@ rules:
|
||||
status: exempt
|
||||
comment: Underlying huawei-lte-api does not use aiohttp or httpx, so this does not apply.
|
||||
strict-typing:
|
||||
status: todo
|
||||
comment: Integration is strictly typechecked already, and huawei-lte-api and url-normalize are in order. stringcase is not typed.
|
||||
status: done
|
||||
|
||||
@@ -9,7 +9,6 @@ from typing import TYPE_CHECKING, Any
|
||||
|
||||
from aiosolaredge import SolarEdge
|
||||
from solaredge_web import EnergyData, SolarEdgeWeb, TimeUnit
|
||||
from stringcase import snakecase
|
||||
|
||||
from homeassistant.components.recorder import get_instance
|
||||
from homeassistant.components.recorder.models import (
|
||||
@@ -26,7 +25,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, UnitOfEnergy
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util import dt as dt_util, snakecase
|
||||
from homeassistant.util.unit_conversion import EnergyConverter
|
||||
|
||||
from .const import (
|
||||
|
||||
@@ -14,9 +14,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aiosolaredge", "solaredge_web"],
|
||||
"requirements": [
|
||||
"aiosolaredge==0.2.0",
|
||||
"stringcase==1.2.0",
|
||||
"solaredge-web==0.0.1"
|
||||
]
|
||||
"requirements": ["aiosolaredge==0.2.0", "solaredge-web==0.0.1"]
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import logging
|
||||
|
||||
from requests import RequestException
|
||||
from requests.exceptions import HTTPError
|
||||
from stringcase import camelcase, snakecase
|
||||
from stringcase import camelcase
|
||||
import thermoworks_smoke
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -30,6 +30,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import snakecase
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/traccar",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pytraccar"],
|
||||
"requirements": ["pytraccar==3.0.0", "stringcase==1.2.0"]
|
||||
"requirements": ["pytraccar==3.0.0"]
|
||||
}
|
||||
|
||||
@@ -97,6 +97,20 @@ def get_random_string(length: int = 10) -> str:
|
||||
return "".join(generator.choice(source_chars) for _ in range(length))
|
||||
|
||||
|
||||
# Adapted from https://github.com/okunishinishi/python-stringcase, with improvements
|
||||
def snakecase(text: str) -> str:
|
||||
"""Convert a string to snake_case."""
|
||||
text = re.sub(r"[\s.-]", "_", text)
|
||||
if not text.isupper():
|
||||
# Underscore before last uppercase of groups of 2+ uppercase ("HTTPResponse", "IPAddress")
|
||||
text = re.sub(
|
||||
r"[A-Z]{2,}(?=[A-Z][^A-Z])", lambda match: match.group(0) + "_", text
|
||||
)
|
||||
# Underscore between non-uppercase followed by uppercase
|
||||
text = re.sub(r"(?<=[^A-Z_])[A-Z]", lambda match: "_" + match.group(0), text)
|
||||
return text.lower()
|
||||
|
||||
|
||||
class Throttle:
|
||||
"""A class for throttling the execution of tasks.
|
||||
|
||||
|
||||
5
requirements_all.txt
generated
5
requirements_all.txt
generated
@@ -2899,11 +2899,6 @@ stookwijzer==1.6.1
|
||||
# homeassistant.components.streamlabswater
|
||||
streamlabswater==1.0.1
|
||||
|
||||
# homeassistant.components.huawei_lte
|
||||
# homeassistant.components.solaredge
|
||||
# homeassistant.components.traccar
|
||||
stringcase==1.2.0
|
||||
|
||||
# homeassistant.components.subaru
|
||||
subarulink==0.7.15
|
||||
|
||||
|
||||
5
requirements_test_all.txt
generated
5
requirements_test_all.txt
generated
@@ -2408,11 +2408,6 @@ stookwijzer==1.6.1
|
||||
# homeassistant.components.streamlabswater
|
||||
streamlabswater==1.0.1
|
||||
|
||||
# homeassistant.components.huawei_lte
|
||||
# homeassistant.components.solaredge
|
||||
# homeassistant.components.traccar
|
||||
stringcase==1.2.0
|
||||
|
||||
# homeassistant.components.subaru
|
||||
subarulink==0.7.15
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
"""Huawei LTE device tracker tests."""
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.huawei_lte import device_tracker
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("value", "expected"),
|
||||
[
|
||||
("HTTP", "http"),
|
||||
("ID", "id"),
|
||||
("IPAddress", "ip_address"),
|
||||
("HTTPResponse", "http_response"),
|
||||
("foo_bar", "foo_bar"),
|
||||
],
|
||||
)
|
||||
def test_better_snakecase(value, expected) -> None:
|
||||
"""Test that better snakecase works better."""
|
||||
assert device_tracker._better_snakecase(value) == expected
|
||||
@@ -233,3 +233,31 @@ async def test_throttle_async() -> None:
|
||||
|
||||
assert (await test_method2()) is True
|
||||
assert (await test_method2()) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("input", "expected"),
|
||||
[
|
||||
("fooBar", "foo_bar"),
|
||||
("FooBar", "foo_bar"),
|
||||
("Foobar", "foobar"),
|
||||
("Foo.bar", "foo_bar"),
|
||||
("_foo-Bar", "_foo_bar"),
|
||||
("HTTP", "http"),
|
||||
("HTTPResponse", "http_response"),
|
||||
("iPhone", "i_phone"),
|
||||
("IPAddress", "ip_address"),
|
||||
("IP_Address", "ip_address"),
|
||||
("My IP Address", "my_ip_address"),
|
||||
("LocalIP", "local_ip"),
|
||||
("Python3Thing", "python3_thing"),
|
||||
("mTLS", "m_tls"),
|
||||
("DTrace", "dtrace"),
|
||||
("IPv4", "ipv4"),
|
||||
("ID", "id"),
|
||||
("", ""),
|
||||
],
|
||||
)
|
||||
def test_snakecase(input, expected) -> None:
|
||||
"""Test snake casing a string."""
|
||||
assert util.snakecase(input) == expected
|
||||
|
||||
Reference in New Issue
Block a user