Run pylint from tox (#243)
This commit is contained in:
@@ -209,7 +209,7 @@ async def async_setup(hass: HomeAssistant, config: dict):
|
|||||||
await discovery.start()
|
await discovery.start()
|
||||||
hass.data[DOMAIN][DATA_DISCOVERY] = discovery
|
hass.data[DOMAIN][DATA_DISCOVERY] = discovery
|
||||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)
|
||||||
except Exception:
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception("failed to set up discovery")
|
_LOGGER.exception("failed to set up discovery")
|
||||||
|
|
||||||
hass.helpers.service.async_register_admin_service(
|
hass.helpers.service.async_register_admin_service(
|
||||||
|
@@ -65,7 +65,7 @@ async def async_setup_entry(
|
|||||||
# Add DPS used by this platform to the request list
|
# Add DPS used by this platform to the request list
|
||||||
for dp_conf in dps_config_fields:
|
for dp_conf in dps_config_fields:
|
||||||
if dp_conf in device_config:
|
if dp_conf in device_config:
|
||||||
tuyainterface._dps_to_request[device_config[dp_conf]] = None
|
tuyainterface.add_dps_to_request(device_config[dp_conf])
|
||||||
|
|
||||||
entities.append(
|
entities.append(
|
||||||
entity_class(
|
entity_class(
|
||||||
@@ -93,11 +93,22 @@ def get_entity_config(config_entry, dp_id):
|
|||||||
raise Exception(f"missing entity config for id {dp_id}")
|
raise Exception(f"missing entity config for id {dp_id}")
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_config_entry_by_device_id(hass, device_id):
|
||||||
|
"""Look up config entry by device id."""
|
||||||
|
current_entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
|
for entry in current_entries:
|
||||||
|
if entry.data[CONF_DEVICE_ID] == device_id:
|
||||||
|
return entry
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
|
class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
|
||||||
"""Cache wrapper for pytuya.TuyaInterface."""
|
"""Cache wrapper for pytuya.TuyaInterface."""
|
||||||
|
|
||||||
def __init__(self, hass, config_entry):
|
def __init__(self, hass, config_entry):
|
||||||
"""Initialize the cache."""
|
"""Initialize the cache."""
|
||||||
|
super().__init__()
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
self._config_entry = config_entry
|
self._config_entry = config_entry
|
||||||
self._interface = None
|
self._interface = None
|
||||||
@@ -140,7 +151,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
|
|||||||
raise Exception("Failed to retrieve status")
|
raise Exception("Failed to retrieve status")
|
||||||
|
|
||||||
self.status_updated(status)
|
self.status_updated(status)
|
||||||
except Exception:
|
except Exception: # pylint: disable=broad-except
|
||||||
self.exception(f"Connect to {self._config_entry[CONF_HOST]} failed")
|
self.exception(f"Connect to {self._config_entry[CONF_HOST]} failed")
|
||||||
if self._interface is not None:
|
if self._interface is not None:
|
||||||
await self._interface.close()
|
await self._interface.close()
|
||||||
@@ -161,7 +172,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
|
|||||||
if self._interface is not None:
|
if self._interface is not None:
|
||||||
try:
|
try:
|
||||||
await self._interface.set_dp(state, dp_index)
|
await self._interface.set_dp(state, dp_index)
|
||||||
except Exception:
|
except Exception: # pylint: disable=broad-except
|
||||||
self.exception("Failed to set DP %d to %d", dp_index, state)
|
self.exception("Failed to set DP %d to %d", dp_index, state)
|
||||||
else:
|
else:
|
||||||
self.error(
|
self.error(
|
||||||
@@ -173,7 +184,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
|
|||||||
if self._interface is not None:
|
if self._interface is not None:
|
||||||
try:
|
try:
|
||||||
await self._interface.set_dps(states)
|
await self._interface.set_dps(states)
|
||||||
except Exception:
|
except Exception: # pylint: disable=broad-except
|
||||||
self.exception("Failed to set DPs %r", states)
|
self.exception("Failed to set DPs %r", states)
|
||||||
else:
|
else:
|
||||||
self.error(
|
self.error(
|
||||||
@@ -203,6 +214,7 @@ class LocalTuyaEntity(Entity, pytuya.ContextualLogger):
|
|||||||
|
|
||||||
def __init__(self, device, config_entry, dp_id, logger, **kwargs):
|
def __init__(self, device, config_entry, dp_id, logger, **kwargs):
|
||||||
"""Initialize the Tuya entity."""
|
"""Initialize the Tuya entity."""
|
||||||
|
super().__init__()
|
||||||
self._device = device
|
self._device = device
|
||||||
self._config_entry = config_entry
|
self._config_entry = config_entry
|
||||||
self._config = get_entity_config(config_entry, dp_id)
|
self._config = get_entity_config(config_entry, dp_id)
|
||||||
|
@@ -171,10 +171,10 @@ async def validate_input(hass: core.HomeAssistant, data):
|
|||||||
)
|
)
|
||||||
|
|
||||||
detected_dps = await interface.detect_available_dps()
|
detected_dps = await interface.detect_available_dps()
|
||||||
except (ConnectionRefusedError, ConnectionResetError):
|
except (ConnectionRefusedError, ConnectionResetError) as ex:
|
||||||
raise CannotConnect
|
raise CannotConnect from ex
|
||||||
except ValueError:
|
except ValueError as ex:
|
||||||
raise InvalidAuth
|
raise InvalidAuth from ex
|
||||||
finally:
|
finally:
|
||||||
if interface:
|
if interface:
|
||||||
await interface.close()
|
await interface.close()
|
||||||
@@ -230,7 +230,7 @@ class LocaltuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
errors["base"] = "address_in_use"
|
errors["base"] = "address_in_use"
|
||||||
else:
|
else:
|
||||||
errors["base"] = "discovery_failed"
|
errors["base"] = "discovery_failed"
|
||||||
except Exception:
|
except Exception: # pylint: disable= broad-except
|
||||||
_LOGGER.exception("discovery failed")
|
_LOGGER.exception("discovery failed")
|
||||||
errors["base"] = "discovery_failed"
|
errors["base"] = "discovery_failed"
|
||||||
|
|
||||||
|
@@ -54,7 +54,7 @@ class TuyaDiscovery(asyncio.DatagramProtocol):
|
|||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Stop discovery."""
|
"""Stop discovery."""
|
||||||
self.callback = None
|
self._callback = None
|
||||||
for transport, _ in self._listeners:
|
for transport, _ in self._listeners:
|
||||||
transport.close()
|
transport.close()
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ class TuyaDiscovery(asyncio.DatagramProtocol):
|
|||||||
data = data[20:-8]
|
data = data[20:-8]
|
||||||
try:
|
try:
|
||||||
data = decrypt_udp(data)
|
data = decrypt_udp(data)
|
||||||
except Exception:
|
except Exception: # pylint: disable=broad-except
|
||||||
data = data.decode()
|
data = data.decode()
|
||||||
|
|
||||||
decoded = json.loads(data)
|
decoded = json.loads(data)
|
||||||
@@ -81,10 +81,10 @@ class TuyaDiscovery(asyncio.DatagramProtocol):
|
|||||||
|
|
||||||
async def discover():
|
async def discover():
|
||||||
"""Discover and return devices on local network."""
|
"""Discover and return devices on local network."""
|
||||||
discover = TuyaDiscovery()
|
discovery = TuyaDiscovery()
|
||||||
try:
|
try:
|
||||||
await discover.start()
|
await discovery.start()
|
||||||
await asyncio.sleep(DEFAULT_TIMEOUT)
|
await asyncio.sleep(DEFAULT_TIMEOUT)
|
||||||
finally:
|
finally:
|
||||||
discover.close()
|
discovery.close()
|
||||||
return discover.devices
|
return discovery.devices
|
||||||
|
@@ -188,7 +188,7 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
|
|||||||
"""Return the hs color value."""
|
"""Return the hs color value."""
|
||||||
if self.is_color_mode:
|
if self.is_color_mode:
|
||||||
return self._hs
|
return self._hs
|
||||||
elif self.supported_features & SUPPORT_COLOR:
|
if self.supported_features & SUPPORT_COLOR:
|
||||||
return [0, 0]
|
return [0, 0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@@ -178,7 +178,7 @@ class AESCipher:
|
|||||||
|
|
||||||
def __init__(self, key):
|
def __init__(self, key):
|
||||||
"""Initialize a new AESCipher."""
|
"""Initialize a new AESCipher."""
|
||||||
self.bs = 16
|
self.block_size = 16
|
||||||
self.cipher = Cipher(algorithms.AES(key), modes.ECB(), default_backend())
|
self.cipher = Cipher(algorithms.AES(key), modes.ECB(), default_backend())
|
||||||
|
|
||||||
def encrypt(self, raw, use_base64=True):
|
def encrypt(self, raw, use_base64=True):
|
||||||
@@ -195,13 +195,13 @@ class AESCipher:
|
|||||||
decryptor = self.cipher.decryptor()
|
decryptor = self.cipher.decryptor()
|
||||||
return self._unpad(decryptor.update(enc) + decryptor.finalize()).decode()
|
return self._unpad(decryptor.update(enc) + decryptor.finalize()).decode()
|
||||||
|
|
||||||
def _pad(self, s):
|
def _pad(self, data):
|
||||||
padnum = self.bs - len(s) % self.bs
|
padnum = self.block_size - len(data) % self.block_size
|
||||||
return s + padnum * chr(padnum).encode()
|
return data + padnum * chr(padnum).encode()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _unpad(s):
|
def _unpad(data):
|
||||||
return s[: -ord(s[len(s) - 1 :])]
|
return data[: -ord(data[len(data) - 1 :])]
|
||||||
|
|
||||||
|
|
||||||
class MessageDispatcher(ContextualLogger):
|
class MessageDispatcher(ContextualLogger):
|
||||||
@@ -213,6 +213,7 @@ class MessageDispatcher(ContextualLogger):
|
|||||||
|
|
||||||
def __init__(self, dev_id, listener):
|
def __init__(self, dev_id, listener):
|
||||||
"""Initialize a new MessageBuffer."""
|
"""Initialize a new MessageBuffer."""
|
||||||
|
super().__init__()
|
||||||
self.buffer = b""
|
self.buffer = b""
|
||||||
self.listeners = {}
|
self.listeners = {}
|
||||||
self.listener = listener
|
self.listener = listener
|
||||||
@@ -311,7 +312,7 @@ class TuyaListener(ABC):
|
|||||||
"""Device updated status."""
|
"""Device updated status."""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def disconnected(self, exc):
|
def disconnected(self):
|
||||||
"""Device disconnected."""
|
"""Device disconnected."""
|
||||||
|
|
||||||
|
|
||||||
@@ -321,7 +322,7 @@ class EmptyListener(TuyaListener):
|
|||||||
def status_updated(self, status):
|
def status_updated(self, status):
|
||||||
"""Device updated status."""
|
"""Device updated status."""
|
||||||
|
|
||||||
def disconnected(self, exc):
|
def disconnected(self):
|
||||||
"""Device disconnected."""
|
"""Device disconnected."""
|
||||||
|
|
||||||
|
|
||||||
@@ -340,6 +341,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
|
|||||||
Attributes:
|
Attributes:
|
||||||
port (int): The port to connect to.
|
port (int): The port to connect to.
|
||||||
"""
|
"""
|
||||||
|
super().__init__()
|
||||||
self.loop = asyncio.get_running_loop()
|
self.loop = asyncio.get_running_loop()
|
||||||
self.set_logger(_LOGGER, dev_id)
|
self.set_logger(_LOGGER, dev_id)
|
||||||
self.id = dev_id
|
self.id = dev_id
|
||||||
@@ -381,7 +383,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
|
|||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
self.debug("Stopped heartbeat loop")
|
self.debug("Stopped heartbeat loop")
|
||||||
raise
|
raise
|
||||||
except Exception as ex:
|
except Exception as ex: # pylint: disable=broad-except
|
||||||
self.exception("Heartbeat failed (%s), disconnecting", ex)
|
self.exception("Heartbeat failed (%s), disconnecting", ex)
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -404,7 +406,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
|
|||||||
listener = self.listener and self.listener()
|
listener = self.listener and self.listener()
|
||||||
if listener is not None:
|
if listener is not None:
|
||||||
listener.disconnected()
|
listener.disconnected()
|
||||||
except Exception:
|
except Exception: # pylint: disable=broad-except
|
||||||
self.exception("Failed to call disconnected callback")
|
self.exception("Failed to call disconnected callback")
|
||||||
|
|
||||||
async def close(self):
|
async def close(self):
|
||||||
@@ -503,8 +505,8 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
|
|||||||
self.add_dps_to_request(range(*dps_range))
|
self.add_dps_to_request(range(*dps_range))
|
||||||
try:
|
try:
|
||||||
data = await self.status()
|
data = await self.status()
|
||||||
except Exception as e:
|
except Exception as ex:
|
||||||
self.exception("Failed to get status: %s", e)
|
self.exception("Failed to get status: %s", ex)
|
||||||
raise
|
raise
|
||||||
if "dps" in data:
|
if "dps" in data:
|
||||||
self.dps_cache.update(data["dps"])
|
self.dps_cache.update(data["dps"])
|
||||||
@@ -525,7 +527,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
|
|||||||
if not payload:
|
if not payload:
|
||||||
payload = "{}"
|
payload = "{}"
|
||||||
elif payload.startswith(b"{"):
|
elif payload.startswith(b"{"):
|
||||||
payload = payload
|
pass
|
||||||
elif payload.startswith(PROTOCOL_VERSION_BYTES_31):
|
elif payload.startswith(PROTOCOL_VERSION_BYTES_31):
|
||||||
payload = payload[len(PROTOCOL_VERSION_BYTES_31) :] # remove version header
|
payload = payload[len(PROTOCOL_VERSION_BYTES_31) :] # remove version header
|
||||||
# remove (what I'm guessing, but not confirmed is) 16-bytes of MD5
|
# remove (what I'm guessing, but not confirmed is) 16-bytes of MD5
|
||||||
@@ -591,7 +593,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
|
|||||||
payload = PROTOCOL_33_HEADER + payload
|
payload = PROTOCOL_33_HEADER + payload
|
||||||
elif command == SET:
|
elif command == SET:
|
||||||
payload = self.cipher.encrypt(payload)
|
payload = self.cipher.encrypt(payload)
|
||||||
preMd5String = (
|
to_hash = (
|
||||||
b"data="
|
b"data="
|
||||||
+ payload
|
+ payload
|
||||||
+ b"||lpv="
|
+ b"||lpv="
|
||||||
@@ -599,9 +601,9 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
|
|||||||
+ b"||"
|
+ b"||"
|
||||||
+ self.local_key
|
+ self.local_key
|
||||||
)
|
)
|
||||||
m = md5()
|
hasher = md5()
|
||||||
m.update(preMd5String)
|
hasher.update(to_hash)
|
||||||
hexdigest = m.hexdigest()
|
hexdigest = hasher.hexdigest()
|
||||||
payload = (
|
payload = (
|
||||||
PROTOCOL_VERSION_BYTES_31
|
PROTOCOL_VERSION_BYTES_31
|
||||||
+ hexdigest[8:][:16].encode("latin1")
|
+ hexdigest[8:][:16].encode("latin1")
|
||||||
|
@@ -1,3 +1,82 @@
|
|||||||
[tool.black]
|
[tool.black]
|
||||||
target-version = ["py37", "py38"]
|
target-version = ["py37", "py38"]
|
||||||
include = 'custom_components/localtuya/.*\.py'
|
include = 'custom_components/localtuya/.*\.py'
|
||||||
|
|
||||||
|
# pylint config stolen from Home Assistant
|
||||||
|
# Use a conservative default here; 2 should speed up most setups and not hurt
|
||||||
|
# any too bad. Override on command line as appropriate.
|
||||||
|
# Disabled for now: https://github.com/PyCQA/pylint/issues/3584
|
||||||
|
#jobs = 2
|
||||||
|
load-plugins = [
|
||||||
|
"pylint_strict_informational",
|
||||||
|
]
|
||||||
|
persistent = false
|
||||||
|
extension-pkg-whitelist = [
|
||||||
|
"ciso8601",
|
||||||
|
"cv2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.pylint.BASIC]
|
||||||
|
good-names = [
|
||||||
|
"_",
|
||||||
|
"ev",
|
||||||
|
"ex",
|
||||||
|
"fp",
|
||||||
|
"i",
|
||||||
|
"id",
|
||||||
|
"j",
|
||||||
|
"k",
|
||||||
|
"Run",
|
||||||
|
"T",
|
||||||
|
"hs",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.pylint."MESSAGES CONTROL"]
|
||||||
|
# Reasons disabled:
|
||||||
|
# format - handled by black
|
||||||
|
# locally-disabled - it spams too much
|
||||||
|
# duplicate-code - unavoidable
|
||||||
|
# cyclic-import - doesn't test if both import on load
|
||||||
|
# abstract-class-little-used - prevents from setting right foundation
|
||||||
|
# unused-argument - generic callbacks and setup methods create a lot of warnings
|
||||||
|
# too-many-* - are not enforced for the sake of readability
|
||||||
|
# too-few-* - same as too-many-*
|
||||||
|
# abstract-method - with intro of async there are always methods missing
|
||||||
|
# inconsistent-return-statements - doesn't handle raise
|
||||||
|
# too-many-ancestors - it's too strict.
|
||||||
|
# wrong-import-order - isort guards this
|
||||||
|
disable = [
|
||||||
|
"format",
|
||||||
|
"abstract-class-little-used",
|
||||||
|
"abstract-method",
|
||||||
|
"cyclic-import",
|
||||||
|
"duplicate-code",
|
||||||
|
"inconsistent-return-statements",
|
||||||
|
"locally-disabled",
|
||||||
|
"not-context-manager",
|
||||||
|
"too-few-public-methods",
|
||||||
|
"too-many-ancestors",
|
||||||
|
"too-many-arguments",
|
||||||
|
"too-many-branches",
|
||||||
|
"too-many-instance-attributes",
|
||||||
|
"too-many-lines",
|
||||||
|
"too-many-locals",
|
||||||
|
"too-many-public-methods",
|
||||||
|
"too-many-return-statements",
|
||||||
|
"too-many-statements",
|
||||||
|
"too-many-boolean-expressions",
|
||||||
|
"unused-argument",
|
||||||
|
"wrong-import-order",
|
||||||
|
]
|
||||||
|
enable = [
|
||||||
|
"use-symbolic-message-instead",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.pylint.REPORTS]
|
||||||
|
score = false
|
||||||
|
|
||||||
|
[tool.pylint.FORMAT]
|
||||||
|
expected-line-ending-format = "LF"
|
||||||
|
|
||||||
|
[tool.pylint.MISCELLANEOUS]
|
||||||
|
notes = "XXX"
|
@@ -3,4 +3,7 @@ codespell==1.17.1
|
|||||||
flake8==3.8.3
|
flake8==3.8.3
|
||||||
mypy==0.782
|
mypy==0.782
|
||||||
pydocstyle==5.1.1
|
pydocstyle==5.1.1
|
||||||
cryptography==2.9.2
|
cryptography==3.2
|
||||||
|
pylint==2.6.0
|
||||||
|
pylint-strict-informational==0.1
|
||||||
|
homeassistant==2020.12.0
|
||||||
|
Reference in New Issue
Block a user