mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-07-12 21:08:13 +00:00
fix(prediction): green up CI for the new PV providers (mypy + provider sequence)
- Use urllib.parse.quote instead of requests.utils.quote in the pvnode and Solcast providers: the runtime re-export exists, but the requests type stubs do not declare it, so the pre-commit mypy hook failed with 'Module has no attribute "quote"'. - Mark the force_update keyword in the new provider tests with `# type: ignore` — it is consumed by the cache_in_file decorator at runtime; same call convention and ignore style as pvforecastakkudoktor.py. - Add PVForecastPVNode, PVForecastForecastSolar and PVForecastSolcast to the expected provider sequence in tests/test_prediction.py (fixture + index assertions) — the two sequence tests failed because the new providers were registered in prediction.py but missing from the hardcoded expectations.
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user