mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-10-15 18:41:57 +00:00
fix: provider settings in top level field of configuration (#1161)
Configuration for some providers was given in the sub-field `provider_settings` combining the settings of several providers. Pydantic does not understand this very well and the configuration became cumbersome, especially in EOSdash. All provider settings from the `provider-settings` sub-field are now lifted to the top level field of the configuration. The changes are automatically migrated in the configuration. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
This commit is contained in:
@@ -70,7 +70,7 @@ class ElecPriceCommonSettings(SettingsBaseModel):
|
||||
|
||||
elecpriceimport: ElecPriceImportCommonSettings = Field(
|
||||
default_factory=ElecPriceImportCommonSettings,
|
||||
json_schema_extra={"description": "Import provider settings."},
|
||||
json_schema_extra={"description": "Electricity price import provider settings."},
|
||||
)
|
||||
|
||||
energycharts: ElecPriceEnergyChartsCommonSettings = Field(
|
||||
|
||||
@@ -25,19 +25,6 @@ def feedintariff_provider_ids() -> list[str]:
|
||||
]
|
||||
|
||||
|
||||
class FeedInTariffCommonProviderSettings(SettingsBaseModel):
|
||||
"""Feed In Tariff Prediction Provider Configuration."""
|
||||
|
||||
FeedInTariffFixed: Optional[FeedInTariffFixedCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "FeedInTariffFixed settings", "examples": [None]},
|
||||
)
|
||||
FeedInTariffImport: Optional[FeedInTariffImportCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "FeedInTariffImport settings", "examples": [None]},
|
||||
)
|
||||
|
||||
|
||||
class FeedInTariffCommonSettings(SettingsBaseModel):
|
||||
"""Feed In Tariff Prediction Configuration."""
|
||||
|
||||
@@ -49,18 +36,14 @@ class FeedInTariffCommonSettings(SettingsBaseModel):
|
||||
},
|
||||
)
|
||||
|
||||
provider_settings: FeedInTariffCommonProviderSettings = Field(
|
||||
default_factory=FeedInTariffCommonProviderSettings,
|
||||
json_schema_extra={
|
||||
"description": "Provider settings",
|
||||
"examples": [
|
||||
# Example 1: Empty/default settings (all providers None)
|
||||
{
|
||||
"FeedInTariffFixed": None,
|
||||
"FeedInTariffImport": None,
|
||||
},
|
||||
],
|
||||
},
|
||||
feedintarifffixed: FeedInTariffFixedCommonSettings = Field(
|
||||
default_factory=FeedInTariffFixedCommonSettings,
|
||||
json_schema_extra={"description": "Fixed feed in tariff provider settings."},
|
||||
)
|
||||
|
||||
feedintariffimport: FeedInTariffImportCommonSettings = Field(
|
||||
default_factory=FeedInTariffImportCommonSettings,
|
||||
json_schema_extra={"description": "Feed in tarif import provider settings."},
|
||||
)
|
||||
|
||||
@computed_field # type: ignore[prop-decorator]
|
||||
|
||||
@@ -37,9 +37,7 @@ class FeedInTariffFixed(FeedInTariffProvider):
|
||||
async def _update_data(self, force_update: Optional[bool] = False) -> None:
|
||||
error_msg = "Feed in tariff not provided"
|
||||
try:
|
||||
feed_in_tariff = (
|
||||
self.config.feedintariff.provider_settings.FeedInTariffFixed.feed_in_tariff_kwh
|
||||
)
|
||||
feed_in_tariff = self.config.feedintariff.feedintarifffixed.feed_in_tariff_kwh
|
||||
except Exception:
|
||||
logger.exception(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
|
||||
@@ -67,16 +67,16 @@ class FeedInTariffImport(FeedInTariffProvider, PredictionImportProvider):
|
||||
async def _update_data(self, force_update: Optional[bool] = False) -> None:
|
||||
# Both _sequence_lock and _record_lock are already held by the caller.
|
||||
# Use internal sync methods only — never await public async counterparts.
|
||||
if self.config.feedintariff.provider_settings.FeedInTariffImport is None:
|
||||
if self.config.feedintariff.feedintariffimport is None:
|
||||
logger.debug(f"{self.provider_id()} data update without provider settings.")
|
||||
return
|
||||
if self.config.feedintariff.provider_settings.FeedInTariffImport.import_file_path:
|
||||
if self.config.feedintariff.feedintariffimport.import_file_path:
|
||||
await self._import_from_file(
|
||||
self.config.provider_settings.FeedInTariffImport.import_file_path,
|
||||
self.config.feedintariffimport.import_file_path,
|
||||
key_prefix="feedintariff",
|
||||
)
|
||||
if self.config.feedintariff.provider_settings.FeedInTariffImport.import_json:
|
||||
if self.config.feedintariff.feedintariffimport.import_json:
|
||||
await self._import_from_json(
|
||||
self.config.feedintariff.provider_settings.FeedInTariffImport.import_json,
|
||||
self.config.feedintariff.feedintariffimport.import_json,
|
||||
key_prefix="feedintariff",
|
||||
)
|
||||
|
||||
@@ -44,16 +44,16 @@ class LoadCommonSettings(SettingsBaseModel):
|
||||
json_schema_extra={"description": "LoadAkkudoktor provider settings."},
|
||||
)
|
||||
|
||||
loadvrm: LoadVrmCommonSettings = Field(
|
||||
default_factory=LoadVrmCommonSettings,
|
||||
json_schema_extra={"description": "LoadVrm provider settings."},
|
||||
)
|
||||
|
||||
loadimport: LoadImportCommonSettings = Field(
|
||||
default_factory=LoadImportCommonSettings,
|
||||
json_schema_extra={"description": "LoadImport provider settings."},
|
||||
)
|
||||
|
||||
vrm: LoadVrmCommonSettings = Field(
|
||||
default_factory=LoadVrmCommonSettings,
|
||||
json_schema_extra={"description": "Victron Remote Management (VRM) provider settings."},
|
||||
)
|
||||
|
||||
@computed_field # type: ignore[prop-decorator]
|
||||
@property
|
||||
def providers(self) -> list[str]:
|
||||
|
||||
@@ -26,14 +26,14 @@ class VrmForecastResponse(PydanticBaseModel):
|
||||
class LoadVrmCommonSettings(SettingsBaseModel):
|
||||
"""Common settings for load forecast VRM API."""
|
||||
|
||||
load_vrm_token: str = Field(
|
||||
token: str = Field(
|
||||
default="your-token",
|
||||
json_schema_extra={
|
||||
"description": "Token for Connecting VRM API",
|
||||
"description": "Access token for connecting to the Victron Remote Management (VRM) API",
|
||||
"examples": ["your-token"],
|
||||
},
|
||||
)
|
||||
load_vrm_idsite: int = Field(
|
||||
site_id: int = Field(
|
||||
default=12345, json_schema_extra={"description": "VRM-Installation-ID", "examples": [12345]}
|
||||
)
|
||||
|
||||
@@ -62,9 +62,9 @@ class LoadVrm(LoadProvider):
|
||||
def _request_forecast(self, start_ts: int, end_ts: int) -> VrmForecastResponse:
|
||||
"""Fetch forecast data from Victron VRM API."""
|
||||
base_url = "https://vrmapi.victronenergy.com/v2/installations"
|
||||
vrm_settings = self.config.load.loadvrm
|
||||
installation_id = vrm_settings.load_vrm_idsite
|
||||
api_token = vrm_settings.load_vrm_token
|
||||
vrm_settings = self.config.load.vrm
|
||||
installation_id = vrm_settings.site_id
|
||||
api_token = vrm_settings.token
|
||||
|
||||
url = f"{base_url}/{installation_id}/stats?type=forecast&start={start_ts}&end={end_ts}&interval=hours"
|
||||
headers = {"X-Authorization": f"Token {api_token}", "Content-Type": "application/json"}
|
||||
|
||||
@@ -180,31 +180,6 @@ class PVForecastPlaneSetting(SettingsBaseModel):
|
||||
return pvtechchoice
|
||||
|
||||
|
||||
class PVForecastCommonProviderSettings(SettingsBaseModel):
|
||||
"""PV Forecast Provider Configuration."""
|
||||
|
||||
PVForecastImport: Optional[PVForecastImportCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "PVForecastImport settings", "examples": [None]},
|
||||
)
|
||||
PVForecastVrm: Optional[PVForecastVrmCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "PVForecastVrm settings", "examples": [None]},
|
||||
)
|
||||
PVForecastPVNode: Optional[PVForecastPVNodeCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "PVForecastPVNode settings", "examples": [None]},
|
||||
)
|
||||
PVForecastForecastSolar: Optional[PVForecastForecastSolarCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "PVForecastForecastSolar settings", "examples": [None]},
|
||||
)
|
||||
PVForecastSolcast: Optional[PVForecastSolcastCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "PVForecastSolcast settings", "examples": [None]},
|
||||
)
|
||||
|
||||
|
||||
class PVForecastCommonSettings(SettingsBaseModel):
|
||||
"""PV Forecast Configuration."""
|
||||
|
||||
@@ -221,18 +196,29 @@ class PVForecastCommonSettings(SettingsBaseModel):
|
||||
},
|
||||
)
|
||||
|
||||
provider_settings: PVForecastCommonProviderSettings = Field(
|
||||
default_factory=PVForecastCommonProviderSettings,
|
||||
json_schema_extra={
|
||||
"description": "Provider settings",
|
||||
"examples": [
|
||||
# Example 1: Empty/default settings (all providers None)
|
||||
{
|
||||
"PVForecastImport": None,
|
||||
"PVForecastVrm": None,
|
||||
},
|
||||
],
|
||||
},
|
||||
pvforecastimport: PVForecastImportCommonSettings = Field(
|
||||
default_factory=PVForecastImportCommonSettings,
|
||||
json_schema_extra={"description": "PV forecast import provider settings"},
|
||||
)
|
||||
|
||||
vrm: PVForecastVrmCommonSettings = Field(
|
||||
default_factory=PVForecastVrmCommonSettings,
|
||||
json_schema_extra={"description": "Victron Remote Management (VRM) provider settings"},
|
||||
)
|
||||
|
||||
pvnode: PVForecastPVNodeCommonSettings = Field(
|
||||
default_factory=PVForecastPVNodeCommonSettings,
|
||||
json_schema_extra={"description": "PVNode provider settings"},
|
||||
)
|
||||
|
||||
forecastsolar: PVForecastForecastSolarCommonSettings = Field(
|
||||
default_factory=PVForecastForecastSolarCommonSettings,
|
||||
json_schema_extra={"description": "ForecastSolar provider settings"},
|
||||
)
|
||||
|
||||
solcast: PVForecastSolcastCommonSettings = Field(
|
||||
default_factory=PVForecastSolcastCommonSettings,
|
||||
json_schema_extra={"description": "Solcast provider settings"},
|
||||
)
|
||||
|
||||
planes: Optional[list[PVForecastPlaneSetting]] = Field(
|
||||
|
||||
@@ -62,7 +62,7 @@ class PVForecastForecastSolar(PVForecastProvider):
|
||||
|
||||
@property
|
||||
def _api_key(self) -> Optional[str]:
|
||||
settings = self.config.pvforecast.provider_settings.PVForecastForecastSolar
|
||||
settings = self.config.pvforecast.forecastsolar
|
||||
return settings.api_key if settings is not None else None
|
||||
|
||||
def _to_utc_datetime(self, local_ts: Any, iana_tz: Optional[str]) -> Any:
|
||||
|
||||
@@ -67,16 +67,16 @@ class PVForecastImport(PVForecastProvider, PredictionImportProvider):
|
||||
async def _update_data(self, force_update: Optional[bool] = False) -> None:
|
||||
# Both _sequence_lock and _record_lock are already held by the caller.
|
||||
# Use internal sync methods only — never await public async counterparts.
|
||||
if self.config.pvforecast.provider_settings.PVForecastImport is None:
|
||||
if self.config.pvforecast.pvforecastimport is None:
|
||||
logger.debug(f"{self.provider_id()} data update without provider settings.")
|
||||
return
|
||||
if self.config.pvforecast.provider_settings.PVForecastImport.import_file_path is not None:
|
||||
if self.config.pvforecast.pvforecastimport.import_file_path is not None:
|
||||
await self._import_from_file(
|
||||
self.config.pvforecast.provider_settings.PVForecastImport.import_file_path,
|
||||
self.config.pvforecast.pvforecastimport.import_file_path,
|
||||
key_prefix="pvforecast",
|
||||
)
|
||||
if self.config.pvforecast.provider_settings.PVForecastImport.import_json is not None:
|
||||
if self.config.pvforecast.pvforecastimport.import_json is not None:
|
||||
await self._import_from_json(
|
||||
self.config.pvforecast.provider_settings.PVForecastImport.import_json,
|
||||
self.config.pvforecast.pvforecastimport.import_json,
|
||||
key_prefix="pvforecast",
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ null is treated as 0 W so the optimizer's linear resampling does not interpolate
|
||||
phantom production across the night.
|
||||
|
||||
Notes:
|
||||
- Requires ``pvforecast.provider_settings.PVForecastPVNode.api_key`` (Bearer auth).
|
||||
- Requires ``pvforecast.pvnode.api_key`` (Bearer auth).
|
||||
- API: https://api.pvnode.com/v2 (15-minute resolution).
|
||||
"""
|
||||
|
||||
@@ -81,7 +81,7 @@ class PVForecastPVNode(PVForecastProvider):
|
||||
|
||||
@property
|
||||
def _settings(self) -> PVForecastPVNodeCommonSettings:
|
||||
settings = self.config.pvforecast.provider_settings.PVForecastPVNode
|
||||
settings = self.config.pvforecast.pvnode
|
||||
if settings is None:
|
||||
settings = PVForecastPVNodeCommonSettings()
|
||||
return settings
|
||||
@@ -158,7 +158,7 @@ class PVForecastPVNode(PVForecastProvider):
|
||||
settings = self._settings
|
||||
api_key = settings.api_key
|
||||
if not api_key:
|
||||
raise ValueError("PVForecastPVNode requires pvforecast...PVForecastPVNode.api_key")
|
||||
raise ValueError("PVForecastPVNode requires pvforecast.pvnode.api_key")
|
||||
|
||||
headers = {"Authorization": f"Bearer {api_key}", "Accept": "application/json"}
|
||||
params = {"forecast_days": str(settings.forecast_days)}
|
||||
@@ -189,7 +189,7 @@ class PVForecastPVNode(PVForecastProvider):
|
||||
if latitude is None or longitude is None:
|
||||
raise ValueError(
|
||||
"PVForecastPVNode inline mode needs general.latitude/longitude "
|
||||
"(or set pvforecast...PVForecastPVNode.site_id)"
|
||||
"(or set pvforecast.pvnode.site_id)"
|
||||
)
|
||||
planes = self.config.pvforecast.planes or []
|
||||
strings = []
|
||||
|
||||
@@ -63,7 +63,7 @@ class PVForecastSolcast(PVForecastProvider):
|
||||
|
||||
@property
|
||||
def _settings(self) -> PVForecastSolcastCommonSettings:
|
||||
settings = self.config.pvforecast.provider_settings.PVForecastSolcast
|
||||
settings = self.config.pvforecast.solcast
|
||||
if settings is None:
|
||||
settings = PVForecastSolcastCommonSettings()
|
||||
return settings
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Retrieves pvforecast data from VRM API."""
|
||||
"""Retrieves pvforecast data from Victron Remote Management (VRM) API."""
|
||||
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
@@ -26,14 +26,14 @@ class VrmForecastResponse(PydanticBaseModel):
|
||||
class PVForecastVrmCommonSettings(SettingsBaseModel):
|
||||
"""Common settings for PV forecast VRM API."""
|
||||
|
||||
pvforecast_vrm_token: str = Field(
|
||||
token: str = Field(
|
||||
default="your-token",
|
||||
json_schema_extra={
|
||||
"description": "Token for Connecting VRM API",
|
||||
"description": "Access token for connecting to the Victron Remote Management (VRM) API",
|
||||
"examples": ["your-token"],
|
||||
},
|
||||
)
|
||||
pvforecast_vrm_idsite: int = Field(
|
||||
site_id: int = Field(
|
||||
default=12345, json_schema_extra={"description": "VRM-Installation-ID", "examples": [12345]}
|
||||
)
|
||||
|
||||
@@ -63,8 +63,8 @@ class PVForecastVrm(PVForecastProvider):
|
||||
def _request_forecast(self, start_ts: int, end_ts: int) -> VrmForecastResponse:
|
||||
"""Fetch forecast data from Victron VRM API."""
|
||||
source = "https://vrmapi.victronenergy.com/v2/installations"
|
||||
id_site = self.config.pvforecast.provider_settings.PVForecastVrm.pvforecast_vrm_idsite
|
||||
api_token = self.config.pvforecast.provider_settings.PVForecastVrm.pvforecast_vrm_token
|
||||
id_site = self.config.pvforecast.vrm.site_id
|
||||
api_token = self.config.pvforecast.vrm.token
|
||||
headers = {"X-Authorization": f"Token {api_token}", "Content-Type": "application/json"}
|
||||
url = f"{source}/{id_site}/stats?type=forecast&start={start_ts}&end={end_ts}&interval=hours"
|
||||
logger.debug(f"Requesting VRM forecast: {url}")
|
||||
|
||||
@@ -26,15 +26,6 @@ def weather_provider_ids() -> list[str]:
|
||||
]
|
||||
|
||||
|
||||
class WeatherCommonProviderSettings(SettingsBaseModel):
|
||||
"""Weather Forecast Provider Configuration."""
|
||||
|
||||
WeatherImport: Optional[WeatherImportCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "WeatherImport settings", "examples": [None]},
|
||||
)
|
||||
|
||||
|
||||
class WeatherCommonSettings(SettingsBaseModel):
|
||||
"""Weather Forecast Configuration."""
|
||||
|
||||
@@ -46,17 +37,9 @@ class WeatherCommonSettings(SettingsBaseModel):
|
||||
},
|
||||
)
|
||||
|
||||
provider_settings: WeatherCommonProviderSettings = Field(
|
||||
default_factory=WeatherCommonProviderSettings,
|
||||
json_schema_extra={
|
||||
"description": "Provider settings",
|
||||
"examples": [
|
||||
# Example 1: Empty/default settings (all providers None)
|
||||
{
|
||||
"WeatherImport": None,
|
||||
},
|
||||
],
|
||||
},
|
||||
weatherimport: WeatherImportCommonSettings = Field(
|
||||
default_factory=WeatherImportCommonSettings,
|
||||
json_schema_extra={"description": "Weather import provider settings"},
|
||||
)
|
||||
|
||||
@computed_field # type: ignore[prop-decorator]
|
||||
|
||||
@@ -67,16 +67,16 @@ class WeatherImport(WeatherProvider, PredictionImportProvider):
|
||||
async def _update_data(self, force_update: Optional[bool] = False) -> None:
|
||||
# Both _sequence_lock and _record_lock are already held by the caller.
|
||||
# Use internal sync methods only — never await public async counterparts.
|
||||
if self.config.weather.provider_settings.WeatherImport is None:
|
||||
if self.config.weather.weatherimport is None:
|
||||
logger.debug(f"{self.provider_id()} data update without provider settings.")
|
||||
return
|
||||
if self.config.weather.provider_settings.WeatherImport.import_file_path:
|
||||
if self.config.weather.weatherimport.import_file_path:
|
||||
await self._import_from_file(
|
||||
self.config.weather.provider_settings.WeatherImport.import_file_path,
|
||||
self.config.weather.weatherimport.import_file_path,
|
||||
key_prefix="weather",
|
||||
)
|
||||
if self.config.weather.provider_settings.WeatherImport.import_json:
|
||||
if self.config.weather.weatherimport.import_json:
|
||||
await self._import_from_json(
|
||||
self.config.weather.provider_settings.WeatherImport.import_json,
|
||||
self.config.weather.weatherimport.import_json,
|
||||
key_prefix="weather",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user