googlewifiapi 0.1.0__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.
- googlewifiapi/__init__.py +5 -0
- googlewifiapi/api.py +57 -0
- googlewifiapi/const.py +7 -0
- googlewifiapi/exception.py +13 -0
- googlewifiapi/status.py +93 -0
- googlewifiapi-0.1.0.dist-info/METADATA +106 -0
- googlewifiapi-0.1.0.dist-info/RECORD +10 -0
- googlewifiapi-0.1.0.dist-info/WHEEL +5 -0
- googlewifiapi-0.1.0.dist-info/licenses/LICENSE.txt +21 -0
- googlewifiapi-0.1.0.dist-info/top_level.txt +1 -0
googlewifiapi/api.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Google Wifi API wrapper."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import aiohttp
|
|
6
|
+
from yarl import URL
|
|
7
|
+
|
|
8
|
+
from .const import DEFAULT_HOST, ENDPOINT
|
|
9
|
+
from .exception import (
|
|
10
|
+
GoogleWifiClientError,
|
|
11
|
+
GoogleWifiDataValidationError,
|
|
12
|
+
GoogleWifiException,
|
|
13
|
+
)
|
|
14
|
+
from .status import GoogleWifiStatus
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GoogleWifiAPI:
|
|
18
|
+
"""Get the latest data and update the states."""
|
|
19
|
+
|
|
20
|
+
raw_data: dict[str, Any] | None = None
|
|
21
|
+
available: bool = True
|
|
22
|
+
_resource: URL
|
|
23
|
+
data: GoogleWifiStatus | None = None
|
|
24
|
+
sess: aiohttp.ClientSession
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self, host: str = DEFAULT_HOST, sess: aiohttp.ClientSession | None = None
|
|
28
|
+
) -> None:
|
|
29
|
+
"""Initialize the API wrapper."""
|
|
30
|
+
self._resource = URL(f"http://{host}{ENDPOINT}")
|
|
31
|
+
self.sess = sess or aiohttp.ClientSession()
|
|
32
|
+
|
|
33
|
+
async def async_update(self) -> GoogleWifiStatus:
|
|
34
|
+
"""Get the latest data from the router."""
|
|
35
|
+
try:
|
|
36
|
+
resp = await self.sess.get(self._resource)
|
|
37
|
+
raw_data = await resp.json()
|
|
38
|
+
except (aiohttp.ClientError, ValueError) as err:
|
|
39
|
+
raise GoogleWifiClientError() from err
|
|
40
|
+
|
|
41
|
+
if not isinstance(raw_data, dict):
|
|
42
|
+
raise GoogleWifiDataValidationError(
|
|
43
|
+
f"The router provided data that could not be validated. "
|
|
44
|
+
f"Raw data: {raw_data}"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
self.raw_data = raw_data
|
|
48
|
+
try:
|
|
49
|
+
self.data = GoogleWifiStatus.from_dict(raw_data)
|
|
50
|
+
return self.data
|
|
51
|
+
except (LookupError, TypeError, ValueError) as err:
|
|
52
|
+
raise GoogleWifiDataValidationError(
|
|
53
|
+
f"The router provided data that could not be validated. "
|
|
54
|
+
f"Raw data: {raw_data}"
|
|
55
|
+
) from err
|
|
56
|
+
except Exception as err:
|
|
57
|
+
raise GoogleWifiException() from err
|
googlewifiapi/const.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Exceptions for the Google Wifi API Wrapper."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class GoogleWifiException(Exception):
|
|
5
|
+
"""Raised when there is a generic exception while wrapping the API."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GoogleWifiDataValidationError(GoogleWifiException):
|
|
9
|
+
"""Raised when the data cannot be validated."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GoogleWifiClientError(GoogleWifiException):
|
|
13
|
+
"""Raised when the connection fails in some way."""
|
googlewifiapi/status.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Google Wifi Status using mashumaro dataclasses."""
|
|
2
|
+
|
|
3
|
+
import ipaddress
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from datetime import datetime, timedelta
|
|
6
|
+
from typing import Literal, Union
|
|
7
|
+
|
|
8
|
+
from mashumaro import DataClassDictMixin
|
|
9
|
+
from mashumaro.config import BaseConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
IPAddress = Union[ipaddress.IPv4Address, ipaddress.IPv6Address]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class GoogleWifiStatus(DataClassDictMixin):
|
|
17
|
+
"""Google Wifi status."""
|
|
18
|
+
|
|
19
|
+
dns: "GoogleWifiStatus.DNS"
|
|
20
|
+
software: "GoogleWifiStatus.Software"
|
|
21
|
+
system: "GoogleWifiStatus.System"
|
|
22
|
+
wan: "GoogleWifiStatus.WAN"
|
|
23
|
+
|
|
24
|
+
class Config(BaseConfig):
|
|
25
|
+
serialize_by_alias = True
|
|
26
|
+
deserialize_by_alias = True
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class DNS(DataClassDictMixin):
|
|
30
|
+
mode: Literal["automatic", "custom"]
|
|
31
|
+
servers: list[IPAddress]
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class Software(DataClassDictMixin):
|
|
35
|
+
software_version: str
|
|
36
|
+
update_status: str
|
|
37
|
+
update_required: bool
|
|
38
|
+
update_progress: float
|
|
39
|
+
|
|
40
|
+
class Config(BaseConfig):
|
|
41
|
+
serialize_by_alias = True
|
|
42
|
+
deserialize_by_alias = True
|
|
43
|
+
aliases = {
|
|
44
|
+
"software_version": "softwareVersion",
|
|
45
|
+
"update_status": "updateStatus",
|
|
46
|
+
"update_required": "updateRequired",
|
|
47
|
+
"update_progress": "updateProgress",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class System(DataClassDictMixin):
|
|
52
|
+
country_code: str
|
|
53
|
+
model_id: str
|
|
54
|
+
uptime: int
|
|
55
|
+
last_restart: datetime = field(init=False)
|
|
56
|
+
|
|
57
|
+
class Config(BaseConfig):
|
|
58
|
+
serialize_by_alias = True
|
|
59
|
+
deserialize_by_alias = True
|
|
60
|
+
aliases = {
|
|
61
|
+
"country_code": "countryCode",
|
|
62
|
+
"model_id": "modelId",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
def __post_init__(self):
|
|
66
|
+
"""Compute last_restart from uptime."""
|
|
67
|
+
object.__setattr__(
|
|
68
|
+
self,
|
|
69
|
+
"last_restart",
|
|
70
|
+
datetime.now() - timedelta(seconds=self.uptime),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
@dataclass(frozen=True)
|
|
74
|
+
class WAN(DataClassDictMixin):
|
|
75
|
+
online: bool
|
|
76
|
+
ethernet_link: bool
|
|
77
|
+
gateway_ip_address: IPAddress
|
|
78
|
+
local_ip_address: IPAddress
|
|
79
|
+
ip_method: str
|
|
80
|
+
ip_prefix_length: int
|
|
81
|
+
name_servers: list[IPAddress]
|
|
82
|
+
|
|
83
|
+
class Config(BaseConfig):
|
|
84
|
+
serialize_by_alias = True
|
|
85
|
+
deserialize_by_alias = True
|
|
86
|
+
aliases = {
|
|
87
|
+
"ethernet_link": "ethernetLink",
|
|
88
|
+
"gateway_ip_address": "gatewayIpAddress",
|
|
89
|
+
"local_ip_address": "localIpAddress",
|
|
90
|
+
"ip_method": "ipMethod",
|
|
91
|
+
"ip_prefix_length": "ipPrefixLength",
|
|
92
|
+
"name_servers": "nameServers",
|
|
93
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: googlewifiapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Asynchronous Python wrapper for the local Google Wifi JSON API
|
|
5
|
+
Author-email: Eddie Reasoner <eddie.reasoner@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/EReaso/GoogleWifiApi
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/EReaso/GoogleWifiApi/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.13
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE.txt
|
|
14
|
+
Requires-Dist: aiohttp<3.14.0,>=3.11.0
|
|
15
|
+
Requires-Dist: mashumaro>=3.22
|
|
16
|
+
Requires-Dist: yarl>=1.24.5
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# googlewifiapi
|
|
20
|
+
|
|
21
|
+
An asynchronous Python client for reading status from the local JSON API exposed
|
|
22
|
+
by Google Wifi routers.
|
|
23
|
+
The package is designed for integrations such as Home Assistant, but can be used by any Python application that needs router status data.
|
|
24
|
+
|
|
25
|
+
## About this README
|
|
26
|
+
|
|
27
|
+
This README was generated by AI with human review.
|
|
28
|
+
All code is either written by a human or is thoroughly reviewed.
|
|
29
|
+
I'll try to keep the README updated, but sometimes I might forget.
|
|
30
|
+
|
|
31
|
+
## Requirements
|
|
32
|
+
|
|
33
|
+
- Python 3.13 or newer
|
|
34
|
+
- A Google Wifi router reachable on the local network
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install googlewifiapi
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
Pass an `aiohttp.ClientSession` to the client so your application controls the
|
|
45
|
+
session lifecycle:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import asyncio
|
|
49
|
+
|
|
50
|
+
import aiohttp
|
|
51
|
+
|
|
52
|
+
from googlewifiapi import GoogleWifiAPI
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def main() -> None:
|
|
56
|
+
async with aiohttp.ClientSession() as session:
|
|
57
|
+
router = GoogleWifiAPI(host="192.168.86.1", sess=session)
|
|
58
|
+
await router.async_update()
|
|
59
|
+
|
|
60
|
+
if router.data is not None:
|
|
61
|
+
print(router.data.wan.online)
|
|
62
|
+
print(router.data.software.software_version)
|
|
63
|
+
print(router.data.system.last_restart)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
asyncio.run(main())
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The host defaults to `192.168.86.1`. After a successful update:
|
|
70
|
+
|
|
71
|
+
- `router.raw_data` contains the raw JSON response.
|
|
72
|
+
- `router.data` contains a validated, typed `GoogleWifiStatus`.
|
|
73
|
+
- IP address fields are parsed as `IPv4Address` or `IPv6Address` objects.
|
|
74
|
+
- `system.last_restart` is calculated from the router's uptime.
|
|
75
|
+
|
|
76
|
+
The status object exposes `dns`, `software`, `system`, and `wan` sections. Their
|
|
77
|
+
fields use Python names such as `software_version`, `gateway_ip_address`, and
|
|
78
|
+
`ip_prefix_length`, while the client handles the router API's camelCase keys.
|
|
79
|
+
|
|
80
|
+
## Errors
|
|
81
|
+
|
|
82
|
+
All library errors inherit from `GoogleWifiException`:
|
|
83
|
+
|
|
84
|
+
- `GoogleWifiClientError` indicates a request, connection, timeout, or JSON
|
|
85
|
+
decoding failure.
|
|
86
|
+
- `GoogleWifiDataValidationError` indicates that the response does not match
|
|
87
|
+
the expected status schema.
|
|
88
|
+
|
|
89
|
+
## Development
|
|
90
|
+
|
|
91
|
+
Install the test dependencies and run the test suite with [uv](https://docs.astral.sh/uv/):
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
uv sync --group test
|
|
95
|
+
uv run pytest
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
This project is licensed under the MIT License. See [LICENSE.txt](LICENSE.txt).
|
|
101
|
+
|
|
102
|
+
## Disclaimer
|
|
103
|
+
|
|
104
|
+
This is an independent, community-developed project and is not affiliated with
|
|
105
|
+
or endorsed by Google LLC. It relies on a local API that Google may change or
|
|
106
|
+
remove without notice.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
googlewifiapi/__init__.py,sha256=vFHdWJlq6-StukcpM6lp1fv7SFC0IznlJiI6-s9TqCI,153
|
|
2
|
+
googlewifiapi/api.py,sha256=G9iVBrh5UTn0OaIbjE9xIHLv8hQFFUSbDtRfB7onXN4,1801
|
|
3
|
+
googlewifiapi/const.py,sha256=iqyR3lS01vQXVKuHg4vf1QOuGxcPb4lJ3tcPjjtaT7A,164
|
|
4
|
+
googlewifiapi/exception.py,sha256=Np7EofKO8CyMYwPpWeW0yPeFV9UbX4pBPhhDnI_4ROY,385
|
|
5
|
+
googlewifiapi/status.py,sha256=5ZVbujcgqkHF4hZv3UoXx4qE9ps64gsu-0ZAvT_ojak,2778
|
|
6
|
+
googlewifiapi-0.1.0.dist-info/licenses/LICENSE.txt,sha256=l2iRY7G89sXwOXio9L2752hqHxP9bOmaX49p6jCLUiE,1071
|
|
7
|
+
googlewifiapi-0.1.0.dist-info/METADATA,sha256=sVrW_lZB0XhR62qFxuZupHcw6I1SVajaA7smbKLepT0,3129
|
|
8
|
+
googlewifiapi-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
9
|
+
googlewifiapi-0.1.0.dist-info/top_level.txt,sha256=rxzXa5_G4xRQvIYMBB2cafHTT2QqJhNbEoWLwmhQ_1Q,14
|
|
10
|
+
googlewifiapi-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016 Eddie Reasoner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
googlewifiapi
|