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,37 +1,38 @@
# 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
TuyaInterface(dev_id, address, local_key=None)
dev_id (str): Device ID e.g. 01234567891234567890
address (str): Device Network IP Address e.g. 10.0.1.99
local_key (str, optional): The encryption key. Defaults to None.
Functions Classes
json = status() # returns json payload TuyaInterface(dev_id, address, local_key=None)
set_version(version) # 3.1 [default] or 3.3 dev_id (str): Device ID e.g. 01234567891234567890
detect_available_dps() # returns a list of available dps provided by the device address (str): Device Network IP Address e.g. 10.0.1.99
add_dps_to_request(dps_index) # adds dps_index to the list of dps used by the device (to be queried in the payload) local_key (str, optional): The encryption key. Defaults to None.
set_dps(on, dps_index) # Set value of any dps index.
set_timer(num_secs): Functions
json = status() # returns json payload
set_version(version) # 3.1 [default] or 3.3
detect_available_dps() # returns a list of available dps provided by the device
add_dps_to_request(dps_index) # adds dps_index to the list of dps used by the device (to be queried in the payload)
set_dps(on, dps_index) # Set value of any dps index.
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
The origin of this python module (now abandoned) The origin of this python module (now abandoned)
* LocalTuya https://github.com/rospogrigio/localtuya-homeassistant by rospogrigio * LocalTuya https://github.com/rospogrigio/localtuya-homeassistant by rospogrigio
Updated pytuya to support devices with Device IDs of 22 characters Updated pytuya to support devices with Device IDs of 22 characters
""" """
import base64 import base64
@@ -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 =