Skip to content

Commit 35c9e61

Browse files
authored
Make meteonorm remote_data tests robust to data changes (#2810)
The meteonorm iotools tests pinned specific values, the full column list, and exact column counts returned by the live API. Meteonorm regularly updates their weather dataset and adds parameters to the 'all' set, neither of which is a breaking API change, but both cause these tests to fail spuriously (e.g. new 'surface_tilt'/'surface_azimuth' columns broke the exact-list assertion). Assert only what pvlib controls, not what meteonorm's dataset controls: - realtime: check the known columns are a subset of the response and that the column count is at least as large, instead of exact equality. - training/tmy: drop the golden-value assert_frame_equal comparisons (and their fixtures); check columns, numeric dtype, and that at most 20% of values are NaN (the guarantee meteonorm provides). Row counts and index checks are kept since they are determined by the requested time range, not the dataset.
1 parent 7613f6d commit 35c9e61

2 files changed

Lines changed: 19 additions & 65 deletions

File tree

docs/sphinx/source/whatsnew/v0.15.3.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,6 @@ Contributors
5050
~~~~~~~~~~~~
5151
* Eesh Saxena (:ghuser:`eeshsaxena`)
5252
* Karl Hill (:ghuser:`karlhillx`)
53+
* Mathias Aschwanden (:ghuser:`maschwanden`)
5354
* Yonry Zhu (:ghuser:`yonryzhu`)
5455
* Darshan Gowda (:ghuser:`dgowdaan-cmyk`)

tests/iotools/test_meteonorm.py

Lines changed: 18 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -61,30 +61,6 @@ def expected_meteonorm_index():
6161
return expected_meteonorm_index
6262

6363

64-
@pytest.fixture
65-
def expected_meteonorm_data():
66-
# The first 12 rows of data
67-
columns = ['ghi', 'global_horizontal_irradiance_with_shading']
68-
expected = [
69-
[0.0, 0.0],
70-
[0.0, 0.0],
71-
[0.0, 0.0],
72-
[0.0, 0.0],
73-
[0.0, 0.0],
74-
[0.0, 0.0],
75-
[0.0, 0.0],
76-
[3.75, 3.74],
77-
[57.25, 57.20],
78-
[149.0, 148.96],
79-
[242.25, 242.24],
80-
[228.0, 227.98],
81-
]
82-
index = pd.date_range('2025-01-01 00:30', periods=12, freq='1h', tz='UTC')
83-
index.freq = None
84-
expected = pd.DataFrame(expected, index=index, columns=columns)
85-
return expected
86-
87-
8864
@pytest.fixture
8965
def expected_columns_all():
9066
columns = [
@@ -114,8 +90,7 @@ def expected_columns_all():
11490
@pytest.mark.remote_data
11591
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
11692
def test_get_meteonorm_training(
117-
demo_api_key, demo_url, expected_meta, expected_meteonorm_index,
118-
expected_meteonorm_data):
93+
demo_api_key, demo_url, expected_meta, expected_meteonorm_index):
11994
data, meta = pvlib.iotools.get_meteonorm_observation_training(
12095
latitude=50, longitude=10,
12196
start='2025-01-01', end='2026-01-01',
@@ -128,10 +103,12 @@ def test_get_meteonorm_training(
128103
for key in ['version', 'commit']:
129104
assert key in meta # value changes, so only check presence
130105
pd.testing.assert_index_equal(data.index, expected_meteonorm_index)
131-
# meteonorm API only guarantees similar, not identical, results between
132-
# calls. so we allow a small amount of variation with atol.
133-
pd.testing.assert_frame_equal(data.iloc[:12], expected_meteonorm_data,
134-
check_exact=False, atol=1)
106+
# don't pin values: meteonorm may update the dataset without it being a
107+
# breaking change. check parsing instead.
108+
assert list(data.columns) == \
109+
['ghi', 'global_horizontal_irradiance_with_shading']
110+
assert data.dtypes.map(pd.api.types.is_numeric_dtype).all()
111+
assert (data.isna().mean() <= 0.2).all() # meteonorm guarantees <=20% NaN
135112

136113

137114
@pytest.mark.remote_data
@@ -156,8 +133,11 @@ def test_get_meteonorm_realtime(demo_api_key, demo_url, expected_columns_all):
156133
assert meta['surface_tilt'] == 20
157134
assert meta['surface_azimuth'] == 10
158135

159-
assert list(data.columns) == expected_columns_all
160-
assert data.shape == (241, 19)
136+
# meteonorm may add parameters to 'all' at any time, so only check that
137+
# the columns we know about are present, not that the set matches exactly.
138+
assert set(expected_columns_all).issubset(data.columns)
139+
assert data.shape[0] == 241 # row count is determined by the time range
140+
assert data.shape[1] >= len(expected_columns_all)
161141
# can't test the specific index as it varies due to the
162142
# use of pd.Timestamp.now
163143
assert type(data.index) is pd.core.indexes.interval.IntervalIndex
@@ -259,38 +239,10 @@ def expected_meteonorm_tmy_meta():
259239
return meta
260240

261241

262-
@pytest.fixture
263-
def expected_meteonorm_tmy_data():
264-
# The first 12 rows of data
265-
columns = ['diffuse_horizontal_irradiance']
266-
expected = [
267-
[0.],
268-
[0.],
269-
[0.],
270-
[0.],
271-
[0.],
272-
[0.],
273-
[0.],
274-
[0.],
275-
[9.07],
276-
[8.44],
277-
[86.64],
278-
[110.44],
279-
]
280-
index = pd.date_range(
281-
'2030-01-01', periods=12, freq='1h', tz=3600)
282-
index.freq = None
283-
interval_index = pd.IntervalIndex.from_arrays(
284-
index, index + pd.Timedelta(hours=1), closed='left')
285-
expected = pd.DataFrame(expected, index=interval_index, columns=columns)
286-
return expected
287-
288-
289242
@pytest.mark.remote_data
290243
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
291244
def test_get_meteonorm_tmy(
292-
demo_api_key, demo_url, expected_meteonorm_tmy_meta,
293-
expected_meteonorm_tmy_data):
245+
demo_api_key, demo_url, expected_meteonorm_tmy_meta):
294246
data, meta = pvlib.iotools.get_meteonorm_tmy(
295247
latitude=50, longitude=10,
296248
api_key=demo_api_key,
@@ -312,10 +264,11 @@ def test_get_meteonorm_tmy(
312264
assert meta.items() >= expected_meteonorm_tmy_meta.items()
313265
for key in ['version', 'commit']:
314266
assert key in meta # value changes, so only check presence
315-
# meteonorm API only guarantees similar, not identical, results between
316-
# calls. so we allow a small amount of variation with atol.
317-
pd.testing.assert_frame_equal(data.iloc[:12], expected_meteonorm_tmy_data,
318-
check_exact=False, atol=1)
267+
# don't pin values: meteonorm may update the dataset without it being a
268+
# breaking change. check parsing instead.
269+
assert list(data.columns) == ['diffuse_horizontal_irradiance']
270+
assert data.dtypes.map(pd.api.types.is_numeric_dtype).all()
271+
assert (data.isna().mean() <= 0.2).all() # meteonorm guarantees <=20% NaN
319272

320273

321274
@fail_on_pvlib_version('0.16.0')

0 commit comments

Comments
 (0)