eero-api 1.2.4__py3-none-any.whl
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.
- eero/__init__.py +25 -0
- eero/__main__.py +6 -0
- eero/api/__init__.py +137 -0
- eero/api/ac_compat.py +49 -0
- eero/api/activity.py +182 -0
- eero/api/auth.py +481 -0
- eero/api/auth_storage.py +301 -0
- eero/api/backup.py +205 -0
- eero/api/base.py +239 -0
- eero/api/blacklist.py +116 -0
- eero/api/burst_reporters.py +88 -0
- eero/api/devices.py +304 -0
- eero/api/diagnostics.py +76 -0
- eero/api/dns.py +251 -0
- eero/api/eeros.py +413 -0
- eero/api/forwards.py +147 -0
- eero/api/insights.py +76 -0
- eero/api/networks.py +469 -0
- eero/api/ouicheck.py +77 -0
- eero/api/password.py +77 -0
- eero/api/profiles.py +361 -0
- eero/api/reservations.py +151 -0
- eero/api/routing.py +77 -0
- eero/api/schedule.py +264 -0
- eero/api/security.py +314 -0
- eero/api/settings.py +77 -0
- eero/api/sqm.py +241 -0
- eero/api/support.py +79 -0
- eero/api/thread.py +77 -0
- eero/api/transfer.py +82 -0
- eero/api/updates.py +76 -0
- eero/client.py +1806 -0
- eero/const.py +52 -0
- eero/exceptions.py +76 -0
- eero/models/__init__.py +40 -0
- eero/models/account.py +35 -0
- eero/models/activity.py +153 -0
- eero/models/device.py +275 -0
- eero/models/diagnostics.py +54 -0
- eero/models/eero.py +239 -0
- eero/models/network.py +165 -0
- eero/models/profile.py +278 -0
- eero/py.typed +0 -0
- eero_api-1.2.4.dist-info/METADATA +108 -0
- eero_api-1.2.4.dist-info/RECORD +48 -0
- eero_api-1.2.4.dist-info/WHEEL +5 -0
- eero_api-1.2.4.dist-info/licenses/LICENSE +21 -0
- eero_api-1.2.4.dist-info/top_level.txt +1 -0
eero/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Eero API - Async Python client for Eero mesh WiFi networks."""
|
|
2
|
+
|
|
3
|
+
from .api import EeroAPI
|
|
4
|
+
from .client import EeroClient
|
|
5
|
+
from .exceptions import (
|
|
6
|
+
EeroAPIException,
|
|
7
|
+
EeroAuthenticationException,
|
|
8
|
+
EeroException,
|
|
9
|
+
EeroNetworkException,
|
|
10
|
+
EeroRateLimitException,
|
|
11
|
+
EeroTimeoutException,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"EeroAPI",
|
|
16
|
+
"EeroClient",
|
|
17
|
+
"EeroException",
|
|
18
|
+
"EeroAPIException",
|
|
19
|
+
"EeroAuthenticationException",
|
|
20
|
+
"EeroNetworkException",
|
|
21
|
+
"EeroRateLimitException",
|
|
22
|
+
"EeroTimeoutException",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
__version__ = "1.2.4"
|
eero/__main__.py
ADDED
eero/api/__init__.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""API module for Eero."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from aiohttp import ClientSession
|
|
6
|
+
|
|
7
|
+
from .ac_compat import ACCompatAPI
|
|
8
|
+
from .activity import ActivityAPI
|
|
9
|
+
from .auth import AuthAPI
|
|
10
|
+
from .backup import BackupAPI
|
|
11
|
+
from .blacklist import BlacklistAPI
|
|
12
|
+
from .burst_reporters import BurstReportersAPI
|
|
13
|
+
from .devices import DevicesAPI
|
|
14
|
+
from .diagnostics import DiagnosticsAPI
|
|
15
|
+
from .dns import DnsAPI
|
|
16
|
+
from .eeros import EerosAPI
|
|
17
|
+
from .forwards import ForwardsAPI
|
|
18
|
+
from .insights import InsightsAPI
|
|
19
|
+
from .networks import NetworksAPI
|
|
20
|
+
from .ouicheck import OUICheckAPI
|
|
21
|
+
from .password import PasswordAPI
|
|
22
|
+
from .profiles import ProfilesAPI
|
|
23
|
+
from .reservations import ReservationsAPI
|
|
24
|
+
from .routing import RoutingAPI
|
|
25
|
+
from .schedule import ScheduleAPI
|
|
26
|
+
from .security import SecurityAPI
|
|
27
|
+
from .settings import SettingsAPI
|
|
28
|
+
from .sqm import SqmAPI
|
|
29
|
+
from .support import SupportAPI
|
|
30
|
+
from .thread import ThreadAPI
|
|
31
|
+
from .transfer import TransferAPI
|
|
32
|
+
from .updates import UpdatesAPI
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class EeroAPI:
|
|
36
|
+
"""API client for interacting with the Eero API."""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
session: Optional[ClientSession] = None,
|
|
41
|
+
cookie_file: Optional[str] = None,
|
|
42
|
+
use_keyring: bool = True,
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Initialize the EeroAPI.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
session: Optional aiohttp ClientSession to use for requests
|
|
48
|
+
cookie_file: Optional path to a file for storing authentication cookies
|
|
49
|
+
use_keyring: Whether to use keyring for secure token storage
|
|
50
|
+
"""
|
|
51
|
+
self.auth = AuthAPI(session, cookie_file, use_keyring)
|
|
52
|
+
self.activity = ActivityAPI(self.auth)
|
|
53
|
+
self.backup = BackupAPI(self.auth)
|
|
54
|
+
self.dns = DnsAPI(self.auth)
|
|
55
|
+
self.networks = NetworksAPI(self.auth)
|
|
56
|
+
self.devices = DevicesAPI(self.auth)
|
|
57
|
+
self.eeros = EerosAPI(self.auth)
|
|
58
|
+
self.profiles = ProfilesAPI(self.auth)
|
|
59
|
+
self.schedule = ScheduleAPI(self.auth)
|
|
60
|
+
self.security = SecurityAPI(self.auth)
|
|
61
|
+
self.sqm = SqmAPI(self.auth)
|
|
62
|
+
self.diagnostics = DiagnosticsAPI(self.auth)
|
|
63
|
+
self.settings = SettingsAPI(self.auth)
|
|
64
|
+
self.updates = UpdatesAPI(self.auth)
|
|
65
|
+
self.insights = InsightsAPI(self.auth)
|
|
66
|
+
self.routing = RoutingAPI(self.auth)
|
|
67
|
+
self.thread = ThreadAPI(self.auth)
|
|
68
|
+
self.support = SupportAPI(self.auth)
|
|
69
|
+
self.blacklist = BlacklistAPI(self.auth)
|
|
70
|
+
self.reservations = ReservationsAPI(self.auth)
|
|
71
|
+
self.forwards = ForwardsAPI(self.auth)
|
|
72
|
+
self.transfer = TransferAPI(self.auth)
|
|
73
|
+
self.burst_reporters = BurstReportersAPI(self.auth)
|
|
74
|
+
self.ac_compat = ACCompatAPI(self.auth)
|
|
75
|
+
self.ouicheck = OUICheckAPI(self.auth)
|
|
76
|
+
self.password = PasswordAPI(self.auth)
|
|
77
|
+
|
|
78
|
+
async def __aenter__(self) -> "EeroAPI":
|
|
79
|
+
"""Enter async context manager."""
|
|
80
|
+
await self.auth.__aenter__()
|
|
81
|
+
return self
|
|
82
|
+
|
|
83
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
84
|
+
"""Exit async context manager."""
|
|
85
|
+
await self.auth.__aexit__(exc_type, exc_val, exc_tb)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def is_authenticated(self) -> bool:
|
|
89
|
+
"""Check if the client is authenticated."""
|
|
90
|
+
return self.auth.is_authenticated
|
|
91
|
+
|
|
92
|
+
async def login(self, user_identifier: str) -> bool:
|
|
93
|
+
"""Start the login process by requesting a verification code.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
user_identifier: Email address or phone number for the Eero account
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
True if login request was successful
|
|
100
|
+
"""
|
|
101
|
+
return await self.auth.login(user_identifier)
|
|
102
|
+
|
|
103
|
+
async def verify(self, verification_code: str) -> bool:
|
|
104
|
+
"""Verify login with the code sent to the user.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
verification_code: The verification code sent to the user
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
True if verification was successful
|
|
111
|
+
"""
|
|
112
|
+
return await self.auth.verify(verification_code)
|
|
113
|
+
|
|
114
|
+
async def logout(self) -> bool:
|
|
115
|
+
"""Log out from the Eero API.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
True if logout was successful
|
|
119
|
+
"""
|
|
120
|
+
return await self.auth.logout()
|
|
121
|
+
|
|
122
|
+
def set_preferred_network(self, network_id: str) -> None:
|
|
123
|
+
"""Set the preferred network ID to use for requests.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
network_id: ID of the network to use
|
|
127
|
+
"""
|
|
128
|
+
self.auth.preferred_network_id = network_id
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def preferred_network_id(self) -> Optional[str]:
|
|
132
|
+
"""Get the preferred network ID.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Preferred network ID or None
|
|
136
|
+
"""
|
|
137
|
+
return self.auth.preferred_network_id
|
eero/api/ac_compat.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""AC Compatibility API for Eero."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Dict
|
|
5
|
+
|
|
6
|
+
from ..const import API_ENDPOINT
|
|
7
|
+
from ..exceptions import EeroAuthenticationException
|
|
8
|
+
from .auth import AuthAPI
|
|
9
|
+
from .base import AuthenticatedAPI
|
|
10
|
+
|
|
11
|
+
_LOGGER = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ACCompatAPI(AuthenticatedAPI):
|
|
15
|
+
"""AC Compatibility API for Eero."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, auth_api: AuthAPI) -> None:
|
|
18
|
+
"""Initialize the ACCompatAPI.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
auth_api: Authentication API instance
|
|
22
|
+
"""
|
|
23
|
+
super().__init__(auth_api, API_ENDPOINT)
|
|
24
|
+
|
|
25
|
+
async def get_ac_compat(self, network_id: str) -> Dict[str, Any]:
|
|
26
|
+
"""Get AC compatibility information.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
network_id: ID of the network to get AC compatibility info for
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
AC compatibility data
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
EeroAuthenticationException: If not authenticated
|
|
36
|
+
EeroAPIException: If the API returns an error
|
|
37
|
+
"""
|
|
38
|
+
auth_token = await self._auth_api.get_auth_token()
|
|
39
|
+
if not auth_token:
|
|
40
|
+
raise EeroAuthenticationException("Not authenticated")
|
|
41
|
+
|
|
42
|
+
_LOGGER.debug(f"Getting AC compatibility for network {network_id}")
|
|
43
|
+
|
|
44
|
+
response = await self.get(
|
|
45
|
+
f"networks/{network_id}/ac_compat",
|
|
46
|
+
auth_token=auth_token,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return response.get("data", {})
|
eero/api/activity.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Activity API for Eero (Eero Plus feature)."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Dict, List
|
|
5
|
+
|
|
6
|
+
from ..const import API_ENDPOINT
|
|
7
|
+
from ..exceptions import EeroAuthenticationException
|
|
8
|
+
from .auth import AuthAPI
|
|
9
|
+
from .base import AuthenticatedAPI
|
|
10
|
+
|
|
11
|
+
_LOGGER = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ActivityAPI(AuthenticatedAPI):
|
|
15
|
+
"""Activity API for Eero.
|
|
16
|
+
|
|
17
|
+
Note: Activity data requires an active Eero Plus/Eero Secure subscription.
|
|
18
|
+
API calls may return empty data or errors for non-premium accounts.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, auth_api: AuthAPI) -> None:
|
|
22
|
+
"""Initialize the ActivityAPI.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
auth_api: Authentication API instance
|
|
26
|
+
"""
|
|
27
|
+
super().__init__(auth_api, API_ENDPOINT)
|
|
28
|
+
|
|
29
|
+
async def get_activity(self, network_id: str) -> Dict[str, Any]:
|
|
30
|
+
"""Get network activity summary.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
network_id: ID of the network to get activity from
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Activity data including total usage, top clients, etc.
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
EeroAuthenticationException: If not authenticated
|
|
40
|
+
EeroAPIException: If the API returns an error (may occur for non-premium)
|
|
41
|
+
"""
|
|
42
|
+
auth_token = await self._auth_api.get_auth_token()
|
|
43
|
+
if not auth_token:
|
|
44
|
+
raise EeroAuthenticationException("Not authenticated")
|
|
45
|
+
|
|
46
|
+
_LOGGER.debug("Getting activity for network %s", network_id)
|
|
47
|
+
|
|
48
|
+
response = await self.get(
|
|
49
|
+
f"networks/{network_id}/activity",
|
|
50
|
+
auth_token=auth_token,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return response.get("data", {})
|
|
54
|
+
|
|
55
|
+
async def get_activity_clients(self, network_id: str) -> List[Dict[str, Any]]:
|
|
56
|
+
"""Get per-client activity data.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
network_id: ID of the network to get client activity from
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
List of clients with their activity data
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
EeroAuthenticationException: If not authenticated
|
|
66
|
+
EeroAPIException: If the API returns an error
|
|
67
|
+
"""
|
|
68
|
+
auth_token = await self._auth_api.get_auth_token()
|
|
69
|
+
if not auth_token:
|
|
70
|
+
raise EeroAuthenticationException("Not authenticated")
|
|
71
|
+
|
|
72
|
+
_LOGGER.debug("Getting client activity for network %s", network_id)
|
|
73
|
+
|
|
74
|
+
response = await self.get(
|
|
75
|
+
f"networks/{network_id}/activity/clients",
|
|
76
|
+
auth_token=auth_token,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Handle different response formats
|
|
80
|
+
data = response.get("data", [])
|
|
81
|
+
if isinstance(data, list):
|
|
82
|
+
return data
|
|
83
|
+
elif isinstance(data, dict) and "data" in data:
|
|
84
|
+
return data.get("data", [])
|
|
85
|
+
elif isinstance(data, dict) and "clients" in data:
|
|
86
|
+
return data.get("clients", [])
|
|
87
|
+
return []
|
|
88
|
+
|
|
89
|
+
async def get_activity_for_device(self, network_id: str, device_id: str) -> Dict[str, Any]:
|
|
90
|
+
"""Get activity data for a specific device.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
network_id: ID of the network
|
|
94
|
+
device_id: ID of the device to get activity for
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Activity data for the device
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
EeroAuthenticationException: If not authenticated
|
|
101
|
+
EeroAPIException: If the API returns an error
|
|
102
|
+
"""
|
|
103
|
+
auth_token = await self._auth_api.get_auth_token()
|
|
104
|
+
if not auth_token:
|
|
105
|
+
raise EeroAuthenticationException("Not authenticated")
|
|
106
|
+
|
|
107
|
+
_LOGGER.debug("Getting activity for device %s in network %s", device_id, network_id)
|
|
108
|
+
|
|
109
|
+
response = await self.get(
|
|
110
|
+
f"networks/{network_id}/activity/{device_id}",
|
|
111
|
+
auth_token=auth_token,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return response.get("data", {})
|
|
115
|
+
|
|
116
|
+
async def get_activity_history(
|
|
117
|
+
self,
|
|
118
|
+
network_id: str,
|
|
119
|
+
period: str = "day",
|
|
120
|
+
) -> Dict[str, Any]:
|
|
121
|
+
"""Get historical activity data.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
network_id: ID of the network
|
|
125
|
+
period: Time period - "hour", "day", "week", or "month"
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Historical activity data
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
EeroAuthenticationException: If not authenticated
|
|
132
|
+
EeroAPIException: If the API returns an error
|
|
133
|
+
"""
|
|
134
|
+
auth_token = await self._auth_api.get_auth_token()
|
|
135
|
+
if not auth_token:
|
|
136
|
+
raise EeroAuthenticationException("Not authenticated")
|
|
137
|
+
|
|
138
|
+
valid_periods = ["hour", "day", "week", "month"]
|
|
139
|
+
if period not in valid_periods:
|
|
140
|
+
_LOGGER.warning("Invalid period '%s', defaulting to 'day'", period)
|
|
141
|
+
period = "day"
|
|
142
|
+
|
|
143
|
+
_LOGGER.debug("Getting activity history for network %s (period: %s)", network_id, period)
|
|
144
|
+
|
|
145
|
+
response = await self.get(
|
|
146
|
+
f"networks/{network_id}/activity/history",
|
|
147
|
+
auth_token=auth_token,
|
|
148
|
+
params={"period": period},
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return response.get("data", {})
|
|
152
|
+
|
|
153
|
+
async def get_activity_categories(self, network_id: str) -> List[Dict[str, Any]]:
|
|
154
|
+
"""Get activity data grouped by category.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
network_id: ID of the network
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Activity data grouped by category (streaming, gaming, social, etc.)
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
EeroAuthenticationException: If not authenticated
|
|
164
|
+
EeroAPIException: If the API returns an error
|
|
165
|
+
"""
|
|
166
|
+
auth_token = await self._auth_api.get_auth_token()
|
|
167
|
+
if not auth_token:
|
|
168
|
+
raise EeroAuthenticationException("Not authenticated")
|
|
169
|
+
|
|
170
|
+
_LOGGER.debug("Getting activity categories for network %s", network_id)
|
|
171
|
+
|
|
172
|
+
response = await self.get(
|
|
173
|
+
f"networks/{network_id}/activity/categories",
|
|
174
|
+
auth_token=auth_token,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
data = response.get("data", [])
|
|
178
|
+
if isinstance(data, list):
|
|
179
|
+
return data
|
|
180
|
+
elif isinstance(data, dict) and "categories" in data:
|
|
181
|
+
return data.get("categories", [])
|
|
182
|
+
return []
|