diff --git a/docs/sphinx/source/reference/forecasting.rst b/docs/sphinx/source/reference/forecasting.rst index ff4df7ed4d..8b1e125c8a 100644 --- a/docs/sphinx/source/reference/forecasting.rst +++ b/docs/sphinx/source/reference/forecasting.rst @@ -3,6 +3,12 @@ Forecasting =========== +.. warning:: + + All functionality in the ``pvlib.forecast`` module is deprecated as of + pvlib v0.9.1. For details, see :ref:`forecasts`. + + Forecast models --------------- diff --git a/docs/sphinx/source/user_guide/forecasts.rst b/docs/sphinx/source/user_guide/forecasts.rst index a89904eccd..d61b40387a 100644 --- a/docs/sphinx/source/user_guide/forecasts.rst +++ b/docs/sphinx/source/user_guide/forecasts.rst @@ -4,6 +4,25 @@ Forecasting *********** +.. warning:: + + The ``pvlib.forecast`` module is deprecated as of version ``0.9.1``. + + Because none of the current pvlib team members are able to continue + maintaining it, the functionality in ``pvlib.forecast`` is deprecated + and will be removed without replacement in a future version. If you + are interested in maintaining this functionality, please let us know. + + You can fetch forecast data yourself using ``siphon`` (see the + docs below this warning) and the code from pvlib v0.9.0 as a reference: + https://github.com/pvlib/pvlib-python/blob/v0.9.0/pvlib/forecast.py + + The `Solar Forecast Arbiter Core + `_ + offers similar (and more robust) forecast processing functionality + and may be a suitable replacement for some users. + + pvlib python provides a set of functions and classes that make it easy to obtain weather forecast data and convert that data into a PV power forecast. Users can retrieve standardized weather forecast data relevant diff --git a/docs/sphinx/source/whatsnew/v0.9.1.rst b/docs/sphinx/source/whatsnew/v0.9.1.rst index a5072c2e64..4fac13dc8c 100644 --- a/docs/sphinx/source/whatsnew/v0.9.1.rst +++ b/docs/sphinx/source/whatsnew/v0.9.1.rst @@ -16,6 +16,8 @@ Deprecations :py:meth:`pvlib.modelchain.ModelChain.with_sapm` for alternative simplified :py:class:`~pvlib.modelchain.ModelChain` interfaces, although note that the inputs do not directly translate. (:pull:`1401`) +* All functionality in the ``pvlib.forecast`` module is deprecated. + For details, see :ref:`forecasts`. (:issue:`1057`, :pull:`1426`) Enhancements ~~~~~~~~~~~~ diff --git a/pvlib/forecast.py b/pvlib/forecast.py index 634ae75ab2..ce80e0ad74 100644 --- a/pvlib/forecast.py +++ b/pvlib/forecast.py @@ -15,6 +15,7 @@ from siphon.ncss import NCSS import warnings +from pvlib._deprecation import deprecated warnings.warn( @@ -22,7 +23,15 @@ 'The API may change, the functionality may be consolidated into an io ' 'module, or the module may be separated into its own package.') +_forecast_deprecated = deprecated( + since='0.9.1', + removal='a future release', + addendum='For details, see https://pvlib-python.readthedocs.io/en/stable/user_guide/forecasts.html' # noqa: E501 +) +# don't decorate the base class to prevent the subclasses from showing +# duplicate warnings: +# @_forecast_deprecated class ForecastModel: """ An object for querying and holding forecast model information for @@ -684,6 +693,7 @@ def gust_to_speed(self, data, scaling=1/1.4): return wind_speed +@_forecast_deprecated class GFS(ForecastModel): """ Subclass of the ForecastModel class representing GFS @@ -785,6 +795,7 @@ def process_data(self, data, cloud_cover='total_clouds', **kwargs): return data[self.output_variables] +@_forecast_deprecated class HRRR_ESRL(ForecastModel): # noqa: N801 """ Subclass of the ForecastModel class representing @@ -875,6 +886,7 @@ def process_data(self, data, cloud_cover='total_clouds', **kwargs): return data[self.output_variables] +@_forecast_deprecated class NAM(ForecastModel): """ Subclass of the ForecastModel class representing NAM @@ -956,6 +968,7 @@ def process_data(self, data, cloud_cover='total_clouds', **kwargs): return data[self.output_variables] +@_forecast_deprecated class HRRR(ForecastModel): """ Subclass of the ForecastModel class representing HRRR @@ -1044,6 +1057,7 @@ def process_data(self, data, cloud_cover='total_clouds', **kwargs): return data[self.output_variables] +@_forecast_deprecated class NDFD(ForecastModel): """ Subclass of the ForecastModel class representing NDFD forecast @@ -1112,6 +1126,7 @@ def process_data(self, data, **kwargs): return data[self.output_variables] +@_forecast_deprecated class RAP(ForecastModel): """ Subclass of the ForecastModel class representing RAP forecast model. diff --git a/pvlib/tests/test_forecast.py b/pvlib/tests/test_forecast.py index db2ed75154..4382666317 100644 --- a/pvlib/tests/test_forecast.py +++ b/pvlib/tests/test_forecast.py @@ -14,6 +14,8 @@ ) from .conftest import RERUNS, RERUNS_DELAY +from pvlib._deprecation import pvlibDeprecationWarning + pytestmark = pytest.mark.skipif(not has_siphon, reason='requires siphon') @@ -52,7 +54,8 @@ @requires_siphon @pytest.fixture(scope='module', params=_modelclasses) def model(request): - amodel = request.param() + with pytest.warns(pvlibDeprecationWarning): + amodel = request.param() try: raw_data = amodel.get_data(_latitude, _longitude, _start, _end) except Exception as e: @@ -90,7 +93,8 @@ def test_process_data(model): def test_bad_kwarg_get_data(): # For more information on why you would want to pass an unknown keyword # argument, see Github issue #745. - amodel = NAM() + with pytest.warns(pvlibDeprecationWarning): + amodel = NAM() data = amodel.get_data(_latitude, _longitude, _start, _end, bad_kwarg=False) assert not data.empty @@ -103,7 +107,8 @@ def test_bad_kwarg_get_data(): def test_bad_kwarg_get_processed_data(): # For more information on why you would want to pass an unknown keyword # argument, see Github issue #745. - amodel = NAM() + with pytest.warns(pvlibDeprecationWarning): + amodel = NAM() data = amodel.get_processed_data(_latitude, _longitude, _start, _end, bad_kwarg=False) assert not data.empty @@ -114,7 +119,8 @@ def test_bad_kwarg_get_processed_data(): @pytest.mark.remote_data @pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY) def test_how_kwarg_get_processed_data(): - amodel = NAM() + with pytest.warns(pvlibDeprecationWarning): + amodel = NAM() data = amodel.get_processed_data(_latitude, _longitude, _start, _end, how='clearsky_scaling') assert not data.empty @@ -125,7 +131,8 @@ def test_how_kwarg_get_processed_data(): @pytest.mark.remote_data @pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY) def test_vert_level(): - amodel = NAM() + with pytest.warns(pvlibDeprecationWarning): + amodel = NAM() vert_level = 5000 amodel.get_processed_data(_latitude, _longitude, _start, _end, vert_level=vert_level) @@ -136,7 +143,8 @@ def test_vert_level(): @pytest.mark.remote_data @pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY) def test_datetime(): - amodel = NAM() + with pytest.warns(pvlibDeprecationWarning): + amodel = NAM() start = datetime.now(tz=timezone.utc) end = start + timedelta(days=1) amodel.get_processed_data(_latitude, _longitude, start, end) @@ -147,7 +155,8 @@ def test_datetime(): @pytest.mark.remote_data @pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY) def test_queryvariables(): - amodel = GFS() + with pytest.warns(pvlibDeprecationWarning): + amodel = GFS() new_variables = ['u-component_of_wind_height_above_ground'] data = amodel.get_data(_latitude, _longitude, _start, _end, query_variables=new_variables) @@ -156,16 +165,19 @@ def test_queryvariables(): @requires_siphon def test_latest(): - GFS(set_type='latest') + with pytest.warns(pvlibDeprecationWarning): + GFS(set_type='latest') @requires_siphon def test_full(): - GFS(set_type='full') + with pytest.warns(pvlibDeprecationWarning): + GFS(set_type='full') def test_temp_convert(): - amodel = GFS() + with pytest.warns(pvlibDeprecationWarning): + amodel = GFS() data = pd.DataFrame({'temp_air': [273.15]}) data['temp_air'] = amodel.kelvin_to_celsius(data['temp_air']) @@ -183,27 +195,31 @@ def test_temp_convert(): def test_set_location(): - amodel = GFS() + with pytest.warns(pvlibDeprecationWarning): + amodel = GFS() latitude, longitude = 32.2, -110.9 time = 'UTC' amodel.set_location(time, latitude, longitude) def test_set_query_time_range_tzfail(): - amodel = GFS() + with pytest.warns(pvlibDeprecationWarning): + amodel = GFS() with pytest.raises(TypeError): amodel.set_query_time_range(datetime.now(), datetime.now()) def test_cloud_cover_to_transmittance_linear(): - amodel = GFS() + with pytest.warns(pvlibDeprecationWarning): + amodel = GFS() assert_allclose(amodel.cloud_cover_to_transmittance_linear(0), 0.75) assert_allclose(amodel.cloud_cover_to_transmittance_linear(100), 0.0) assert_allclose(amodel.cloud_cover_to_transmittance_linear(0, 0.5), 0.5) def test_cloud_cover_to_ghi_linear(): - amodel = GFS() + with pytest.warns(pvlibDeprecationWarning): + amodel = GFS() ghi_clear = 1000 offset = 25 out = amodel.cloud_cover_to_ghi_linear(0, ghi_clear, offset=offset)