python-google-weather-api 0.0.5__tar.gz → 0.0.7__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.
- {python_google_weather_api-0.0.5/src/python_google_weather_api.egg-info → python_google_weather_api-0.0.7}/PKG-INFO +3 -1
- {python_google_weather_api-0.0.5 → python_google_weather_api-0.0.7}/pyproject.toml +11 -1
- {python_google_weather_api-0.0.5 → python_google_weather_api-0.0.7}/src/google_weather_api/api.py +40 -7
- {python_google_weather_api-0.0.5 → python_google_weather_api-0.0.7}/src/google_weather_api/exceptions.py +17 -17
- {python_google_weather_api-0.0.5 → python_google_weather_api-0.0.7}/src/google_weather_api/model.py +641 -605
- {python_google_weather_api-0.0.5 → python_google_weather_api-0.0.7/src/python_google_weather_api.egg-info}/PKG-INFO +3 -1
- {python_google_weather_api-0.0.5 → python_google_weather_api-0.0.7}/src/python_google_weather_api.egg-info/SOURCES.txt +2 -1
- {python_google_weather_api-0.0.5 → python_google_weather_api-0.0.7}/src/python_google_weather_api.egg-info/requires.txt +2 -0
- python_google_weather_api-0.0.7/tests/test_api.py +269 -0
- python_google_weather_api-0.0.7/tests/test_model.py +143 -0
- python_google_weather_api-0.0.5/tests/test_api.py +0 -6
- {python_google_weather_api-0.0.5 → python_google_weather_api-0.0.7}/LICENSE +0 -0
- {python_google_weather_api-0.0.5 → python_google_weather_api-0.0.7}/README.md +0 -0
- {python_google_weather_api-0.0.5 → python_google_weather_api-0.0.7}/setup.cfg +0 -0
- {python_google_weather_api-0.0.5 → python_google_weather_api-0.0.7}/src/google_weather_api/__init__.py +0 -0
- {python_google_weather_api-0.0.5 → python_google_weather_api-0.0.7}/src/google_weather_api/py.typed +0 -0
- {python_google_weather_api-0.0.5 → python_google_weather_api-0.0.7}/src/python_google_weather_api.egg-info/dependency_links.txt +0 -0
- {python_google_weather_api-0.0.5 → python_google_weather_api-0.0.7}/src/python_google_weather_api.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-google-weather-api
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
4
|
Summary: A python client library for Google Weather API
|
|
5
5
|
Author-email: tronikos <tronikos@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -13,6 +13,8 @@ Requires-Dist: aiohttp>=3.8
|
|
|
13
13
|
Requires-Dist: mashumaro
|
|
14
14
|
Provides-Extra: test
|
|
15
15
|
Requires-Dist: pytest; extra == "test"
|
|
16
|
+
Requires-Dist: pytest-asyncio; extra == "test"
|
|
17
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
16
18
|
Dynamic: license-file
|
|
17
19
|
|
|
18
20
|
# python-google-weather-api
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-google-weather-api"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.7"
|
|
4
4
|
license = "Apache-2.0"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name="tronikos", email="tronikos@gmail.com" },
|
|
@@ -16,6 +16,8 @@ dependencies = [
|
|
|
16
16
|
[project.optional-dependencies]
|
|
17
17
|
test = [
|
|
18
18
|
"pytest",
|
|
19
|
+
"pytest-asyncio",
|
|
20
|
+
"pytest-cov",
|
|
19
21
|
]
|
|
20
22
|
|
|
21
23
|
|
|
@@ -27,6 +29,13 @@ test = [
|
|
|
27
29
|
requires = ["setuptools"]
|
|
28
30
|
build-backend = "setuptools.build_meta"
|
|
29
31
|
|
|
32
|
+
[tool.pytest.ini_options]
|
|
33
|
+
asyncio_mode = "auto"
|
|
34
|
+
addopts = "--cov=google_weather_api --cov-report=term-missing --cov-fail-under=90"
|
|
35
|
+
|
|
36
|
+
[tool.coverage.run]
|
|
37
|
+
branch = true
|
|
38
|
+
|
|
30
39
|
[tool.ruff]
|
|
31
40
|
target-version = "py311"
|
|
32
41
|
line-length = 127
|
|
@@ -64,6 +73,7 @@ ignore = [
|
|
|
64
73
|
"D203", # 1 blank line required before class docstring
|
|
65
74
|
"D213", # Multi-line docstring summary should start at the second line
|
|
66
75
|
"PLR0913", # Too many arguments in function definition
|
|
76
|
+
"PLR0917", # Too many positional arguments in function definition
|
|
67
77
|
"PLR1714", # Consider merging multiple comparisons
|
|
68
78
|
"PLR2004", # Magic value used in comparison
|
|
69
79
|
"TRY003", # Avoid specifying long messages outside the exception class
|
{python_google_weather_api-0.0.5 → python_google_weather_api-0.0.7}/src/google_weather_api/api.py
RENAMED
|
@@ -53,11 +53,12 @@ class GoogleWeatherApi:
|
|
|
53
53
|
headers[aiohttp.hdrs.REFERER] = self.referrer
|
|
54
54
|
params = {
|
|
55
55
|
**params,
|
|
56
|
-
"key": self.api_key,
|
|
57
56
|
"language_code": self.language_code,
|
|
58
57
|
"units_system": self.units_system,
|
|
59
58
|
}
|
|
59
|
+
# Log before adding the API key so it doesn't end up in debug logs.
|
|
60
60
|
_LOGGER.debug("GET %s with params: %s", url, params)
|
|
61
|
+
params["key"] = self.api_key
|
|
61
62
|
try:
|
|
62
63
|
async with self.session.get(
|
|
63
64
|
url,
|
|
@@ -68,15 +69,45 @@ class GoogleWeatherApi:
|
|
|
68
69
|
res: dict[str, Any] = await resp.json()
|
|
69
70
|
_LOGGER.debug("Got %s for %s", resp.status, url)
|
|
70
71
|
if resp.status != HTTPStatus.OK:
|
|
72
|
+
error_data = res.get("error", {})
|
|
73
|
+
error_msg = error_data.get("message", "Unknown API error")
|
|
74
|
+
|
|
71
75
|
if resp.status in (HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN):
|
|
72
|
-
raise GoogleWeatherApiAuthError(
|
|
73
|
-
|
|
76
|
+
raise GoogleWeatherApiAuthError(error_msg)
|
|
77
|
+
|
|
78
|
+
# Google APIs often return 400 Bad Request for invalid API keys
|
|
79
|
+
if resp.status == HTTPStatus.BAD_REQUEST:
|
|
80
|
+
for detail in error_data.get("details", []):
|
|
81
|
+
if detail.get("reason") == "API_KEY_INVALID":
|
|
82
|
+
raise GoogleWeatherApiAuthError(error_msg)
|
|
83
|
+
|
|
84
|
+
raise GoogleWeatherApiResponseError(error_msg)
|
|
74
85
|
return res
|
|
75
86
|
except TimeoutError as err:
|
|
76
87
|
raise GoogleWeatherApiConnectionError("Timeout") from err
|
|
77
88
|
except aiohttp.ClientError as err:
|
|
78
89
|
raise GoogleWeatherApiConnectionError(err) from err
|
|
79
90
|
|
|
91
|
+
async def _async_get_all_pages(
|
|
92
|
+
self, endpoint: str, params: dict[str, Any], records_key: str, limit: int
|
|
93
|
+
) -> dict[str, Any]:
|
|
94
|
+
"""Perform a GET request, following pagination until `limit` records are collected.
|
|
95
|
+
|
|
96
|
+
The API caps the number of records it returns per page (24 for hourly forecasts),
|
|
97
|
+
regardless of the requested page size, so a single request is not enough.
|
|
98
|
+
"""
|
|
99
|
+
params = {**params, "page_size": limit}
|
|
100
|
+
data = await self._async_get(endpoint, params)
|
|
101
|
+
records: list[Any] = data.get(records_key, [])
|
|
102
|
+
while len(records) < limit and (page_token := data.get("nextPageToken")):
|
|
103
|
+
data = await self._async_get(endpoint, {**params, "page_token": page_token})
|
|
104
|
+
page_records: list[Any] = data.get(records_key, [])
|
|
105
|
+
if not page_records:
|
|
106
|
+
break
|
|
107
|
+
records.extend(page_records)
|
|
108
|
+
data[records_key] = records[:limit]
|
|
109
|
+
return data
|
|
110
|
+
|
|
80
111
|
async def async_get_current_conditions(self, latitude: float, longitude: float) -> CurrentConditionsResponse:
|
|
81
112
|
"""Fetch current weather conditions.
|
|
82
113
|
|
|
@@ -96,14 +127,15 @@ class GoogleWeatherApi:
|
|
|
96
127
|
|
|
97
128
|
See https://developers.google.com/maps/documentation/weather/reference/rest/v1/forecast.hours/lookup
|
|
98
129
|
"""
|
|
99
|
-
data = await self.
|
|
130
|
+
data = await self._async_get_all_pages(
|
|
100
131
|
"forecast/hours:lookup",
|
|
101
132
|
{
|
|
102
133
|
"location.latitude": latitude,
|
|
103
134
|
"location.longitude": longitude,
|
|
104
135
|
"hours": hours,
|
|
105
|
-
"page_size": hours,
|
|
106
136
|
},
|
|
137
|
+
"forecastHours",
|
|
138
|
+
hours,
|
|
107
139
|
)
|
|
108
140
|
return HourlyForecastResponse.from_dict(data)
|
|
109
141
|
|
|
@@ -112,13 +144,14 @@ class GoogleWeatherApi:
|
|
|
112
144
|
|
|
113
145
|
See https://developers.google.com/maps/documentation/weather/reference/rest/v1/forecast.days/lookup
|
|
114
146
|
"""
|
|
115
|
-
data = await self.
|
|
147
|
+
data = await self._async_get_all_pages(
|
|
116
148
|
"forecast/days:lookup",
|
|
117
149
|
{
|
|
118
150
|
"location.latitude": latitude,
|
|
119
151
|
"location.longitude": longitude,
|
|
120
152
|
"days": days,
|
|
121
|
-
"page_size": days,
|
|
122
153
|
},
|
|
154
|
+
"forecastDays",
|
|
155
|
+
days,
|
|
123
156
|
)
|
|
124
157
|
return DailyForecastResponse.from_dict(data)
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
"""Exceptions for Google Weather API."""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class GoogleWeatherApiError(Exception):
|
|
5
|
-
"""Exception talking to the Google Weather API."""
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class GoogleWeatherApiConnectionError(GoogleWeatherApiError):
|
|
9
|
-
"""Exception connecting to the Google Weather API."""
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class GoogleWeatherApiAuthError(GoogleWeatherApiError):
|
|
13
|
-
"""Exception raised for authentication errors in the Google Weather API."""
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class GoogleWeatherApiResponseError(GoogleWeatherApiError):
|
|
17
|
-
"""Exception raised for errors in the Google Weather API response."""
|
|
1
|
+
"""Exceptions for Google Weather API."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class GoogleWeatherApiError(Exception):
|
|
5
|
+
"""Exception talking to the Google Weather API."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GoogleWeatherApiConnectionError(GoogleWeatherApiError):
|
|
9
|
+
"""Exception connecting to the Google Weather API."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GoogleWeatherApiAuthError(GoogleWeatherApiError):
|
|
13
|
+
"""Exception raised for authentication errors in the Google Weather API."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GoogleWeatherApiResponseError(GoogleWeatherApiError):
|
|
17
|
+
"""Exception raised for errors in the Google Weather API response."""
|