python-library-lan-router 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ .env
9
+ .pytest_cache/
10
+ config.yaml
11
+ logs/
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-library-lan-router
3
+ Version: 0.1.0
4
+ Requires-Python: >=3.10
5
+ Requires-Dist: pydantic>=2.0.0
6
+ Requires-Dist: tplinkrouterc6u>=0.1.0
@@ -0,0 +1,16 @@
1
+ from .device import Device
2
+ from .base import BaseRouter
3
+ from .tplink import TPLinkRouter
4
+ from typing import Literal
5
+
6
+ def create_router(vendor: Literal["tplink"],**kwargs) -> BaseRouter:
7
+ if vendor == "tplink":
8
+ return TPLinkRouter(**kwargs)
9
+ else:
10
+ raise ValueError(f"不支持的厂商: {vendor}")
11
+
12
+ __all__ = [
13
+ "Device",
14
+ "BaseRouter",
15
+ "TPLinkRouter",
16
+ ]
@@ -0,0 +1,24 @@
1
+ from pydantic import BaseModel
2
+ from abc import ABC, abstractmethod
3
+ from .device import Device
4
+
5
+ class BaseRouter(BaseModel, ABC):
6
+ hostname: str
7
+ """路由器主机名"""
8
+ username: str
9
+ """路由器用户名"""
10
+ password: str
11
+ """路由器密码"""
12
+
13
+ @abstractmethod
14
+ def scan(self) -> list[Device]:
15
+ """扫描设备"""
16
+ pass
17
+
18
+ def login(self):
19
+ """登录"""
20
+ pass
21
+
22
+ def logout(self):
23
+ """登出"""
24
+ pass
@@ -0,0 +1,11 @@
1
+ from pydantic import BaseModel
2
+
3
+ class Device(BaseModel):
4
+ name: str
5
+ """设备名称"""
6
+ ip: str
7
+ """设备IP地址"""
8
+ mac: str
9
+ """设备MAC地址"""
10
+ type: str
11
+ """设备类型"""
@@ -0,0 +1,33 @@
1
+ from .base import BaseRouter
2
+ from .device import Device
3
+ from tplinkrouterc6u import TPLinkXDRClient
4
+
5
+ class TPLinkRouter(BaseRouter):
6
+ _router: TPLinkXDRClient = None
7
+
8
+ def model_post_init(self,ctx):
9
+ self._router = TPLinkXDRClient(self.hostname, self.username, self.password)
10
+
11
+ def login(self):
12
+ self._router.authorize()
13
+
14
+ def logout(self):
15
+ self._router.logout()
16
+
17
+ def scan(self) -> list[Device]:
18
+ devices = []
19
+ status = self._router.get_status()
20
+
21
+ for data in status.devices:
22
+ type = getattr(data.type, "value", str(data.type))
23
+ name = data.hostname or ""
24
+ ip = self._show(data.ipaddr)
25
+ mac = self._show(data.macaddr)
26
+ device = Device(name=name, ip=ip, mac=mac, type=type)
27
+ devices.append(device)
28
+
29
+ return devices
30
+
31
+ def _show(self, v: str | None) -> str:
32
+ """显示空值"""
33
+ return "-" if v is None or v == "" else v
@@ -0,0 +1,15 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "python-library-lan-router"
7
+ version = "0.1.0"
8
+ requires-python = ">=3.10"
9
+ dependencies = [
10
+ "tplinkrouterc6u>=0.1.0",
11
+ "pydantic>=2.0.0",
12
+ ]
13
+
14
+ [tool.hatch.build.targets.wheel]
15
+ packages = ["lan_router"]
@@ -0,0 +1,2 @@
1
+ pydantic-settings>=2.0.0
2
+ PyYAML>=6.0.0
@@ -0,0 +1,46 @@
1
+ from pydantic_settings import BaseSettings, SettingsConfigDict
2
+ from pathlib import Path
3
+ import yaml
4
+ from typing import Literal
5
+
6
+ def find_env_file() -> Path | None:
7
+ """从当前文件所在目录开始,逐层向上查找 .env"""
8
+ start = Path(__file__).resolve().parent
9
+ for parent in [start, *start.parents]:
10
+ env_file = parent / ".env"
11
+ if env_file.is_file():
12
+ return env_file
13
+ return None
14
+
15
+ class Settings(BaseSettings):
16
+ model_config = SettingsConfigDict(
17
+ env_file=find_env_file(),
18
+ env_file_encoding="utf-8",
19
+ )
20
+
21
+ router_vendor: Literal["tplink"]
22
+ """路由器厂商"""
23
+ router_hostname: str
24
+ """路由器主机名"""
25
+ router_username: str
26
+ """路由器用户名"""
27
+ router_password: str
28
+ """路由器密码"""
29
+
30
+ settings = Settings()
31
+
32
+ def load_settings(input_yaml:str|None):
33
+ """加载配置文件"""
34
+ global settings
35
+
36
+ if input_yaml:
37
+ with open(input_yaml) as f:
38
+ local_overrides = yaml.safe_load(f)
39
+ for key, value in local_overrides.items():
40
+ if hasattr(settings, key):
41
+ setattr(settings, key, value)
42
+
43
+ __all__ = [
44
+ "settings",
45
+ "load_settings",
46
+ ]
@@ -0,0 +1,11 @@
1
+ @echo off
2
+ cd /d %~dp0
3
+
4
+ if not exist .venv (
5
+ python -m venv .venv
6
+ )
7
+
8
+ call .venv\Scripts\activate.bat
9
+ python -m pip install -e .
10
+ pip install -r requirements.txt
11
+ python -m unittest discover -s tests -p "test_*.py"
@@ -0,0 +1,25 @@
1
+ """需配置 .env 与真实路由器,在包根目录执行: python tests/router_scan_demo.py"""
2
+
3
+ from lan_router import create_router
4
+ from settings import settings
5
+
6
+ if __name__ == "__main__":
7
+ router = create_router(
8
+ settings.router_vendor,
9
+ hostname=settings.router_hostname,
10
+ username=settings.router_username,
11
+ password=settings.router_password,
12
+ )
13
+
14
+ router.login()
15
+
16
+ devices = router.scan()
17
+ for i, device in enumerate(devices):
18
+ print(f"[{i}] {device.name}")
19
+ print(f" 连接类型 : {device.type}")
20
+ print(f" 主机名 : {device.name}")
21
+ print(f" IP : {device.ip}")
22
+ print(f" MAC : {device.mac}")
23
+ print("-" * 90)
24
+
25
+ router.logout()
@@ -0,0 +1,19 @@
1
+ import unittest
2
+
3
+ from lan_router import Device, create_router
4
+
5
+
6
+ class LanRouterTests(unittest.TestCase):
7
+ def test_create_router_rejects_unknown_vendor(self) -> None:
8
+ with self.assertRaises(ValueError) as ctx:
9
+ create_router("unknown") # type: ignore[arg-type]
10
+ self.assertIn("不支持", str(ctx.exception))
11
+
12
+ def test_device_model(self) -> None:
13
+ d = Device(name="n", ip="192.168.0.1", mac="00:00:00:00:00:01", type="wifi")
14
+ self.assertEqual(d.name, "n")
15
+ self.assertEqual(d.ip, "192.168.0.1")
16
+
17
+
18
+ if __name__ == "__main__":
19
+ unittest.main()