Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion flexmeasures_weather/cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,11 @@ def collect_weather_data(location, asset_id, store_in_db, num_cells, method, reg
a geometrical grid (See the --location parameter).
"""

api_key = str(current_app.config.get("WEATHERAPI_KEY", ""))
api_key = str(
current_app.config.get(
"WEATHERAPI_KEY", current_app.config.get("OPENWEATHERMAP_API_KEY", "")
)
)
if api_key == "":
raise Exception("[FLEXMEASURES-WEATHER] Setting WEATHERAPI_KEY not available.")
if asset_id is not None:
Expand Down
4 changes: 2 additions & 2 deletions flexmeasures_weather/cli/schemas/weather_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ class WeatherSensorSchema(Schema):
)

@validates("name")
def validate_name_is_supported(self, name: str):
def validate_name_is_supported(self, name: str, **kwargs):
if get_supported_sensor_spec(name):
return
raise ValidationError(
f"Weather sensors with name '{name}' are not supported by flexmeasures-weather. For now, the following is supported: [{get_supported_sensors_str()}]"
)

@validates("timezone")
def validate_timezone(self, timezone: str):
def validate_timezone(self, timezone: str, **kwargs):
try:
pytz.timezone(timezone)
except pytz.UnknownTimeZoneError:
Expand Down
5 changes: 4 additions & 1 deletion flexmeasures_weather/cli/tests/test_get_forecasts.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ def test_get_weather_forecasts_no_close_sensors(
with caplog.at_level(logging.WARNING):
result = runner.invoke(
collect_weather_data,
["--location", f"{weather_station.latitude-5},{weather_station.longitude}"],
[
"--location",
f"{weather_station.latitude - 5},{weather_station.longitude}",
],
)
print(result.output)
assert "Reported task get-weather-forecasts status as True" in result.output
Expand Down
40 changes: 37 additions & 3 deletions flexmeasures_weather/utils/modeling.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,61 @@
else:
SOURCE_TYPE = "forecaster"

FM_SUPPORTS_ACCOUNT_LINKED_SOURCES = version.parse(
flexmeasures_version
) >= version.parse("0.32")

if FM_SUPPORTS_ACCOUNT_LINKED_SOURCES:
from flexmeasures import Account
else:
Account = None


def get_or_create_weather_account():
"""Make sure we have an account for the weather provider service."""
if Account is None:
raise RuntimeError(
"FlexMeasures Account model is unavailable before FlexMeasures 0.32."
)
account_name = current_app.config.get(
"WEATHER_DATA_SOURCE_NAME", DEFAULT_DATA_SOURCE_NAME
)
weather_account = Account.query.filter(
Account.name == account_name,
).one_or_none()
if weather_account is None:
weather_account = Account(name=account_name)
db.session.add(weather_account)
db.session.flush()
return weather_account


def get_or_create_owm_data_source() -> Source:
"""Make sure we have an data source"""
return get_or_create_source(
"""Make sure we have a weather provider data source of the configured type."""
source_kwargs = dict(
source=current_app.config.get(
"WEATHER_DATA_SOURCE_NAME", DEFAULT_DATA_SOURCE_NAME
),
source_type=SOURCE_TYPE,
flush=False,
)
if FM_SUPPORTS_ACCOUNT_LINKED_SOURCES:
source_kwargs["account"] = get_or_create_weather_account()
return get_or_create_source(**source_kwargs)


def get_or_create_owm_data_source_for_derived_data() -> Source:
owm_source_name = current_app.config.get(
"WEATHER_DATA_SOURCE_NAME", DEFAULT_DATA_SOURCE_NAME
)
return get_or_create_source(
source_kwargs = dict(
source=f"FlexMeasures {owm_source_name}",
source_type=SOURCE_TYPE,
flush=False,
)
if FM_SUPPORTS_ACCOUNT_LINKED_SOURCES:
source_kwargs["account"] = get_or_create_weather_account()
return get_or_create_source(**source_kwargs)


def get_or_create_weather_station_type() -> GenericAssetType:
Expand Down
188 changes: 186 additions & 2 deletions flexmeasures_weather/utils/tests/test_modeling.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,194 @@
from types import SimpleNamespace

import pytest

from flexmeasures import Asset

from flexmeasures_weather import DEFAULT_WEATHER_STATION_NAME
from flexmeasures_weather.utils.modeling import get_or_create_weather_station
import flexmeasures_weather.utils.modeling as modeling
from flexmeasures_weather import DEFAULT_DATA_SOURCE_NAME, DEFAULT_WEATHER_STATION_NAME
from flexmeasures_weather.utils.modeling import (
FM_SUPPORTS_ACCOUNT_LINKED_SOURCES,
SOURCE_TYPE,
get_or_create_owm_data_source,
get_or_create_owm_data_source_for_derived_data,
get_or_create_weather_account,
get_or_create_weather_station,
)


def test_creating_two_weather_stations(fresh_db):
get_or_create_weather_station(50, 40)
get_or_create_weather_station(40, 50)
assert Asset.query.filter(Asset.name == DEFAULT_WEATHER_STATION_NAME).count() == 2


# The version-branch tests below still use monkeypatching to isolate source
# creation side effects without requiring multiple FlexMeasures installs.
@pytest.mark.skipif(
not FM_SUPPORTS_ACCOUNT_LINKED_SOURCES,
reason="Weather source accounts are only supported on FlexMeasures >= 0.32.",
)
def test_get_or_create_weather_account(fresh_db):
weather_account = get_or_create_weather_account()

assert weather_account.name == DEFAULT_DATA_SOURCE_NAME
assert (
modeling.Account.query.filter(
modeling.Account.name == weather_account.name
).count()
== 1
)


@pytest.mark.skipif(
not FM_SUPPORTS_ACCOUNT_LINKED_SOURCES,
reason="Account-linked weather sources are only supported on FlexMeasures >= 0.32.",
)
def test_get_or_create_owm_data_source_registers_weather_source_on_weather_account(
fresh_db,
):
data_source = get_or_create_owm_data_source()

assert data_source.type == SOURCE_TYPE
assert data_source.account is not None
assert data_source.account.name == data_source.name


@pytest.mark.skipif(
not FM_SUPPORTS_ACCOUNT_LINKED_SOURCES,
reason="Account-linked weather sources are only supported on FlexMeasures >= 0.32.",
)
def test_get_or_create_owm_data_source_for_derived_data_uses_weather_account(fresh_db):
derived_data_source = get_or_create_owm_data_source_for_derived_data()

assert derived_data_source.type == SOURCE_TYPE
assert derived_data_source.account is not None
assert derived_data_source.account.name == DEFAULT_DATA_SOURCE_NAME


@pytest.mark.skipif(
not FM_SUPPORTS_ACCOUNT_LINKED_SOURCES,
reason="Account-linked weather sources are only supported on FlexMeasures >= 0.32.",
)
def test_get_or_create_owm_data_source_passes_weather_account_when_supported(
fresh_db, monkeypatch
):
captured_kwargs = {}

def fake_get_or_create_source(source, source_type, account, flush):
captured_kwargs.update(
dict(
source=source,
source_type=source_type,
account=account,
flush=flush,
)
)
return SimpleNamespace(type=source_type, account=account)

monkeypatch.setattr(
"flexmeasures_weather.utils.modeling.get_or_create_source",
fake_get_or_create_source,
)

data_source = get_or_create_owm_data_source()

assert data_source.type == SOURCE_TYPE
assert captured_kwargs["account"].name == DEFAULT_DATA_SOURCE_NAME


@pytest.mark.skipif(
not FM_SUPPORTS_ACCOUNT_LINKED_SOURCES,
reason="Account-linked weather sources are only supported on FlexMeasures >= 0.32.",
)
def test_get_or_create_owm_derived_data_source_passes_weather_account_when_supported(
fresh_db, monkeypatch
):
captured_kwargs = {}

def fake_get_or_create_source(source, source_type, account, flush):
captured_kwargs.update(
dict(
source=source,
source_type=source_type,
account=account,
flush=flush,
)
)
return SimpleNamespace(type=source_type, account=account)

monkeypatch.setattr(
"flexmeasures_weather.utils.modeling.get_or_create_source",
fake_get_or_create_source,
)

data_source = get_or_create_owm_data_source_for_derived_data()

assert data_source.type == SOURCE_TYPE
assert captured_kwargs["account"].name == DEFAULT_DATA_SOURCE_NAME


@pytest.mark.skipif(
FM_SUPPORTS_ACCOUNT_LINKED_SOURCES,
reason="Legacy source creation without accounts is only used on FlexMeasures < 0.32.",
)
def test_get_or_create_owm_data_source_omits_account_when_not_supported(monkeypatch):
captured_kwargs = {}

def fake_get_or_create_source(source, source_type, flush):
captured_kwargs.update(
dict(
source=source,
source_type=source_type,
flush=flush,
)
)
return SimpleNamespace(type=source_type, name=source)

monkeypatch.setattr(
"flexmeasures_weather.utils.modeling.get_or_create_source",
fake_get_or_create_source,
)

data_source = get_or_create_owm_data_source()

assert data_source.type == SOURCE_TYPE
assert captured_kwargs == {
"source": DEFAULT_DATA_SOURCE_NAME,
"source_type": SOURCE_TYPE,
"flush": False,
}


@pytest.mark.skipif(
FM_SUPPORTS_ACCOUNT_LINKED_SOURCES,
reason="Legacy source creation without accounts is only used on FlexMeasures < 0.32.",
)
def test_get_or_create_owm_derived_data_source_omits_account_when_not_supported(
monkeypatch,
):
captured_kwargs = {}

def fake_get_or_create_source(source, source_type, flush):
captured_kwargs.update(
dict(
source=source,
source_type=source_type,
flush=flush,
)
)
return SimpleNamespace(type=source_type, name=source)

monkeypatch.setattr(
"flexmeasures_weather.utils.modeling.get_or_create_source",
fake_get_or_create_source,
)

data_source = get_or_create_owm_data_source_for_derived_data()

assert data_source.type == SOURCE_TYPE
assert captured_kwargs == {
"source": f"FlexMeasures {DEFAULT_DATA_SOURCE_NAME}",
"source_type": SOURCE_TYPE,
"flush": False,
}
2 changes: 1 addition & 1 deletion flexmeasures_weather/utils/weather.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ def call_api(
Exception: If an invalid weather provider is configured.
"""

provider = str(current_app.config.get("WEATHER_PROVIDER", ""))
provider = str(current_app.config.get("WEATHER_PROVIDER", "OWM"))
if provider not in ["OWM", "WAPI"]:
raise Exception(
"Invalid provider name. Please set WEATHER_PROVIDER setting in config file to either OWM or WAPI, the two permissible options."
Expand Down
Loading