Skip to content

Commit 7b7e3c7

Browse files
Standardize pvgis_hourly to return (data,meta) (#2462)
* Add inputs to metadata for get_pvgis_hourly * Update doc string for read_pvgis_hourly * Update tests * Fix linter * Add whatsnew entry * Apply suggestions from code review Co-authored-by: Kevin Anderson <[email protected]> * Add versionchanged entries --------- Co-authored-by: Kevin Anderson <[email protected]>
1 parent e711e9e commit 7b7e3c7

File tree

3 files changed

+54
-27
lines changed

3 files changed

+54
-27
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ v0.12.1 (XXXX, 2025)
66

77
Breaking Changes
88
~~~~~~~~~~~~~~~~
9-
9+
* The functions :py:func:`~pvlib.iotools.read_pvgis_hourly` and
10+
:py:func:`~pvlib.iotools.get_pvgis_hourly` now return ``(data,meta)``
11+
following the iotools convention instead of ``(data,inputs,meta)``.
12+
The ``inputs`` dictionary is now included in ``meta``, which
13+
has changed structure to accommodate it. (:pull:`2462`)
1014

1115
Deprecations
1216
~~~~~~~~~~~~

pvlib/iotools/pvgis.py

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ def get_pvgis_hourly(latitude, longitude, start=None, end=None,
5757
5858
PVGIS data is freely available at [1]_.
5959
60+
.. versionchanged:: 0.13.0
61+
The function now returns two items ``(data,meta)``. Previous
62+
versions of this function returned three elements
63+
``(data,inputs,meta)``. The ``inputs`` dictionary is now included in
64+
``meta``, which has changed structure to accommodate it.
65+
6066
Parameters
6167
----------
6268
latitude: float
@@ -130,8 +136,6 @@ def get_pvgis_hourly(latitude, longitude, start=None, end=None,
130136
-------
131137
data : pandas.DataFrame
132138
Time-series of hourly data, see Notes for fields
133-
inputs : dict
134-
Dictionary of the request input parameters
135139
metadata : dict
136140
Dictionary containing metadata
137141
@@ -189,7 +193,7 @@ def get_pvgis_hourly(latitude, longitude, start=None, end=None,
189193
Examples
190194
--------
191195
>>> # Retrieve two years of irradiance data from PVGIS:
192-
>>> data, meta, inputs = pvlib.iotools.get_pvgis_hourly( # doctest: +SKIP
196+
>>> data, meta = pvlib.iotools.get_pvgis_hourly( # doctest: +SKIP
193197
>>> latitude=45, longitude=8, start=2015, end=2016) # doctest: +SKIP
194198
195199
References
@@ -241,28 +245,33 @@ def get_pvgis_hourly(latitude, longitude, start=None, end=None,
241245

242246

243247
def _parse_pvgis_hourly_json(src, map_variables):
244-
inputs = src['inputs']
245-
metadata = src['meta']
248+
metadata = src['meta'].copy()
249+
# Override the "inputs" in metadata
250+
metadata['inputs'] = src['inputs']
251+
# Re-add the inputs in metadata one-layer down
252+
metadata['inputs']['descriptions'] = src['meta']['inputs']
246253
data = pd.DataFrame(src['outputs']['hourly'])
247254
data.index = pd.to_datetime(data['time'], format='%Y%m%d:%H%M', utc=True)
248255
data = data.drop('time', axis=1)
249256
data = data.astype(dtype={'Int': 'int'}) # The 'Int' column to be integer
250257
if map_variables:
251258
data = data.rename(columns=VARIABLE_MAP)
252-
return data, inputs, metadata
259+
return data, metadata
253260

254261

255262
def _parse_pvgis_hourly_csv(src, map_variables):
256263
# The first 4 rows are latitude, longitude, elevation, radiation database
257-
inputs = {}
264+
metadata = {'inputs': {}}
265+
# 'location' metadata
258266
# 'Latitude (decimal degrees): 45.000\r\n'
259-
inputs['latitude'] = float(src.readline().split(':')[1])
267+
metadata['inputs']['latitude'] = float(src.readline().split(':')[1])
260268
# 'Longitude (decimal degrees): 8.000\r\n'
261-
inputs['longitude'] = float(src.readline().split(':')[1])
269+
metadata['inputs']['longitude'] = float(src.readline().split(':')[1])
262270
# Elevation (m): 1389.0\r\n
263-
inputs['elevation'] = float(src.readline().split(':')[1])
271+
metadata['inputs']['elevation'] = float(src.readline().split(':')[1])
264272
# 'Radiation database: \tPVGIS-SARAH\r\n'
265-
inputs['radiation_database'] = src.readline().split(':')[1].strip()
273+
metadata['inputs']['radiation_database'] = \
274+
src.readline().split(':')[1].strip()
266275
# Parse through the remaining metadata section (the number of lines for
267276
# this section depends on the requested parameters)
268277
while True:
@@ -273,7 +282,7 @@ def _parse_pvgis_hourly_csv(src, map_variables):
273282
break
274283
# Only retrieve metadata from non-empty lines
275284
elif line.strip() != '':
276-
inputs[line.split(':')[0]] = line.split(':')[1].strip()
285+
metadata['inputs'][line.split(':')[0]] = line.split(':')[1].strip()
277286
elif line == '': # If end of file is reached
278287
raise ValueError('No data section was detected. File has probably '
279288
'been modified since being downloaded from PVGIS')
@@ -295,16 +304,23 @@ def _parse_pvgis_hourly_csv(src, map_variables):
295304
# integer. It is necessary to convert to float, before converting to int
296305
data = data.astype(float).astype(dtype={'Int': 'int'})
297306
# Generate metadata dictionary containing description of parameters
298-
metadata = {}
307+
metadata['descriptions'] = {}
299308
for line in src.readlines():
300309
if ':' in line:
301-
metadata[line.split(':')[0]] = line.split(':')[1].strip()
302-
return data, inputs, metadata
310+
metadata['descriptions'][line.split(':')[0]] = \
311+
line.split(':')[1].strip()
312+
return data, metadata
303313

304314

305315
def read_pvgis_hourly(filename, pvgis_format=None, map_variables=True):
306316
"""Read a PVGIS hourly file.
307317
318+
.. versionchanged:: 0.13.0
319+
The function now returns two items ``(data,meta)``. Previous
320+
versions of this function returned three elements
321+
``(data,inputs,meta)``. The ``inputs`` dictionary is now included in
322+
``meta``, which has changed structure to accommodate it.
323+
308324
Parameters
309325
----------
310326
filename : str, pathlib.Path, or file-like buffer
@@ -323,8 +339,6 @@ def read_pvgis_hourly(filename, pvgis_format=None, map_variables=True):
323339
-------
324340
data : pandas.DataFrame
325341
the time series data
326-
inputs : dict
327-
the inputs
328342
metadata : dict
329343
metadata
330344

tests/iotools/test_pvgis.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,16 @@
132132
'WS10m': {'description': '10-m total wind speed', 'units': 'm/s'}, # noqa: E501
133133
'Int': {'description': '1 means solar radiation values are reconstructed'}}}}} # noqa: E501
134134

135+
# Reformat the metadata as implemented in #2462
136+
descriptions_csv = metadata_radiation_csv.copy()
137+
metadata_radiation_csv = {}
138+
metadata_radiation_csv['descriptions'] = descriptions_csv
139+
metadata_radiation_csv['inputs'] = inputs_radiation_csv
140+
141+
descriptions_json = metadata_pv_json['inputs']
142+
metadata_pv_json['inputs'] = inputs_pv_json
143+
metadata_pv_json['inputs']['descriptions'] = descriptions_json
144+
135145

136146
def generate_expected_dataframe(values, columns, index):
137147
"""Create dataframe from arrays of values, columns and index, in order to
@@ -175,25 +185,24 @@ def expected_pv_json_mapped():
175185
# Test read_pvgis_hourly function using two different files with different
176186
# input arguments (to test variable mapping and pvgis_format)
177187
# pytest request.getfixturevalue is used to simplify the input arguments
178-
@pytest.mark.parametrize('testfile,expected_name,metadata_exp,inputs_exp,map_variables,pvgis_format', [ # noqa: E501
188+
@pytest.mark.parametrize('testfile,expected_name,metadata_exp,map_variables,pvgis_format', [ # noqa: E501
179189
(testfile_radiation_csv, 'expected_radiation_csv', metadata_radiation_csv,
180-
inputs_radiation_csv, False, None),
190+
False, None),
181191
(testfile_radiation_csv, 'expected_radiation_csv_mapped',
182-
metadata_radiation_csv, inputs_radiation_csv, True, 'csv'),
183-
(testfile_pv_json, 'expected_pv_json', metadata_pv_json, inputs_pv_json,
192+
metadata_radiation_csv, True, 'csv'),
193+
(testfile_pv_json, 'expected_pv_json', metadata_pv_json,
184194
False, None),
185195
(testfile_pv_json, 'expected_pv_json_mapped', metadata_pv_json,
186-
inputs_pv_json, True, 'json')])
196+
True, 'json')])
187197
def test_read_pvgis_hourly(testfile, expected_name, metadata_exp,
188-
inputs_exp, map_variables, pvgis_format, request):
198+
map_variables, pvgis_format, request):
189199
# Get expected dataframe from fixture
190200
expected = request.getfixturevalue(expected_name)
191201
# Read data from file
192-
out, inputs, metadata = read_pvgis_hourly(
202+
out, metadata = read_pvgis_hourly(
193203
testfile, map_variables=map_variables, pvgis_format=pvgis_format)
194204
# Assert whether dataframe, metadata, and inputs are as expected
195205
assert_frame_equal(out, expected)
196-
assert inputs == inputs_exp
197206
assert metadata == metadata_exp
198207

199208

@@ -248,7 +257,7 @@ def test_get_pvgis_hourly(requests_mock, testfile, expected_name, args,
248257
# inputs are passing on correctly
249258
requests_mock.get(url_test, text=mock_response)
250259
# Make API call - an error is raised if requested URI does not match
251-
out, inputs, metadata = get_pvgis_hourly(
260+
out, metadata = get_pvgis_hourly(
252261
latitude=45, longitude=8, map_variables=map_variables, **args)
253262
# Get expected dataframe from fixture
254263
expected = request.getfixturevalue(expected_name)

0 commit comments

Comments
 (0)