diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..17e7f6621 Binary files /dev/null and b/.DS_Store differ diff --git a/kasa/__init__.py b/kasa/__init__.py index fc798fb37..503353685 100755 --- a/kasa/__init__.py +++ b/kasa/__init__.py @@ -13,6 +13,7 @@ """ from importlib_metadata import version # type: ignore +from kasa.auth import Auth from kasa.discover import Discover from kasa.emeterstatus import EmeterStatus from kasa.exceptions import SmartDeviceException @@ -28,6 +29,7 @@ __all__ = [ + "Auth", "Discover", "TPLinkSmartHomeProtocol", "SmartBulb", diff --git a/kasa/auth.py b/kasa/auth.py new file mode 100644 index 000000000..efd431a82 --- /dev/null +++ b/kasa/auth.py @@ -0,0 +1,21 @@ +"""Authentication class for KASA username / passwords.""" +from hashlib import md5 + + +class Auth: + """Authentication for Kasa KLAP authentication.""" + + def __init__(self, user: str = "", password: str = ""): + self.user = user + self.password = password + self.md5user = md5(user.encode()).digest() + self.md5password = md5(password.encode()).digest() + self.md5auth = md5(self.md5user + self.md5password).digest() + + def authenticator(self): + """Return the KLAP authenticator for these credentials.""" + return self.md5auth + + def owner(self): + """Return the MD5 hash of the username in this object.""" + return self.md5user diff --git a/kasa/cli.py b/kasa/cli.py index c23019ecf..4df76a92d 100755 --- a/kasa/cli.py +++ b/kasa/cli.py @@ -6,6 +6,7 @@ import asyncclick as click from kasa import ( + Auth, Discover, SmartBulb, SmartDevice, @@ -44,9 +45,24 @@ @click.option("--plug", default=False, is_flag=True) @click.option("--lightstrip", default=False, is_flag=True) @click.option("--strip", default=False, is_flag=True) +@click.option("--klap", default=False, is_flag=True) +@click.option( + "--user", + default="", + required=False, + help="Username/email address to authenticate to device.", +) +@click.option( + "--password", + default="", + required=False, + help="Password to use to authenticate to device.", +) @click.version_option() @click.pass_context -async def cli(ctx, host, alias, target, debug, bulb, plug, lightstrip, strip): +async def cli( + ctx, host, alias, target, debug, bulb, plug, lightstrip, strip, klap, user, password +): """A tool for controlling TP-Link smart home devices.""" # noqa if debug: logging.basicConfig(level=logging.DEBUG) @@ -65,6 +81,15 @@ async def cli(ctx, host, alias, target, debug, bulb, plug, lightstrip, strip): click.echo(f"No device with name {alias} found") return + if password != "" and user == "": + click.echo("Using a password requires a username") + return + + if klap or user != "": + authentication = Auth(user=user, password=password) + else: + authentication = None + if host is None: click.echo("No host name given, trying discovery..") await ctx.invoke(discover) @@ -72,11 +97,11 @@ async def cli(ctx, host, alias, target, debug, bulb, plug, lightstrip, strip): else: if not bulb and not plug and not strip and not lightstrip: click.echo("No --strip nor --bulb nor --plug given, discovering..") - dev = await Discover.discover_single(host) + dev = await Discover.discover_single(host, authentication) elif bulb: dev = SmartBulb(host) elif plug: - dev = SmartPlug(host) + dev = SmartPlug(host, authentication) elif strip: dev = SmartStrip(host) elif lightstrip: @@ -135,8 +160,18 @@ async def join(dev: SmartDevice, ssid, password, keytype): async def discover(ctx, timeout, discover_only, dump_raw): """Discover devices in the network.""" target = ctx.parent.params["target"] - click.echo(f"Discovering devices on {target} for {timeout} seconds") - found_devs = await Discover.discover(target=target, timeout=timeout) + user = ctx.parent.params["user"] + password = ctx.parent.params["password"] + + if user: + auth = Auth(user=user, password=password) + else: + auth = None + + click.echo(f"Discovering devices for {timeout} seconds") + found_devs = await Discover.discover( + target=target, timeout=timeout, return_raw=dump_raw, authentication=auth + ) if not discover_only: for ip, dev in found_devs.items(): if dump_raw: diff --git a/kasa/discover.py b/kasa/discover.py index 5269f3d0b..533de2b5d 100755 --- a/kasa/discover.py +++ b/kasa/discover.py @@ -1,10 +1,14 @@ """Discovery module for TP-Link Smart Home devices.""" import asyncio +import binascii +import hashlib import json import logging import socket -from typing import Awaitable, Callable, Dict, Optional, Type, cast +from typing import Awaitable, Callable, Dict, Mapping, Optional, Type, Union, cast +from kasa.auth import Auth +from kasa.klapprotocol import TPLinkKLAP from kasa.protocol import TPLinkSmartHomeProtocol from kasa.smartbulb import SmartBulb from kasa.smartdevice import SmartDevice, SmartDeviceException @@ -35,13 +39,17 @@ def __init__( target: str = "255.255.255.255", discovery_packets: int = 3, interface: Optional[str] = None, + authentication: Optional[Auth] = None, ): self.transport = None self.discovery_packets = discovery_packets self.interface = interface self.on_discovered = on_discovered self.target = (target, Discover.DISCOVERY_PORT) + self.new_target = (target, Discover.NEW_DISCOVERY_PORT) self.discovered_devices = {} + self.authentication = authentication + self.emptyUser = hashlib.md5().digest() def connection_made(self, transport) -> None: """Set socket options for broadcasting.""" @@ -61,8 +69,10 @@ def do_discover(self) -> None: req = json.dumps(Discover.DISCOVERY_QUERY) _LOGGER.debug("[DISCOVERY] %s >> %s", self.target, Discover.DISCOVERY_QUERY) encrypted_req = TPLinkSmartHomeProtocol.encrypt(req) + new_req = binascii.unhexlify("020000010000000000000000463cb5d3") for i in range(self.discovery_packets): self.transport.sendto(encrypted_req[4:], self.target) # type: ignore + self.transport.sendto(new_req, self.new_target) # type: ignore def datagram_received(self, data, addr) -> None: """Handle discovery responses.""" @@ -70,17 +80,38 @@ def datagram_received(self, data, addr) -> None: if ip in self.discovered_devices: return - info = json.loads(TPLinkSmartHomeProtocol.decrypt(data)) - _LOGGER.debug("[DISCOVERY] %s << %s", ip, info) - - try: + if port == 9999: + info = json.loads(TPLinkSmartHomeProtocol.decrypt(data)) device_class = Discover._get_device_class(info) - except SmartDeviceException as ex: - _LOGGER.debug("Unable to find device type from %s: %s", info, ex) - return + device = device_class(ip) + else: + info = json.loads(data[16:]) + device_class = Discover._get_new_device_class(info) + owner = Discover._get_new_owner(info) + if owner is not None: + owner_bin = bytes.fromhex(owner) + + _LOGGER.debug( + "[DISCOVERY] Device owner is %s, empty owner is %s", + owner_bin, + self.emptyUser, + ) + if owner is None or owner == "" or owner_bin == self.emptyUser: + _LOGGER.debug("[DISCOVERY] Device %s has no owner", ip) + device = device_class(ip, Auth()) + elif ( + self.authentication is not None + and owner_bin == self.authentication.owner() + ): + _LOGGER.debug("[DISCOVERY] Device %s has authenticated owner", ip) + device = device_class(ip, self.authentication) + else: + _LOGGER.debug("[DISCOVERY] Found %s with unknown owner %s", ip, owner) + return + + _LOGGER.debug("[DISCOVERY] %s << %s", ip, info) - device = device_class(ip) - device.update_from_discover_info(info) + asyncio.ensure_future(device.update()) self.discovered_devices[ip] = device @@ -132,6 +163,8 @@ class Discover: DISCOVERY_PORT = 9999 + NEW_DISCOVERY_PORT = 20002 + DISCOVERY_QUERY = { "system": {"get_sysinfo": None}, } @@ -144,7 +177,8 @@ async def discover( timeout=5, discovery_packets=3, interface=None, - ) -> DeviceDict: + authentication=None, + ) -> Mapping[str, Union[SmartDevice, Dict]]: """Discover supported devices. Sends discovery message to 255.255.255.255:9999 in order @@ -171,6 +205,7 @@ async def discover( on_discovered=on_discovered, discovery_packets=discovery_packets, interface=interface, + authentication=authentication, ), local_addr=("0.0.0.0", 0), ) @@ -187,20 +222,29 @@ async def discover( return protocol.discovered_devices @staticmethod - async def discover_single(host: str) -> SmartDevice: + async def discover_single(host: str, authentication=None) -> SmartDevice: """Discover a single device by the given IP address. :param host: Hostname of device to query :rtype: SmartDevice :return: Object for querying/controlling found device. """ - protocol = TPLinkSmartHomeProtocol(host) + if authentication is None: + protocol = TPLinkSmartHomeProtocol(host) + else: + protocol = TPLinkKLAP(host, authentication) + # protocol = TPLinkSmartHomeProtocol(host) info = await protocol.query(Discover.DISCOVERY_QUERY) device_class = Discover._get_device_class(info) - dev = device_class(host) - await dev.update() + if device_class is not None: + if authentication is None: + dev = device_class(host) + else: + dev = device_class(host, authentication) + await dev.update() + return dev return dev @@ -231,3 +275,43 @@ def _get_device_class(info: dict) -> Type[SmartDevice]: return SmartBulb raise SmartDeviceException("Unknown device type: %s" % type_) + + @staticmethod + def _get_new_device_class(info: dict) -> Type[SmartDevice]: + """Find SmartDevice subclass given new discovery payload.""" + if "result" not in info: + raise SmartDeviceException("No 'result' in discovery response") + + if "device_type" not in info["result"]: + raise SmartDeviceException("No 'device_type' in discovery result") + + dtype = info["result"]["device_type"] + + if dtype == "IOT.SMARTPLUGSWITCH": + return SmartPlug + + raise SmartDeviceException("Unknown device type: %s", dtype) + + @staticmethod + def _get_new_owner(info: dict) -> Optional[str]: + """Find owner given new-style discovery payload.""" + if "result" not in info: + raise SmartDeviceException("No 'result' in discovery response") + + if "owner" not in info["result"]: + return None + + return info["result"]["owner"] + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + loop = asyncio.get_event_loop() + + async def _on_device(dev): + await dev.update() + _LOGGER.info("Got device: %s", dev) + + devices = loop.run_until_complete(Discover.discover(on_discovered=_on_device)) + for ip, dev in devices.items(): + print(f"[{ip}] {dev}") diff --git a/kasa/klapprotocol.py b/kasa/klapprotocol.py new file mode 100755 index 000000000..81edde783 --- /dev/null +++ b/kasa/klapprotocol.py @@ -0,0 +1,155 @@ +"""Implementation of the TP-Link Smart Home Protocol. + +Encryption/Decryption methods based on the works of +Lubomir Stroetmann and Tobias Esser + +https://www.softscheck.com/en/reverse-engineering-tp-link-hs110/ +https://github.com/softScheck/tplink-smartplug/ + +which are licensed under the Apache License, Version 2.0 +http://www.apache.org/licenses/LICENSE-2.0 +""" +import asyncio +import hashlib +import logging +import secrets + +import aiohttp +from Crypto.Cipher import AES +from Crypto.Util import Padding +from yarl import URL + +from .auth import Auth +from .exceptions import SmartDeviceException +from .protocol import TPLinkSmartHomeProtocol + +_LOGGER = logging.getLogger(__name__) + + +class TPLinkKLAP(TPLinkSmartHomeProtocol): + """Implementation of the KLAP encryption protocol. + + KLAP is the name used in device discovery for TP-Link's new encryption + protocol, used by newer firmware versions. + """ + + def __init__(self, host: str, authentication: Auth = Auth()) -> None: + self.jar = aiohttp.CookieJar(unsafe=True, quote_cookie=False) + self.client_challenge = secrets.token_bytes(16) + self.authenticator = authentication.authenticator() + self.handshake_lock = asyncio.Lock() + self.handshake_done = False + + super().__init__(host=host) + + _LOGGER.debug("[KLAP] Created KLAP object for %s", self.host) + + @staticmethod + def _sha256(payload: bytes) -> bytes: + return hashlib.sha256(payload).digest() + + async def _handshake(self, session) -> None: + _LOGGER.debug("[KLAP] Starting handshake with %s", self.host) + + # Handshake 1 has a payload of client_challenge + # and a response of 16 bytes, followed by sha256(clientBytes | authenticator) + + url = f"http://{self.host}/app/handshake1" + resp = await session.post(url, data=self.client_challenge) + _LOGGER.debug("Got response of %d to handshake1", resp.status) + if resp.status != 200: + raise SmartDeviceException( + "Device responded with %d to handshake1" % resp.status + ) + + response = await resp.read() + self.server_challenge = response[0:16] + server_hash = response[16:] + + _LOGGER.debug("Server bytes are: %s", self.server_challenge.hex()) + _LOGGER.debug("Server hash is: %s", server_hash.hex()) + + # Check the response from the device + local_hash = self._sha256(self.client_challenge + self.authenticator) + + if local_hash != server_hash: + _LOGGER.debug( + "Expected %s got %s in handshake1", + local_hash.hex(), + server_hash.hex(), + ) + raise SmartDeviceException("Server response doesn't match our challenge") + else: + _LOGGER.debug("handshake1 hashes match") + + # We need to include only the TP_SESSIONID cookie - aiohttp's cookie handling + # adds a bogus TIMEOUT cookie + cookie = session.cookie_jar.filter_cookies(url).get("TP_SESSIONID") + session.cookie_jar.clear() + session.cookie_jar.update_cookies({"TP_SESSIONID": cookie}, URL(url)) + _LOGGER.debug("Cookie is %s", cookie) + + # Handshake 2 has the following payload: + # sha256(serverBytes | authenticator) + url = f"http://{self.host}/app/handshake2" + payload = self._sha256(self.server_challenge + self.authenticator) + resp = await session.post(url, data=payload) + _LOGGER.debug("Got response of %d to handshake2", resp.status) + if resp.status != 200: + raise SmartDeviceException( + "Device responded with %d to handshake2" % resp.status + ) + + # Done handshaking, now we need to compute the encryption keys + agreed = self.client_challenge + self.server_challenge + self.authenticator + self.encrypt_key = self._sha256(b"lsk" + agreed)[:16] + self.hmac_key = self._sha256(b"ldk" + agreed)[:28] + fulliv = self._sha256(b"iv" + agreed) + self.iv = fulliv[:12] + self.seq = int.from_bytes(fulliv[-4:], "big", signed=True) + self.handshake_done = True + + def _encrypt(self, plaintext: bytes, iv: bytes, seq: int) -> bytes: + cipher = AES.new(self.encrypt_key, AES.MODE_CBC, iv) + ciphertext = cipher.encrypt(Padding.pad(plaintext, AES.block_size)) + signature = self._sha256( + self.hmac_key + seq.to_bytes(4, "big", signed=True) + ciphertext + ) + return signature + ciphertext + + def _decrypt(self, payload: bytes, iv: bytes, seq: int) -> bytes: + cipher = AES.new(self.encrypt_key, AES.MODE_CBC, iv) + # In theory we should verify the hmac here too + return Padding.unpad(cipher.decrypt(payload[32:]), AES.block_size) + + async def _ask(self, request: str) -> str: + + try: + timeout = aiohttp.ClientTimeout(total=self.timeout) + session = aiohttp.ClientSession(cookie_jar=self.jar, timeout=timeout) + + async with self.handshake_lock: + if not self.handshake_done: + await self._handshake(session) + + msg_seq = self.seq + msg_iv = self.iv + msg_seq.to_bytes(4, "big", signed=True) + payload = self._encrypt(request.encode("utf-8"), msg_iv, msg_seq) + + url = f"http://{self.host}/app/request" + resp = await session.post(url, params={"seq": msg_seq}, data=payload) + _LOGGER.debug("Got response of %d to request", resp.status) + + # If we failed with a security error, force a new handshake next time + if resp.status == 403: + self.handshake_done = False + + if resp.status != 200: + raise SmartDeviceException( + "Device responded with %d to request with seq %d" + % (resp.status, msg_seq) + ) + response = await resp.read() + return self._decrypt(response, msg_iv, msg_seq).decode("utf-8") + finally: + await session.close() diff --git a/kasa/smartdevice.py b/kasa/smartdevice.py index fabf26b32..2f45bff9f 100755 --- a/kasa/smartdevice.py +++ b/kasa/smartdevice.py @@ -20,8 +20,10 @@ from enum import Enum, auto from typing import Any, Dict, List, Optional, Set +from .auth import Auth from .emeterstatus import EmeterStatus from .exceptions import SmartDeviceException +from .klapprotocol import TPLinkKLAP from .protocol import TPLinkSmartHomeProtocol _LOGGER = logging.getLogger(__name__) @@ -187,14 +189,18 @@ class SmartDevice: TIME_SERVICE = "time" - def __init__(self, host: str) -> None: + def __init__(self, host: str, authentication: Optional[Auth] = None) -> None: """Create a new SmartDevice instance. :param str host: host name or ip address on which the device listens """ self.host = host - self.protocol = TPLinkSmartHomeProtocol(host) + if authentication is None: + self.protocol = TPLinkSmartHomeProtocol(host) + else: + self.protocol = TPLinkKLAP(host, authentication) + self.emeter_type = "emeter" _LOGGER.debug("Initializing %s of type %s", self.host, type(self)) self._device_type = DeviceType.Unknown diff --git a/kasa/smartplug.py b/kasa/smartplug.py index d23bc9396..018468b37 100644 --- a/kasa/smartplug.py +++ b/kasa/smartplug.py @@ -36,8 +36,8 @@ class SmartPlug(SmartDevice): For more examples, see the :class:`SmartDevice` class. """ - def __init__(self, host: str) -> None: - super().__init__(host) + def __init__(self, host: str, authentication=None) -> None: + super().__init__(host, authentication) self.emeter_type = "emeter" self._device_type = DeviceType.Plug diff --git a/kasa/smartstrip.py b/kasa/smartstrip.py index 71373a7a9..391381bcc 100755 --- a/kasa/smartstrip.py +++ b/kasa/smartstrip.py @@ -205,13 +205,13 @@ async def erase_emeter_stats(self): @requires_update def emeter_this_month(self) -> Optional[float]: """Return this month's energy consumption in kWh.""" - return sum([plug.emeter_this_month for plug in self.children]) + return sum(plug.emeter_this_month for plug in self.children) @property # type: ignore @requires_update def emeter_today(self) -> Optional[float]: """Return this month's energy consumption in kWh.""" - return sum([plug.emeter_today for plug in self.children]) + return sum(plug.emeter_today for plug in self.children) @property # type: ignore @requires_update diff --git a/kasa/tests/test_protocol.py b/kasa/tests/test_protocol.py index 5fe4763d5..1ae0060c4 100644 --- a/kasa/tests/test_protocol.py +++ b/kasa/tests/test_protocol.py @@ -24,7 +24,8 @@ def aio_mock_writer(_, __): conn = mocker.patch("asyncio.open_connection", side_effect=aio_mock_writer) with pytest.raises(SmartDeviceException): - await TPLinkSmartHomeProtocol("127.0.0.1").query({}, retry_count=retry_count) + protocol = TPLinkSmartHomeProtocol("127.0.0.1") + await protocol.query({}, retry_count=retry_count) assert conn.call_count == retry_count + 1 diff --git a/poetry.lock b/poetry.lock index 63bbc2c22..92842824d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,22 @@ +[[package]] +name = "aiohttp" +version = "3.7.2" +description = "Async http client/server framework (asyncio)" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +async-timeout = ">=3.0,<4.0" +attrs = ">=17.3.0" +chardet = ">=2.0,<4.0" +multidict = ">=4.5,<7.0" +typing-extensions = ">=3.6.5" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["aiodns", "brotlipy", "cchardet"] + [[package]] name = "alabaster" version = "0.7.12" @@ -24,6 +43,30 @@ doc = ["sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=6.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"] trio = ["trio (>=0.16)"] +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "async-generator" +version = "1.10" +description = "Async generators and context managers for Python 3.5+" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "async-timeout" +version = "3.0.1" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.5.3" + [[package]] name = "asyncclick" version = "7.1.2.3" @@ -103,6 +146,17 @@ category = "dev" optional = false python-versions = ">=3.6.1" +[[package]] +name = "chardet" +version = "3.0.4" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +unicode_backport = ["unicodedata2"] + [[package]] name = "charset-normalizer" version = "2.0.6" @@ -263,6 +317,15 @@ category = "dev" optional = false python-versions = ">=3.5" +[[package]] +name = "multidict" +version = "5.0.2" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.5" + + [[package]] name = "nodeenv" version = "1.6.0" @@ -333,6 +396,14 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pycryptodome" +version = "3.9.9" +description = "Cryptographic library for Python" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [[package]] name = "pygments" version = "2.10.0" @@ -729,6 +800,19 @@ jupyter = ["nbformat", "nbconvert", "jupyter-client", "ipython", "ipykernel"] optional = ["pygments", "colorama", "nbformat", "nbconvert", "jupyter-client", "ipython", "ipykernel"] tests = ["codecov", "scikit-build", "cmake", "ninja", "pybind11", "pytest", "pytest", "pytest-cov", "pytest", "pytest", "pytest-cov", "typing", "nbformat", "nbconvert", "jupyter-client", "ipython", "ipykernel", "pytest", "pytest-cov"] +[[package]] +name = "yarl" +version = "1.6.3" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} + [[package]] name = "zipp" version = "3.5.0" @@ -750,6 +834,41 @@ python-versions = "^3.7" content-hash = "e388fa366e9423e60697bfa77c37151094ca0367eb3ed441d61bb8cc7f055675" [metadata.files] +aiohttp = [ + {file = "aiohttp-3.7.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0989ff15834a4503056d103077ec3652f9ea5699835e1ceaee46b91cf59830bf"}, + {file = "aiohttp-3.7.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:8fbeeb2296bb9fe16071a674eadade7391be785ae0049610e64b60ead6abcdd7"}, + {file = "aiohttp-3.7.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:48104c883099c0e614c5c38f98c1d174a2c68f52f58b2a6e5a07b59df78262ab"}, + {file = "aiohttp-3.7.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:c9a415f4f2764ab6c7d63ee6b86f02a46b4df9bc11b0de7ffef206908b7bf0b4"}, + {file = "aiohttp-3.7.2-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:7e26712871ebaf55497a60f55483dc5e74326d1fb0bfceab86ebaeaa3a266733"}, + {file = "aiohttp-3.7.2-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:8319a55de469d5af3517dfe1f6a77f248f6668c5a552396635ef900f058882ef"}, + {file = "aiohttp-3.7.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2aea79734ac5ceeac1ec22b4af4efb4efd6a5ca3d73d77ec74ed782cf318f238"}, + {file = "aiohttp-3.7.2-cp36-cp36m-win_amd64.whl", hash = "sha256:be9fa3fe94fc95e9bf84e84117a577c892906dd3cb0a95a7ae21e12a84777567"}, + {file = "aiohttp-3.7.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04dcbf6af1868048a9b4754b1684c669252aa2419aa67266efbcaaead42ced7"}, + {file = "aiohttp-3.7.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2e886611b100c8c93b753b457e645c5e4b8008ec443434d2a480e5a2bb3e6514"}, + {file = "aiohttp-3.7.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cdbb65c361ff790c424365a83a496fc8dd1983689a5fb7c6852a9a3ff1710c61"}, + {file = "aiohttp-3.7.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:8a8addd41320637c1445fea0bae1fd9fe4888acc2cd79217ee33e5d1c83cfe01"}, + {file = "aiohttp-3.7.2-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:b822bf7b764283b5015e3c49b7bb93f37fc03545f4abe26383771c6b1c813436"}, + {file = "aiohttp-3.7.2-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:ad5c3559e3cd64f746df43fa498038c91aa14f5d7615941ea5b106e435f3b892"}, + {file = "aiohttp-3.7.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:835bd35e14e4f36414e47c195e6645449a0a1c3fd5eeae4b7f22cb4c5e4f503a"}, + {file = "aiohttp-3.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:11e087c316e933f1f52f3d4a09ce13f15ad966fc43df47f44ca4e8067b6a2e0d"}, + {file = "aiohttp-3.7.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:f8c583c31c6e790dc003d9d574e3ed2c5b337947722965096c4d684e4f183570"}, + {file = "aiohttp-3.7.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b84cef790cb93cec82a468b7d2447bf16e3056d2237b652e80f57d653b61da88"}, + {file = "aiohttp-3.7.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:4afd8002d9238e5e93acf1a8baa38b3ddf1f7f0ebef174374131ff0c6c2d7973"}, + {file = "aiohttp-3.7.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:a1f1cc11c9856bfa7f1ca55002c39070bde2a97ce48ef631468e99e2ac8e3fe6"}, + {file = "aiohttp-3.7.2-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:7f1aeb72f14b9254296cdefa029c00d3c4550a26e1059084f2ee10d22086c2d0"}, + {file = "aiohttp-3.7.2-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:67f8564c534d75c1d613186939cee45a124d7d37e7aece83b17d18af665b0d7a"}, + {file = "aiohttp-3.7.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:184ead67248274f0e20b0cd6bb5f25209b2fad56e5373101cc0137c32c825c87"}, + {file = "aiohttp-3.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:6e0d1231a626d07b23f6fe904caa44efb249da4222d8a16ab039fb2348722292"}, + {file = "aiohttp-3.7.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:476b1f8216e59a3c2ffb71b8d7e1da60304da19f6000d422bacc371abb0fc43d"}, + {file = "aiohttp-3.7.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:89c1aa729953b5ac6ca3c82dcbd83e7cdecfa5cf9792c78c154a642e6e29303d"}, + {file = "aiohttp-3.7.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c53f1d2bd48f5f407b534732f5b3c6b800a58e70b53808637848d8a9ee127fe7"}, + {file = "aiohttp-3.7.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:06efdb01ab71ec20786b592d510d1d354fbe0b2e4449ee47067b9ca65d45a006"}, + {file = "aiohttp-3.7.2-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:027be45c4b37e21be81d07ae5242361d73eebad1562c033f80032f955f34df82"}, + {file = "aiohttp-3.7.2-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:1c36b7ef47cfbc150314c2204cd73613d96d6d0982d41c7679b7cdcf43c0e979"}, + {file = "aiohttp-3.7.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:c588a0f824dc7158be9eec1ff465d1c868ad69a4dc518cd098cc11e4f7da09d9"}, + {file = "aiohttp-3.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:547b196a7177511da4f475fc81d0bb88a51a8d535c7444bbf2338b6dc82cb996"}, + {file = "aiohttp-3.7.2.tar.gz", hash = "sha256:c6da1af59841e6d43255d386a2c4bfb59c0a3b262bdb24325cc969d211be6070"}, +] alabaster = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, @@ -758,6 +877,18 @@ anyio = [ {file = "anyio-3.3.1-py3-none-any.whl", hash = "sha256:d7c604dd491eca70e19c78664d685d5e4337612d574419d503e76f5d7d1590bd"}, {file = "anyio-3.3.1.tar.gz", hash = "sha256:85913b4e2fec030e8c72a8f9f98092eeb9e25847a6e00d567751b77e34f856fe"}, ] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +async-generator = [ + {file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"}, + {file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"}, +] +async-timeout = [ + {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, + {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, +] asyncclick = [ {file = "asyncclick-7.1.2.3.tar.gz", hash = "sha256:c26962b9957abe7ae09c058afbfea199dedea1b54343c1cc2ae1a6a291fab333"}, ] @@ -789,6 +920,10 @@ charset-normalizer = [ {file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"}, {file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"}, ] +charset-normalizer = [ + {file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"}, + {file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"}, +] codecov = [ {file = "codecov-2.1.12-py2.py3-none-any.whl", hash = "sha256:585dc217dc3d8185198ceb402f85d5cb5dbfa0c5f350a5abcdf9e347776a5b47"}, {file = "codecov-2.1.12-py3.8.egg", hash = "sha256:782a8e5352f22593cbc5427a35320b99490eb24d9dcfa2155fd99d2b75cfb635"}, @@ -931,6 +1066,45 @@ more-itertools = [ {file = "more-itertools-8.10.0.tar.gz", hash = "sha256:1debcabeb1df793814859d64a81ad7cb10504c24349368ccf214c664c474f41f"}, {file = "more_itertools-8.10.0-py3-none-any.whl", hash = "sha256:56ddac45541718ba332db05f464bebfb0768110111affd27f66e0051f276fa43"}, ] +multidict = [ + {file = "multidict-5.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b82400ef848bbac6b9035a105ac6acaa1fb3eea0d164e35bbb21619b88e49fed"}, + {file = "multidict-5.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b98af08d7bb37d3456a22f689819ea793e8d6961b9629322d7728c4039071641"}, + {file = "multidict-5.0.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d4a6fb98e9e9be3f7d70fd3e852369c00a027bd5ed0f3e8ade3821bcad257408"}, + {file = "multidict-5.0.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:2ab9cad4c5ef5c41e1123ed1f89f555aabefb9391d4e01fd6182de970b7267ed"}, + {file = "multidict-5.0.2-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:62abab8088704121297d39c8f47156cb8fab1da731f513e59ba73946b22cf3d0"}, + {file = "multidict-5.0.2-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:59182e975b8c197d0146a003d0f0d5dc5487ce4899502061d8df585b0f51fba2"}, + {file = "multidict-5.0.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:76cbdb22f48de64811f9ce1dd4dee09665f84f32d6a26de249a50c1e90e244e0"}, + {file = "multidict-5.0.2-cp36-cp36m-win32.whl", hash = "sha256:653b2bbb0bbf282c37279dd04f429947ac92713049e1efc615f68d4e64b1dbc2"}, + {file = "multidict-5.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:c58e53e1c73109fdf4b759db9f2939325f510a8a5215135330fe6755921e4886"}, + {file = "multidict-5.0.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:359ea00e1b53ceef282232308da9d9a3f60d645868a97f64df19485c7f9ef628"}, + {file = "multidict-5.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b561e76c9e21402d9a446cdae13398f9942388b9bff529f32dfa46220af54d00"}, + {file = "multidict-5.0.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:9380b3f2b00b23a4106ba9dd022df3e6e2e84e1788acdbdd27603b621b3288df"}, + {file = "multidict-5.0.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:1cd102057b09223b919f9447c669cf2efabeefb42a42ae6233f25ffd7ee31a79"}, + {file = "multidict-5.0.2-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:d99da85d6890267292065e654a329e1d2f483a5d2485e347383800e616a8c0b1"}, + {file = "multidict-5.0.2-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:f612e8ef8408391a4a3366e3508bab8ef97b063b4918a317cb6e6de4415f01af"}, + {file = "multidict-5.0.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:6128d2c0956fd60e39ec7d1c8f79426f0c915d36458df59ddd1f0cff0340305f"}, + {file = "multidict-5.0.2-cp37-cp37m-win32.whl", hash = "sha256:9ed9b280f7778ad6f71826b38a73c2fdca4077817c64bc1102fdada58e75c03c"}, + {file = "multidict-5.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f65a2442c113afde52fb09f9a6276bbc31da71add99dc76c3adf6083234e07c6"}, + {file = "multidict-5.0.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:2576e30bbec004e863d87216bc34abe24962cc2e964613241a1c01c7681092ab"}, + {file = "multidict-5.0.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:20cc9b2dd31761990abff7d0e63cd14dbfca4ebb52a77afc917b603473951a38"}, + {file = "multidict-5.0.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:6566749cd78cb37cbf8e8171b5cd2cbfc03c99f0891de12255cf17a11c07b1a3"}, + {file = "multidict-5.0.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:6168839491a533fa75f3f5d48acbb829475e6c7d9fa5c6e245153b5f79b986a3"}, + {file = "multidict-5.0.2-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:e58db0e0d60029915f7fc95a8683fa815e204f2e1990f1fb46a7778d57ca8c35"}, + {file = "multidict-5.0.2-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:8fa4549f341a057feec4c3139056ba73e17ed03a506469f447797a51f85081b5"}, + {file = "multidict-5.0.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:06f39f0ddc308dab4e5fa282d145f90cd38d7ed75390fc83335636909a9ec191"}, + {file = "multidict-5.0.2-cp38-cp38-win32.whl", hash = "sha256:8efcf070d60fd497db771429b1c769a3783e3a0dd96c78c027e676990176adc5"}, + {file = "multidict-5.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:060d68ae3e674c913ec41a464916f12c4d7ff17a3a9ebbf37ba7f2c681c2b33e"}, + {file = "multidict-5.0.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:4a3f19da871befa53b48dd81ee48542f519beffa13090dc135fffc18d8fe36db"}, + {file = "multidict-5.0.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:af271c2540d1cd2a137bef8d95a8052230aa1cda26dd3b2c73d858d89993d518"}, + {file = "multidict-5.0.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:3e61cc244fd30bd9fdfae13bdd0c5ec65da51a86575ff1191255cae677045ffe"}, + {file = "multidict-5.0.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:4df708ef412fd9b59b7e6c77857e64c1f6b4c0116b751cb399384ec9a28baa66"}, + {file = "multidict-5.0.2-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:cbabfc12b401d074298bfda099c58dfa5348415ae2e4ec841290627cb7cb6b2e"}, + {file = "multidict-5.0.2-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:43c7a87d8c31913311a1ab24b138254a0ee89142983b327a2c2eab7a7d10fea9"}, + {file = "multidict-5.0.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fa0503947a99a1be94f799fac89d67a5e20c333e78ddae16e8534b151cdc588a"}, + {file = "multidict-5.0.2-cp39-cp39-win32.whl", hash = "sha256:17847fede1aafdb7e74e01bb34ab47a1a1ea726e8184c623c45d7e428d2d5d34"}, + {file = "multidict-5.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a7b8b5bd16376c8ac2977748bd978a200326af5145d8d0e7f799e2b355d425b6"}, + {file = "multidict-5.0.2.tar.gz", hash = "sha256:e5bf89fe57f702a046c7ec718fe330ed50efd4bcf74722940db2eb0919cddb1c"}, +] nodeenv = [ {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, @@ -955,6 +1129,43 @@ py = [ {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, ] +pycryptodome = [ + {file = "pycryptodome-3.9.9-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:5598dc6c9dbfe882904e54584322893eff185b98960bbe2cdaaa20e8a437b6e5"}, + {file = "pycryptodome-3.9.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1cfdb92dca388e27e732caa72a1cc624520fe93752a665c3b6cd8f1a91b34916"}, + {file = "pycryptodome-3.9.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5f19e6ef750f677d924d9c7141f54bade3cd56695bbfd8a9ef15d0378557dfe4"}, + {file = "pycryptodome-3.9.9-cp27-cp27m-win32.whl", hash = "sha256:a3d8a9efa213be8232c59cdc6b65600276508e375e0a119d710826248fd18d37"}, + {file = "pycryptodome-3.9.9-cp27-cp27m-win_amd64.whl", hash = "sha256:50826b49fbca348a61529693b0031cdb782c39060fb9dca5ac5dff858159dc5a"}, + {file = "pycryptodome-3.9.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:19cb674df6c74a14b8b408aa30ba8a89bd1c01e23505100fb45f930fbf0ed0d9"}, + {file = "pycryptodome-3.9.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:28f75e58d02019a7edc7d4135203d2501dfc47256d175c72c9798f9a129a49a7"}, + {file = "pycryptodome-3.9.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:6d3baaf82681cfb1a842f1c8f77beac791ceedd99af911e4f5fabec32bae2259"}, + {file = "pycryptodome-3.9.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:946399d15eccebafc8ce0257fc4caffe383c75e6b0633509bd011e357368306c"}, + {file = "pycryptodome-3.9.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:eb01f9997e4d6a8ec8a1ad1f676ba5a362781ff64e8189fe2985258ba9cb9706"}, + {file = "pycryptodome-3.9.9-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:411745c6dce4eff918906eebcde78771d44795d747e194462abb120d2e537cd9"}, + {file = "pycryptodome-3.9.9-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:8f9f84059039b672a5a705b3c5aa21747867bacc30a72e28bf0d147cc8ef85ed"}, + {file = "pycryptodome-3.9.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:7798e73225a699651888489fbb1dbc565e03a509942a8ce6194bbe6fb582a41f"}, + {file = "pycryptodome-3.9.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:46e96aeb8a9ca8b1edf9b1fd0af4bf6afcf3f1ca7fa35529f5d60b98f3e4e959"}, + {file = "pycryptodome-3.9.9-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:843e5f10ecdf9d307032b8b91afe9da1d6ed5bb89d0bbec5c8dcb4ba44008e11"}, + {file = "pycryptodome-3.9.9-cp36-cp36m-win32.whl", hash = "sha256:b68794fba45bdb367eeb71249c26d23e61167510a1d0c3d6cf0f2f14636e62ee"}, + {file = "pycryptodome-3.9.9-cp36-cp36m-win_amd64.whl", hash = "sha256:60febcf5baf70c566d9d9351c47fbd8321da9a4edf2eff45c4c31c86164ca794"}, + {file = "pycryptodome-3.9.9-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:4ed27951b0a17afd287299e2206a339b5b6d12de9321e1a1575261ef9c4a851b"}, + {file = "pycryptodome-3.9.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9000877383e2189dafd1b2fc68c6c726eca9a3cfb6d68148fbb72ccf651959b6"}, + {file = "pycryptodome-3.9.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:faa682c404c218e8788c3126c9a4b8fbcc54dc245b5b6e8ea5b46f3b63bd0c84"}, + {file = "pycryptodome-3.9.9-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:62c488a21c253dadc9f731a32f0ac61e4e436d81a1ea6f7d1d9146ed4d20d6bd"}, + {file = "pycryptodome-3.9.9-cp37-cp37m-win32.whl", hash = "sha256:834b790bbb6bd18956f625af4004d9c15eed12d5186d8e57851454ae76d52215"}, + {file = "pycryptodome-3.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:70d807d11d508433daf96244ec1c64e55039e8a35931fc5ea9eee94dbe3cb6b5"}, + {file = "pycryptodome-3.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:27397aee992af69d07502126561d851ba3845aa808f0e55c71ad0efa264dd7d4"}, + {file = "pycryptodome-3.9.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d7ec2bd8f57c559dd24e71891c51c25266a8deb66fc5f02cc97c7fb593d1780a"}, + {file = "pycryptodome-3.9.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e15bde67ccb7d4417f627dd16ffe2f5a4c2941ce5278444e884cb26d73ecbc61"}, + {file = "pycryptodome-3.9.9-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:5c3c4865730dfb0263f822b966d6d58429d8b1e560d1ddae37685fd9e7c63161"}, + {file = "pycryptodome-3.9.9-cp38-cp38-win32.whl", hash = "sha256:76b1a34d74bb2c91bce460cdc74d1347592045627a955e9a252554481c17c52f"}, + {file = "pycryptodome-3.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:6e4227849e4231a3f5b35ea5bdedf9a82b3883500e5624f00a19156e9a9ef861"}, + {file = "pycryptodome-3.9.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2a68df525b387201a43b27b879ce8c08948a430e883a756d6c9e3acdaa7d7bd8"}, + {file = "pycryptodome-3.9.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:a4599c0ca0fc027c780c1c45ed996d5bef03e571470b7b1c7171ec1e1a90914c"}, + {file = "pycryptodome-3.9.9-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b4e6b269a8ddaede774e5c3adbef6bf452ee144e6db8a716d23694953348cd86"}, + {file = "pycryptodome-3.9.9-cp39-cp39-win32.whl", hash = "sha256:a199e9ca46fc6e999e5f47fce342af4b56c7de85fae893c69ab6aa17531fb1e1"}, + {file = "pycryptodome-3.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:6e89bb3826e6f84501e8e3b205c22595d0c5492c2f271cbb9ee1c48eb1866645"}, + {file = "pycryptodome-3.9.9.tar.gz", hash = "sha256:910e202a557e1131b1c1b3f17a63914d57aac55cf9fb9b51644962841c3995c4"}, +] pygments = [ {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"}, {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"}, @@ -1097,6 +1308,45 @@ xdoctest = [ {file = "xdoctest-0.15.8-py3-none-any.whl", hash = "sha256:80a57af2f8ca709ab9da111ab3b16ec474f11297b9efcc34709a2c3e56ed9ce6"}, {file = "xdoctest-0.15.8.tar.gz", hash = "sha256:ddd128780593161a7398fcfefc49f5f6dfe4c2eb2816942cb53768d43bcab7b9"}, ] +yarl = [ + {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76"}, + {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366"}, + {file = "yarl-1.6.3-cp36-cp36m-win32.whl", hash = "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721"}, + {file = "yarl-1.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643"}, + {file = "yarl-1.6.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f"}, + {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970"}, + {file = "yarl-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e"}, + {file = "yarl-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50"}, + {file = "yarl-1.6.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2"}, + {file = "yarl-1.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2"}, + {file = "yarl-1.6.3-cp38-cp38-win32.whl", hash = "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896"}, + {file = "yarl-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a"}, + {file = "yarl-1.6.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0"}, + {file = "yarl-1.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4"}, + {file = "yarl-1.6.3-cp39-cp39-win32.whl", hash = "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424"}, + {file = "yarl-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6"}, + {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"}, +] zipp = [ {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, diff --git a/pyproject.toml b/pyproject.toml index 0671d41b9..6fca3aef1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "python-kasa" -version = "0.4.0" +version = "0.4.1dev2" description = "Python API for TP-Link Kasa Smarthome devices" license = "GPL-3.0-or-later" authors = ["Your Name "] @@ -24,6 +24,8 @@ sphinx = { version = "^3", optional = true } m2r = { version = "^0", optional = true } sphinx_rtd_theme = { version = "^0", optional = true } sphinxcontrib-programoutput = { version = "^0", optional = true } +aiohttp = "^3.7.2" +pycryptodome = "^3.9.9" [tool.poetry.dev-dependencies] pytest = "^5"