Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ jobs:
run: |
python -m pip install --upgrade pip
python -m pip install flake8 pytest pytest-asyncio
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
pip install -r requirements.txt
pip install -e .
- name: Wait for mock API to be ready
run: |
for i in {1..30}; do
Expand Down
41 changes: 41 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[build-system]
requires = ["setuptools>=64"]
build-backend = "setuptools.build_meta"

[project]
name = "tplink-cloud-api"
version = "5.0.0"
description = "Python library for controlling TP-Link Kasa smart home devices via the TP-Link Cloud API"
readme = "README.md"
license = "GPL-3.0-only"
requires-python = ">=3.10"
authors = [
{ name = "Dev Piekstra", email = "piekstra.dev@gmail.com" },
]
classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Operating System :: OS Independent",
]
dependencies = [
"requests>=2,<3",
"aiohttp>=3.9,<4",
]

[project.urls]
Homepage = "https://github.com/piekstra/tplink-cloud-api"
Repository = "https://github.com/piekstra/tplink-cloud-api"
Issues = "https://github.com/piekstra/tplink-cloud-api/issues"

[tool.setuptools.packages.find]
exclude = ["tests*"]

[tool.setuptools.package-data]
tplinkcloud = ["certs/*.pem"]

[tool.pytest.ini_options]
asyncio_mode = "auto"
2 changes: 0 additions & 2 deletions pytest.ini

This file was deleted.

35 changes: 0 additions & 35 deletions setup.py

This file was deleted.

24 changes: 1 addition & 23 deletions tplinkcloud/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,4 @@
'TPLinkDeviceOfflineError',
'TPLinkMFARequiredError',
'TPLinkTokenExpiredError',
]

# Windows OS-specific HACK to silence exception thrown on event loop being closed
# as part of the asyncio library's proactor
# Hack sourced from an issue on the aiohttp library:
# https://github.com/aio-libs/aiohttp/issues/4324#issuecomment-733884349
# This assumes you have the asyncio library installed
import platform
if platform.system() == 'Windows':
from functools import wraps
from asyncio.proactor_events import _ProactorBasePipeTransport

def silence_event_loop_closed(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except RuntimeError as e:
if str(e) != 'Event loop is closed':
raise
return wrapper

_ProactorBasePipeTransport.__del__ = silence_event_loop_closed(_ProactorBasePipeTransport.__del__)
]
55 changes: 22 additions & 33 deletions tplinkcloud/device_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from .client import TPLinkApi
from .exceptions import TPLinkTokenExpiredError

# Supported devices
from .hs100 import HS100
from .hs103 import HS103
from .hs105 import HS105
Expand All @@ -22,6 +21,23 @@
from .ep40 import EP40
from .device import TPLinkDevice

DEVICE_MODEL_MAP: dict[str, type[TPLinkDevice]] = {
'HS100': HS100,
'HS103': HS103,
'HS105': HS105,
'HS110': HS110,
'HS200': HS200,
'HS300': HS300,
'KL420L5': KL420L5,
'KL430': KL430,
'KP115': KP115,
'KP125': KP125,
'KP200': KP200,
'KP303': KP303,
'KP400': KP400,
'EP40': EP40,
}


class TPLinkDeviceManager:

Expand Down Expand Up @@ -96,46 +112,19 @@ async def get_devices(self):
return devices

def _construct_device(self, device_info):
# Construct the TPLinkDeviceInfo here for convenience
tplink_device_info = TPLinkDeviceInfo(device_info)
# In case the app_server_url is different, we construct a client each time
client = TPLinkDeviceClient(
tplink_device_info.app_server_url,
self._auth_token,
verbose=self._verbose,
term_id=self._term_id
)
model = tplink_device_info.device_model
if model.startswith('HS100'):
return HS100(client, tplink_device_info.device_id, tplink_device_info)
elif model.startswith('HS103'):
return HS103(client, tplink_device_info.device_id, tplink_device_info)
elif model.startswith('HS105'):
return HS105(client, tplink_device_info.device_id, tplink_device_info)
elif model.startswith('HS110'):
return HS110(client, tplink_device_info.device_id, tplink_device_info)
elif model.startswith('HS200'):
return HS200(client, tplink_device_info.device_id, tplink_device_info)
elif model.startswith('HS300'):
return HS300(client, tplink_device_info.device_id, tplink_device_info)
elif model.startswith('KL420L5'):
return KL420L5(client, tplink_device_info.device_id, tplink_device_info)
elif model.startswith('KL430'):
return KL430(client, tplink_device_info.device_id, tplink_device_info)
elif model.startswith('KP115'):
return KP115(client, tplink_device_info.device_id, tplink_device_info)
elif model.startswith('KP125'):
return KP125(client, tplink_device_info.device_id, tplink_device_info)
elif model.startswith('KP200'):
return KP200(client, tplink_device_info.device_id, tplink_device_info)
elif model.startswith('KP303'):
return KP303(client, tplink_device_info.device_id, tplink_device_info)
elif model.startswith('KP400'):
return KP400(client, tplink_device_info.device_id, tplink_device_info)
elif model.startswith('EP40'):
return EP40(client, tplink_device_info.device_id, tplink_device_info)
else:
return TPLinkDevice(client, tplink_device_info.device_id, tplink_device_info)
device_cls = next(
(cls for prefix, cls in DEVICE_MODEL_MAP.items() if model.startswith(prefix)),
TPLinkDevice,
)
return device_cls(client, tplink_device_info.device_id, tplink_device_info)

def login(self, username, password, mfa_callback=None):
result = self._tplink_api.login(
Expand Down