python-google-weather-api 0.0.6__tar.gz → 0.0.8__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.
Files changed (18) hide show
  1. {python_google_weather_api-0.0.6/src/python_google_weather_api.egg-info → python_google_weather_api-0.0.8}/PKG-INFO +3 -1
  2. {python_google_weather_api-0.0.6 → python_google_weather_api-0.0.8}/pyproject.toml +11 -1
  3. {python_google_weather_api-0.0.6 → python_google_weather_api-0.0.8}/src/google_weather_api/__init__.py +4 -0
  4. {python_google_weather_api-0.0.6 → python_google_weather_api-0.0.8}/src/google_weather_api/api.py +64 -7
  5. {python_google_weather_api-0.0.6 → python_google_weather_api-0.0.8}/src/google_weather_api/exceptions.py +17 -17
  6. {python_google_weather_api-0.0.6 → python_google_weather_api-0.0.8}/src/google_weather_api/model.py +702 -605
  7. {python_google_weather_api-0.0.6 → python_google_weather_api-0.0.8/src/python_google_weather_api.egg-info}/PKG-INFO +3 -1
  8. {python_google_weather_api-0.0.6 → python_google_weather_api-0.0.8}/src/python_google_weather_api.egg-info/SOURCES.txt +2 -1
  9. {python_google_weather_api-0.0.6 → python_google_weather_api-0.0.8}/src/python_google_weather_api.egg-info/requires.txt +2 -0
  10. python_google_weather_api-0.0.8/tests/test_api.py +285 -0
  11. python_google_weather_api-0.0.8/tests/test_model.py +177 -0
  12. python_google_weather_api-0.0.6/tests/test_api.py +0 -6
  13. {python_google_weather_api-0.0.6 → python_google_weather_api-0.0.8}/LICENSE +0 -0
  14. {python_google_weather_api-0.0.6 → python_google_weather_api-0.0.8}/README.md +0 -0
  15. {python_google_weather_api-0.0.6 → python_google_weather_api-0.0.8}/setup.cfg +0 -0
  16. {python_google_weather_api-0.0.6 → python_google_weather_api-0.0.8}/src/google_weather_api/py.typed +0 -0
  17. {python_google_weather_api-0.0.6 → python_google_weather_api-0.0.8}/src/python_google_weather_api.egg-info/dependency_links.txt +0 -0
  18. {python_google_weather_api-0.0.6 → python_google_weather_api-0.0.8}/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.6
3
+ Version: 0.0.8
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.6"
3
+ version = "0.0.8"
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
@@ -21,9 +21,11 @@ from .model import (
21
21
  IceThickness,
22
22
  Interval,
23
23
  LocalizedText,
24
+ MinuteForecastResponse,
24
25
  MoonEvents,
25
26
  Precipitation,
26
27
  PrecipitationProbability,
28
+ PrecipitationSegment,
27
29
  QuantitativePrecipitationForecast,
28
30
  SunEvents,
29
31
  Temperature,
@@ -54,9 +56,11 @@ __all__ = [
54
56
  "IceThickness",
55
57
  "Interval",
56
58
  "LocalizedText",
59
+ "MinuteForecastResponse",
57
60
  "MoonEvents",
58
61
  "Precipitation",
59
62
  "PrecipitationProbability",
63
+ "PrecipitationSegment",
60
64
  "QuantitativePrecipitationForecast",
61
65
  "SunEvents",
62
66
  "Temperature",
@@ -17,6 +17,7 @@ from .model import (
17
17
  CurrentConditionsResponse,
18
18
  DailyForecastResponse,
19
19
  HourlyForecastResponse,
20
+ MinuteForecastResponse,
20
21
  )
21
22
 
22
23
  _LOGGER = logging.getLogger(__name__)
@@ -45,7 +46,7 @@ class GoogleWeatherApi:
45
46
  self.referrer = referrer
46
47
  self.timeout = aiohttp.ClientTimeout(total=timeout)
47
48
 
48
- async def _async_get(self, endpoint: str, params: dict[str, Any]) -> dict[str, Any]:
49
+ async def _async_get(self, endpoint: str, params: dict[str, Any], *, include_language_code: bool = True) -> dict[str, Any]:
49
50
  """Perform a GET request."""
50
51
  url = f"{_BASE_URL}/{endpoint}"
51
52
  headers = {aiohttp.hdrs.USER_AGENT: _USER_AGENT}
@@ -53,11 +54,13 @@ class GoogleWeatherApi:
53
54
  headers[aiohttp.hdrs.REFERER] = self.referrer
54
55
  params = {
55
56
  **params,
56
- "key": self.api_key,
57
- "language_code": self.language_code,
58
57
  "units_system": self.units_system,
59
58
  }
59
+ if include_language_code:
60
+ params["language_code"] = self.language_code
61
+ # Log before adding the API key so it doesn't end up in debug logs.
60
62
  _LOGGER.debug("GET %s with params: %s", url, params)
63
+ params["key"] = self.api_key
61
64
  try:
62
65
  async with self.session.get(
63
66
  url,
@@ -87,6 +90,35 @@ class GoogleWeatherApi:
87
90
  except aiohttp.ClientError as err:
88
91
  raise GoogleWeatherApiConnectionError(err) from err
89
92
 
93
+ async def _async_get_all_pages(
94
+ self,
95
+ endpoint: str,
96
+ params: dict[str, Any],
97
+ records_key: str,
98
+ limit: int,
99
+ *,
100
+ include_language_code: bool = True,
101
+ ) -> dict[str, Any]:
102
+ """Perform a GET request, following pagination until `limit` records are collected.
103
+
104
+ Some endpoints cap the number of records they return per page regardless of the
105
+ requested page size, for example 24 for hourly forecasts, so a single request is
106
+ not always enough.
107
+ """
108
+ params = {**params, "page_size": limit}
109
+ data = await self._async_get(endpoint, params, include_language_code=include_language_code)
110
+ records: list[Any] = data.get(records_key, [])
111
+ while len(records) < limit and (page_token := data.get("nextPageToken")):
112
+ data = await self._async_get(
113
+ endpoint, {**params, "page_token": page_token}, include_language_code=include_language_code
114
+ )
115
+ page_records: list[Any] = data.get(records_key, [])
116
+ if not page_records:
117
+ break
118
+ records.extend(page_records)
119
+ data[records_key] = records[:limit]
120
+ return data
121
+
90
122
  async def async_get_current_conditions(self, latitude: float, longitude: float) -> CurrentConditionsResponse:
91
123
  """Fetch current weather conditions.
92
124
 
@@ -106,14 +138,15 @@ class GoogleWeatherApi:
106
138
 
107
139
  See https://developers.google.com/maps/documentation/weather/reference/rest/v1/forecast.hours/lookup
108
140
  """
109
- data = await self._async_get(
141
+ data = await self._async_get_all_pages(
110
142
  "forecast/hours:lookup",
111
143
  {
112
144
  "location.latitude": latitude,
113
145
  "location.longitude": longitude,
114
146
  "hours": hours,
115
- "page_size": hours,
116
147
  },
148
+ "forecastHours",
149
+ hours,
117
150
  )
118
151
  return HourlyForecastResponse.from_dict(data)
119
152
 
@@ -122,13 +155,37 @@ class GoogleWeatherApi:
122
155
 
123
156
  See https://developers.google.com/maps/documentation/weather/reference/rest/v1/forecast.days/lookup
124
157
  """
125
- data = await self._async_get(
158
+ data = await self._async_get_all_pages(
126
159
  "forecast/days:lookup",
127
160
  {
128
161
  "location.latitude": latitude,
129
162
  "location.longitude": longitude,
130
163
  "days": days,
131
- "page_size": days,
132
164
  },
165
+ "forecastDays",
166
+ days,
133
167
  )
134
168
  return DailyForecastResponse.from_dict(data)
169
+
170
+ async def async_get_minute_forecast(
171
+ self, latitude: float, longitude: float, page_size: int = 360
172
+ ) -> MinuteForecastResponse:
173
+ """Fetch the precipitation nowcast for up to 6 hours.
174
+
175
+ Segments cover 2- or 15-minute intervals depending on the location. The endpoint
176
+ does not accept language_code (a reserved field in google.maps.weather.v1), so it
177
+ is omitted from these requests.
178
+
179
+ See https://developers.google.com/maps/documentation/weather/minute-forecast
180
+ """
181
+ data = await self._async_get_all_pages(
182
+ "forecast/minutes:lookup",
183
+ {
184
+ "location.latitude": latitude,
185
+ "location.longitude": longitude,
186
+ },
187
+ "segments",
188
+ page_size,
189
+ include_language_code=False,
190
+ )
191
+ return MinuteForecastResponse.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."""