Fix pydocstyle issues and enable in tox

This commit is contained in:
Pierre Ståhl
2020-09-22 12:30:16 +02:00
parent ec7652b193
commit a18c69f8eb
10 changed files with 59 additions and 39 deletions

View File

@@ -140,7 +140,7 @@ def get_entity_config(config_entry, dps_id):
class TuyaDevice: class TuyaDevice:
"""Cache wrapper for pytuya.TuyaInterface""" """Cache wrapper for pytuya.TuyaInterface."""
def __init__(self, interface, friendly_name): def __init__(self, interface, friendly_name):
"""Initialize the cache.""" """Initialize the cache."""
@@ -226,6 +226,7 @@ class LocalTuyaEntity(Entity):
@property @property
def device_info(self): def device_info(self):
"""Return device information for the device registry."""
return { return {
"identifiers": { "identifiers": {
# Serial numbers are unique identifiers within a specific domain # Serial numbers are unique identifiers within a specific domain

View File

@@ -78,7 +78,7 @@ def flow_schema(dps):
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Setup a Tuya cover based on a config entry.""" """Set up a Tuya cover based on a config entry."""
tuyainterface, entities_to_setup = prepare_setup_entities( tuyainterface, entities_to_setup = prepare_setup_entities(
config_entry, DOMAIN config_entry, DOMAIN
) )
@@ -103,7 +103,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class LocaltuyaCover(LocalTuyaEntity, CoverEntity): class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
"""Tuya cover devices.""" """Tuya cover device."""
def __init__( def __init__(
self, self,
@@ -112,6 +112,7 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
switchid, switchid,
**kwargs, **kwargs,
): ):
"""Initialize a new LocaltuyaCover."""
#_LOGGER.info("running def __init__ of LocaltuyaCover(CoverEntity) with self=%s device=%s name=%s friendly_name=%s icon=%s switchid=%s open_cmd=%s close_cmd=%s stop_cmd=%s", self, device, name, friendly_name, icon, switchid, open_cmd, close_cmd, stop_cmd) #_LOGGER.info("running def __init__ of LocaltuyaCover(CoverEntity) with self=%s device=%s name=%s friendly_name=%s icon=%s switchid=%s open_cmd=%s close_cmd=%s stop_cmd=%s", self, device, name, friendly_name, icon, switchid, open_cmd, close_cmd, stop_cmd)
super().__init__(device, config_entry, switchid, **kwargs) super().__init__(device, config_entry, switchid, **kwargs)
self._state = None self._state = None
@@ -137,6 +138,7 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
@property @property
def current_cover_position(self): def current_cover_position(self):
"""Return current cover position in percent."""
# self.update() # self.update()
# state = self._state # state = self._state
# _LOGGER.info("curr_pos() : %i", self._position) # _LOGGER.info("curr_pos() : %i", self._position)
@@ -145,6 +147,7 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
@property @property
def is_opening(self): def is_opening(self):
"""Return if cover is opening."""
# self.update() # self.update()
state = self._state state = self._state
# print('is_opening() : state [{}]'.format(state)) # print('is_opening() : state [{}]'.format(state))
@@ -154,6 +157,7 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
@property @property
def is_closing(self): def is_closing(self):
"""Return if cover is closing."""
# self.update() # self.update()
state = self._state state = self._state
# print('is_closing() : state [{}]'.format(state)) # print('is_closing() : state [{}]'.format(state))
@@ -177,7 +181,6 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
def set_cover_position(self, **kwargs): def set_cover_position(self, **kwargs):
# _LOGGER.info("running set_cover_position from cover") # _LOGGER.info("running set_cover_position from cover")
"""Move the cover to a specific position.""" """Move the cover to a specific position."""
newpos = float(kwargs["position"]) newpos = float(kwargs["position"])
# _LOGGER.info("Set new pos: %f", newpos) # _LOGGER.info("Set new pos: %f", newpos)
@@ -212,5 +215,5 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
self._device.set_dps(self._config[CONF_STOP_CMD], self._dps_id) self._device.set_dps(self._config[CONF_STOP_CMD], self._dps_id)
def status_updated(self): def status_updated(self):
"""Device status was updated. """ """Device status was updated."""
self._state = self.dps(self._dps_id) self._state = self.dps(self._dps_id)

View File

@@ -72,6 +72,7 @@ async def discover(timeout, loop):
def main(): def main():
"""Run discovery and print result."""
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
res = loop.run_until_complete(discover(5, loop)) res = loop.run_until_complete(discover(5, loop))
print(res) print(res)

View File

@@ -49,7 +49,7 @@ def flow_schema(dps):
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Setup a Tuya fan based on a config entry.""" """Set up a Tuya fan based on a config entry."""
tuyainterface, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN) tuyainterface, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN)
if not entities_to_setup: if not entities_to_setup:
return return

View File

@@ -59,7 +59,7 @@ def flow_schema(dps):
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Setup a Tuya light based on a config entry.""" """Set up a Tuya light based on a config entry."""
tuyainterface, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN) tuyainterface, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN)
if not entities_to_setup: if not entities_to_setup:
return return

View File

@@ -1,22 +1,23 @@
# PyTuya Module # PyTuya Module
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Python module to interface with Tuya WiFi smart devices Python module to interface with Tuya WiFi smart devices.
Mostly derived from Shenzhen Xenon ESP8266MOD WiFi smart devices
E.g. https://wikidevi.com/wiki/Xenon_SM-PW701U
Author: clach04 Mostly derived from Shenzhen Xenon ESP8266MOD WiFi smart devices
Maintained by: rospogrigio E.g. https://wikidevi.com/wiki/Xenon_SM-PW701U
For more information see https://github.com/clach04/python-tuya Author: clach04
Maintained by: rospogrigio
Classes For more information see https://github.com/clach04/python-tuya
Classes
TuyaInterface(dev_id, address, local_key=None) TuyaInterface(dev_id, address, local_key=None)
dev_id (str): Device ID e.g. 01234567891234567890 dev_id (str): Device ID e.g. 01234567891234567890
address (str): Device Network IP Address e.g. 10.0.1.99 address (str): Device Network IP Address e.g. 10.0.1.99
local_key (str, optional): The encryption key. Defaults to None. local_key (str, optional): The encryption key. Defaults to None.
Functions Functions
json = status() # returns json payload json = status() # returns json payload
set_version(version) # 3.1 [default] or 3.3 set_version(version) # 3.1 [default] or 3.3
detect_available_dps() # returns a list of available dps provided by the device detect_available_dps() # returns a list of available dps provided by the device
@@ -25,7 +26,7 @@
set_timer(num_secs): set_timer(num_secs):
Credits Credits
* TuyaAPI https://github.com/codetheweb/tuyapi by codetheweb and blackrozes * TuyaAPI https://github.com/codetheweb/tuyapi by codetheweb and blackrozes
For protocol reverse engineering For protocol reverse engineering
* PyTuya https://github.com/clach04/python-tuya by clach04 * PyTuya https://github.com/clach04/python-tuya by clach04
@@ -80,12 +81,16 @@ PROTOCOL_VERSION_BYTES_33 = b"3.3"
IS_PY2 = sys.version_info[0] == 2 IS_PY2 = sys.version_info[0] == 2
class AESCipher(object): class AESCipher:
"""Cipher module for Tuya communication."""
def __init__(self, key): def __init__(self, key):
"""Initialize a new AESCipher."""
self.bs = 16 self.bs = 16
self.key = key self.key = key
def encrypt(self, raw, use_base64=True): def encrypt(self, raw, use_base64=True):
"""Encrypt data to be sent to device."""
if Crypto: if Crypto:
raw = self._pad(raw) raw = self._pad(raw)
cipher = AES.new(self.key, mode=AES.MODE_ECB) cipher = AES.new(self.key, mode=AES.MODE_ECB)
@@ -104,6 +109,7 @@ class AESCipher(object):
return crypted_text return crypted_text
def decrypt(self, enc, use_base64=True): def decrypt(self, enc, use_base64=True):
"""Decrypt data from device."""
if use_base64: if use_base64:
enc = base64.b64decode(enc) enc = base64.b64decode(enc)
# print('enc (%d) %r' % (len(enc), enc)) # print('enc (%d) %r' % (len(enc), enc))
@@ -134,6 +140,7 @@ class AESCipher(object):
def bin2hex(x, pretty=False): def bin2hex(x, pretty=False):
"""Convert binary data to hex string."""
if pretty: if pretty:
space = " " space = " "
else: else:
@@ -146,6 +153,7 @@ def bin2hex(x, pretty=False):
def hex2bin(x): def hex2bin(x):
"""Convert hex string to binary."""
if IS_PY2: if IS_PY2:
return x.decode("hex") return x.decode("hex")
else: else:
@@ -171,12 +179,14 @@ payload_dict = {
} }
class TuyaInterface(object): class TuyaInterface:
"""Represent a Tuya device."""
def __init__( def __init__(
self, dev_id, address, local_key, protocol_version, connection_timeout=10 self, dev_id, address, local_key, protocol_version, connection_timeout=10
): ):
""" """
Represents a Tuya device. Initialize a new TuyaInterface.
Args: Args:
dev_id (str): The device id. dev_id (str): The device id.
@@ -186,7 +196,6 @@ class TuyaInterface(object):
Attributes: Attributes:
port (int): The port to connect to. port (int): The port to connect to.
""" """
self.id = dev_id self.id = dev_id
self.address = address self.address = address
self.local_key = local_key.encode("latin1") self.local_key = local_key.encode("latin1")
@@ -198,6 +207,7 @@ class TuyaInterface(object):
self.port = 6668 # default - do not expect caller to pass in self.port = 6668 # default - do not expect caller to pass in
def __repr__(self): def __repr__(self):
"""Return internal string representation of object."""
return "%r" % ((self.id, self.address),) # FIXME can do better than this return "%r" % ((self.id, self.address),) # FIXME can do better than this
def _send_receive(self, payload): def _send_receive(self, payload):
@@ -239,6 +249,7 @@ class TuyaInterface(object):
return data return data
def detect_available_dps(self): def detect_available_dps(self):
"""Return which datapoints are supported by the device."""
# type_0d devices need a sort of bruteforce querying in order to detect the list of available dps # type_0d devices need a sort of bruteforce querying in order to detect the list of available dps
# experience shows that the dps available are usually in the ranges [1-25] and [100-110] # experience shows that the dps available are usually in the ranges [1-25] and [100-110]
# need to split the bruteforcing in different steps due to request payload limitation (max. length = 255) # need to split the bruteforcing in different steps due to request payload limitation (max. length = 255)
@@ -288,6 +299,7 @@ class TuyaInterface(object):
return detected_dps return detected_dps
def add_dps_to_request(self, dps_index): def add_dps_to_request(self, dps_index):
"""Add a datapoint (DP) to be included in requests."""
if isinstance(dps_index, int): if isinstance(dps_index, int):
self.dps_to_request[str(dps_index)] = None self.dps_to_request[str(dps_index)] = None
else: else:
@@ -393,6 +405,7 @@ class TuyaInterface(object):
return buffer return buffer
def status(self): def status(self):
"""Return device status."""
log.debug("status() entry (dev_type is %s)", self.dev_type) log.debug("status() entry (dev_type is %s)", self.dev_type)
# open device, send request, then close connection # open device, send request, then close connection
payload = self.generate_payload("status") payload = self.generate_payload("status")

View File

@@ -56,7 +56,7 @@ def flow_schema(dps):
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Setup a Tuya sensor based on a config entry.""" """Set up a Tuya sensor based on a config entry."""
tuyainterface, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN) tuyainterface, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN)
if not entities_to_setup: if not entities_to_setup:
return return

View File

@@ -94,7 +94,7 @@ def flow_schema(dps):
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Setup a Tuya switch based on a config entry.""" """Set up a Tuya switch based on a config entry."""
tuyainterface, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN) tuyainterface, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN)
if not entities_to_setup: if not entities_to_setup:
return return
@@ -152,6 +152,7 @@ class LocaltuyaSwitch(LocalTuyaEntity, SwitchEntity):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return device state attributes."""
attrs = {} attrs = {}
if self._config.get(CONF_CURRENT, "-1") != "-1": if self._config.get(CONF_CURRENT, "-1") != "-1":
attrs[ATTR_CURRENT] = self.dps(self._config[CONF_CURRENT]) attrs[ATTR_CURRENT] = self.dps(self._config[CONF_CURRENT])

View File

@@ -1,2 +1,3 @@
black==20.8b1 black==20.8b1
mypy==0.782 mypy==0.782
pydocstyle==5.1.1

View File

@@ -30,7 +30,7 @@ commands =
#codespell -q 4 -L {[tox]cs_exclude_words} --skip="*.pyc,*.pyi,*~" custom_components #codespell -q 4 -L {[tox]cs_exclude_words} --skip="*.pyc,*.pyi,*~" custom_components
#flake8 cu #flake8 cu
black --fast --check . black --fast --check .
#pydocstyle -v custom_components pydocstyle -v custom_components
[testenv:typing] [testenv:typing]
whitelist_externals = whitelist_externals =