diff --git a/src/akkudoktoreos/prediction/pvforecastpvnode.py b/src/akkudoktoreos/prediction/pvforecastpvnode.py index 4e3bd03..4b08eac 100644 --- a/src/akkudoktoreos/prediction/pvforecastpvnode.py +++ b/src/akkudoktoreos/prediction/pvforecastpvnode.py @@ -22,6 +22,7 @@ Notes: import re from typing import Any, Optional +from urllib.parse import quote import pendulum import requests @@ -165,7 +166,7 @@ class PVForecastPVNode(PVForecastProvider): try: if site_id: - url = f"{PVNODE_BASE}/forecast/{requests.utils.quote(site_id, safe='')}" + url = f"{PVNODE_BASE}/forecast/{quote(site_id, safe='')}" response = requests.get(url, headers=headers, params=params, timeout=30) else: body = self._inline_body() diff --git a/src/akkudoktoreos/prediction/pvforecastsolcast.py b/src/akkudoktoreos/prediction/pvforecastsolcast.py index 01a2f99..004050c 100644 --- a/src/akkudoktoreos/prediction/pvforecastsolcast.py +++ b/src/akkudoktoreos/prediction/pvforecastsolcast.py @@ -18,6 +18,7 @@ Notes: import re from typing import Any, Optional +from urllib.parse import quote import requests from loguru import logger @@ -84,7 +85,7 @@ class PVForecastSolcast(PVForecastProvider): if not settings.api_key or not settings.site_id: raise ValueError("PVForecastSolcast requires api_key and site_id") - url = f"{SOLCAST_BASE}/{requests.utils.quote(settings.site_id, safe='')}/forecasts" + url = f"{SOLCAST_BASE}/{quote(settings.site_id, safe='')}/forecasts" params = {"format": "json", "hours": "72"} headers = {"Authorization": f"Bearer {settings.api_key}", "Accept": "application/json"} logger.debug(f"Requesting Solcast forecast: {url}") diff --git a/tests/test_prediction.py b/tests/test_prediction.py index b973017..2b7241d 100644 --- a/tests/test_prediction.py +++ b/tests/test_prediction.py @@ -19,7 +19,10 @@ from akkudoktoreos.prediction.prediction import ( PredictionCommonSettings, ) from akkudoktoreos.prediction.pvforecastakkudoktor import PVForecastAkkudoktor +from akkudoktoreos.prediction.pvforecastforecastsolar import PVForecastForecastSolar from akkudoktoreos.prediction.pvforecastimport import PVForecastImport +from akkudoktoreos.prediction.pvforecastpvnode import PVForecastPVNode +from akkudoktoreos.prediction.pvforecastsolcast import PVForecastSolcast from akkudoktoreos.prediction.pvforecastvrm import PVForecastVrm from akkudoktoreos.prediction.weatherbrightsky import WeatherBrightSky from akkudoktoreos.prediction.weatherclearoutside import WeatherClearOutside @@ -49,6 +52,9 @@ def forecast_providers(): LoadImport(), PVForecastAkkudoktor(), PVForecastVrm(), + PVForecastPVNode(), + PVForecastForecastSolar(), + PVForecastSolcast(), PVForecastImport(), WeatherBrightSky(), WeatherClearOutside(), @@ -98,11 +104,14 @@ def test_provider_sequence(prediction): assert isinstance(prediction.providers[9], LoadImport) assert isinstance(prediction.providers[10], PVForecastAkkudoktor) assert isinstance(prediction.providers[11], PVForecastVrm) - assert isinstance(prediction.providers[12], PVForecastImport) - assert isinstance(prediction.providers[13], WeatherBrightSky) - assert isinstance(prediction.providers[14], WeatherClearOutside) - assert isinstance(prediction.providers[15], WeatherOpenMeteo) - assert isinstance(prediction.providers[16], WeatherImport) + assert isinstance(prediction.providers[12], PVForecastPVNode) + assert isinstance(prediction.providers[13], PVForecastForecastSolar) + assert isinstance(prediction.providers[14], PVForecastSolcast) + assert isinstance(prediction.providers[15], PVForecastImport) + assert isinstance(prediction.providers[16], WeatherBrightSky) + assert isinstance(prediction.providers[17], WeatherClearOutside) + assert isinstance(prediction.providers[18], WeatherOpenMeteo) + assert isinstance(prediction.providers[19], WeatherImport) def test_provider_by_id(prediction, forecast_providers): diff --git a/tests/test_pvforecastforecastsolar.py b/tests/test_pvforecastforecastsolar.py index 1f3b4a0..c0b6daa 100644 --- a/tests/test_pvforecastforecastsolar.py +++ b/tests/test_pvforecastforecastsolar.py @@ -84,7 +84,9 @@ def test_plane_url_converts_azimuth(config_eos): config=config_eos.load, start_datetime=pendulum.datetime(2025, 1, 1, tz="UTC") ) with patch("requests.get", return_value=_http({})) as mock_get: - pv._request_forecast(force_update=True) + # force_update is consumed by the cache_in_file decorator at runtime + # (same call convention as pvforecastakkudoktor.py). + pv._request_forecast(force_update=True) # type: ignore url = mock_get.call_args[0][0] assert url == "https://api.forecast.solar/secret/estimate/52.5/13.4/25.0/90.0/7.5" @@ -106,7 +108,7 @@ def test_request_forecast_sums_planes(config_eos): _http({"2025-01-01 12:00:00": 800.0}), ] with patch("requests.get", side_effect=responses) as mock_get: - body = pv._request_forecast(force_update=True) + body = pv._request_forecast(force_update=True) # type: ignore assert mock_get.call_count == 2 assert body["watts"]["2025-01-01 12:00:00"] == 1800.0 diff --git a/tests/test_pvforecastpvnode.py b/tests/test_pvforecastpvnode.py index 351fd61..ce463b6 100644 --- a/tests/test_pvforecastpvnode.py +++ b/tests/test_pvforecastpvnode.py @@ -129,7 +129,9 @@ def test_request_forecast_inline_post_when_no_site(config_eos): pv = PVForecastPVNode(config=config_eos.load, start_datetime=pendulum.datetime(2025, 1, 1, tz="UTC")) fake = type("R", (), {"raise_for_status": lambda self: None, "json": lambda self: {"values": []}})() with patch("requests.post", return_value=fake) as mock_post: - pv._request_forecast(force_update=True) + # force_update is consumed by the cache_in_file decorator at runtime + # (same call convention as pvforecastakkudoktor.py). + pv._request_forecast(force_update=True) # type: ignore url = mock_post.call_args[0][0] assert url.endswith("/v2/forecast/inline") body = mock_post.call_args.kwargs["json"]