diff --git a/.gitignore b/.gitignore index 2a016bb9228..fdf1b12d706 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ pip-log.txt .tox nosetests.xml .cache +.mypy_cache .ropeproject/ .tags* .testmon* diff --git a/README.rst b/README.rst index f69f7d95c31..6dbf774549d 100644 --- a/README.rst +++ b/README.rst @@ -97,7 +97,7 @@ to supporting the open source scientific computing community. If you like Xarray and want to support our mission, please consider making a donation_ to support our efforts. -.. _donation: https://www.flipcause.com/secure/cause_pdetails/NDE2NTU= +.. _donation: https://numfocus.salsalabs.org/donate-to-xarray/ History ------- diff --git a/ci/requirements-py35-min.yml b/ci/requirements-py35-min.yml index 1f41d0d9dc1..b140d81b959 100644 --- a/ci/requirements-py35-min.yml +++ b/ci/requirements-py35-min.yml @@ -1,6 +1,6 @@ name: test_env dependencies: - - python=3.5 + - python=3.5.0 - pytest - flake8 - mock diff --git a/doc/data-structures.rst b/doc/data-structures.rst index a8887471ec7..5be1f7b4262 100644 --- a/doc/data-structures.rst +++ b/doc/data-structures.rst @@ -353,13 +353,6 @@ setting) variables and attributes: This is particularly useful in an exploratory context, because you can tab-complete these variable names with tools like IPython. -.. warning:: - - We are changing the behavior of iterating over a Dataset the next major - release of xarray, to only include data variables instead of both data - variables and coordinates. In the meantime, prefer iterating over - ``ds.data_vars`` or ``ds.coords``. - Dictionary like methods ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/index.rst b/doc/index.rst index dbe911011cd..002bd102e12 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -52,6 +52,7 @@ Documentation * :doc:`reshaping` * :doc:`combining` * :doc:`time-series` +* :doc:`weather-climate` * :doc:`pandas` * :doc:`io` * :doc:`dask` @@ -70,6 +71,7 @@ Documentation reshaping combining time-series + weather-climate pandas io dask @@ -138,7 +140,7 @@ to supporting the open source scientific computing community. If you like Xarray and want to support our mission, please consider making a donation_ to support our efforts. -.. _donation: https://www.flipcause.com/secure/cause_pdetails/NDE2NTU= +.. _donation: https://numfocus.salsalabs.org/donate-to-xarray/ History diff --git a/doc/io.rst b/doc/io.rst index 0dc5181f9b8..51c747189da 100644 --- a/doc/io.rst +++ b/doc/io.rst @@ -1,11 +1,11 @@ .. _io: -Serialization and IO -==================== +Reading and writing files +========================= xarray supports direct serialization and IO to several file formats, from simple :ref:`io.pickle` files to the more flexible :ref:`io.netcdf` -format. +format (recommended). .. ipython:: python :suppress: @@ -739,11 +739,14 @@ options are listed on the PseudoNetCDF page. .. _PseudoNetCDF: http://github.com/barronh/PseudoNetCDF -Formats supported by Pandas ---------------------------- +CSV and other formats supported by Pandas +----------------------------------------- For more options (tabular formats and CSV files in particular), consider exporting your objects to pandas and using its broad range of `IO tools`_. +For CSV files, one might also consider `xarray_extras`_. + +.. _xarray_extras: https://xarray-extras.readthedocs.io/en/latest/api/csv.html .. _IO tools: http://pandas.pydata.org/pandas-docs/stable/io.html diff --git a/doc/plotting.rst b/doc/plotting.rst index a705c683594..c8f568e516f 100644 --- a/doc/plotting.rst +++ b/doc/plotting.rst @@ -39,6 +39,10 @@ For more extensive plotting applications consider the following projects: data structures for building even complex visualizations easily." Includes native support for xarray objects. +- `hvplot `_: ``hvplot`` makes it very easy to produce + dynamic plots (backed by ``Holoviews`` or ``Geoviews``) by adding a ``hvplot`` + accessor to DataArrays. + - `Cartopy `_: Provides cartographic tools. diff --git a/doc/related-projects.rst b/doc/related-projects.rst index c89e324ff7c..e899022e5d4 100644 --- a/doc/related-projects.rst +++ b/doc/related-projects.rst @@ -13,6 +13,7 @@ Geosciences - `aospy `_: Automated analysis and management of gridded climate data. - `infinite-diff `_: xarray-based finite-differencing, focused on gridded climate/meterology data - `marc_analysis `_: Analysis package for CESM/MARC experiments and output. +- `MetPy `_: A collection of tools in Python for reading, visualizing, and performing calculations with weather data. - `MPAS-Analysis `_: Analysis for simulations produced with Model for Prediction Across Scales (MPAS) components and the Accelerated Climate Model for Energy (ACME). - `OGGM `_: Open Global Glacier Model - `Oocgcm `_: Analysis of large gridded geophysical datasets diff --git a/doc/time-series.rst b/doc/time-series.rst index 3249dad2ec6..53efcd45ba2 100644 --- a/doc/time-series.rst +++ b/doc/time-series.rst @@ -212,140 +212,3 @@ Data that has indices outside of the given ``tolerance`` are set to ``NaN``. For more examples of using grouped operations on a time dimension, see :ref:`toy weather data`. - - -.. _CFTimeIndex: - -Non-standard calendars and dates outside the Timestamp-valid range ------------------------------------------------------------------- - -Through the standalone ``cftime`` library and a custom subclass of -:py:class:`pandas.Index`, xarray supports a subset of the indexing -functionality enabled through the standard :py:class:`pandas.DatetimeIndex` for -dates from non-standard calendars commonly used in climate science or dates -using a standard calendar, but outside the `Timestamp-valid range`_ -(approximately between years 1678 and 2262). - -.. note:: - - As of xarray version 0.11, by default, :py:class:`cftime.datetime` objects - will be used to represent times (either in indexes, as a - :py:class:`~xarray.CFTimeIndex`, or in data arrays with dtype object) if - any of the following are true: - - - The dates are from a non-standard calendar - - Any dates are outside the Timestamp-valid range. - - Otherwise pandas-compatible dates from a standard calendar will be - represented with the ``np.datetime64[ns]`` data type, enabling the use of a - :py:class:`pandas.DatetimeIndex` or arrays with dtype ``np.datetime64[ns]`` - and their full set of associated features. - -For example, you can create a DataArray indexed by a time -coordinate with dates from a no-leap calendar and a -:py:class:`~xarray.CFTimeIndex` will automatically be used: - -.. ipython:: python - - from itertools import product - from cftime import DatetimeNoLeap - dates = [DatetimeNoLeap(year, month, 1) for year, month in - product(range(1, 3), range(1, 13))] - da = xr.DataArray(np.arange(24), coords=[dates], dims=['time'], name='foo') - -xarray also includes a :py:func:`~xarray.cftime_range` function, which enables -creating a :py:class:`~xarray.CFTimeIndex` with regularly-spaced dates. For -instance, we can create the same dates and DataArray we created above using: - -.. ipython:: python - - dates = xr.cftime_range(start='0001', periods=24, freq='MS', calendar='noleap') - da = xr.DataArray(np.arange(24), coords=[dates], dims=['time'], name='foo') - -For data indexed by a :py:class:`~xarray.CFTimeIndex` xarray currently supports: - -- `Partial datetime string indexing`_ using strictly `ISO 8601-format`_ partial - datetime strings: - -.. ipython:: python - - da.sel(time='0001') - da.sel(time=slice('0001-05', '0002-02')) - -- Access of basic datetime components via the ``dt`` accessor (in this case - just "year", "month", "day", "hour", "minute", "second", "microsecond", - "season", "dayofyear", and "dayofweek"): - -.. ipython:: python - - da.time.dt.year - da.time.dt.month - da.time.dt.season - da.time.dt.dayofyear - da.time.dt.dayofweek - -- Group-by operations based on datetime accessor attributes (e.g. by month of - the year): - -.. ipython:: python - - da.groupby('time.month').sum() - -- Interpolation using :py:class:`cftime.datetime` objects: - -.. ipython:: python - - da.interp(time=[DatetimeNoLeap(1, 1, 15), DatetimeNoLeap(1, 2, 15)]) - -- Interpolation using datetime strings: - -.. ipython:: python - - da.interp(time=['0001-01-15', '0001-02-15']) - -- Differentiation: - -.. ipython:: python - - da.differentiate('time') - -- Serialization: - -.. ipython:: python - - da.to_netcdf('example-no-leap.nc') - xr.open_dataset('example-no-leap.nc') - -- And resampling along the time dimension for data indexed by a :py:class:`~xarray.CFTimeIndex`: - -.. ipython:: python - - da.resample(time='81T', closed='right', label='right', base=3).mean() - -.. note:: - - - For some use-cases it may still be useful to convert from - a :py:class:`~xarray.CFTimeIndex` to a :py:class:`pandas.DatetimeIndex`, - despite the difference in calendar types. The recommended way of doing this - is to use the built-in :py:meth:`~xarray.CFTimeIndex.to_datetimeindex` - method: - - .. ipython:: python - :okwarning: - - modern_times = xr.cftime_range('2000', periods=24, freq='MS', calendar='noleap') - da = xr.DataArray(range(24), [('time', modern_times)]) - da - datetimeindex = da.indexes['time'].to_datetimeindex() - da['time'] = datetimeindex - - However in this case one should use caution to only perform operations which - do not depend on differences between dates (e.g. differentiation, - interpolation, or upsampling with resample), as these could introduce subtle - and silent errors due to the difference in calendar types between the dates - encoded in your data and the dates stored in memory. - -.. _Timestamp-valid range: https://pandas.pydata.org/pandas-docs/stable/timeseries.html#timestamp-limitations -.. _ISO 8601-format: https://en.wikipedia.org/wiki/ISO_8601 -.. _partial datetime string indexing: https://pandas.pydata.org/pandas-docs/stable/timeseries.html#partial-string-indexing diff --git a/doc/weather-climate.rst b/doc/weather-climate.rst new file mode 100644 index 00000000000..1950ba62ffb --- /dev/null +++ b/doc/weather-climate.rst @@ -0,0 +1,160 @@ +.. _weather-climate: + +Weather and climate data +======================== + +.. ipython:: python + :suppress: + + import xarray as xr + +``xarray`` can leverage metadata that follows the `Climate and Forecast (CF) conventions`_ if present. Examples include automatic labelling of plots with descriptive names and units if proper metadata is present (see :ref:`plotting`) and support for non-standard calendars used in climate science through the ``cftime`` module (see :ref:`CFTimeIndex`). There are also a number of geosciences-focused projects that build on xarray (see :ref:`related-projects`). + +.. _Climate and Forecast (CF) conventions: http://cfconventions.org + +.. _metpy_accessor: + +CF-compliant coordinate variables +--------------------------------- + +`MetPy`_ adds a ``metpy`` accessor that allows accessing coordinates with appropriate CF metadata using generic names ``x``, ``y``, ``vertical`` and ``time``. There is also a `cartopy_crs` attribute that provides projection information, parsed from the appropriate CF metadata, as a `Cartopy`_ projection object. See `their documentation`_ for more information. + +.. _`MetPy`: https://unidata.github.io/MetPy/dev/index.html +.. _`their documentation`: https://unidata.github.io/MetPy/dev/tutorials/xarray_tutorial.html#coordinates +.. _`Cartopy`: https://scitools.org.uk/cartopy/docs/latest/crs/projections.html + +.. _CFTimeIndex: + +Non-standard calendars and dates outside the Timestamp-valid range +------------------------------------------------------------------ + +Through the standalone ``cftime`` library and a custom subclass of +:py:class:`pandas.Index`, xarray supports a subset of the indexing +functionality enabled through the standard :py:class:`pandas.DatetimeIndex` for +dates from non-standard calendars commonly used in climate science or dates +using a standard calendar, but outside the `Timestamp-valid range`_ +(approximately between years 1678 and 2262). + +.. note:: + + As of xarray version 0.11, by default, :py:class:`cftime.datetime` objects + will be used to represent times (either in indexes, as a + :py:class:`~xarray.CFTimeIndex`, or in data arrays with dtype object) if + any of the following are true: + + - The dates are from a non-standard calendar + - Any dates are outside the Timestamp-valid range. + + Otherwise pandas-compatible dates from a standard calendar will be + represented with the ``np.datetime64[ns]`` data type, enabling the use of a + :py:class:`pandas.DatetimeIndex` or arrays with dtype ``np.datetime64[ns]`` + and their full set of associated features. + +For example, you can create a DataArray indexed by a time +coordinate with dates from a no-leap calendar and a +:py:class:`~xarray.CFTimeIndex` will automatically be used: + +.. ipython:: python + + from itertools import product + from cftime import DatetimeNoLeap + dates = [DatetimeNoLeap(year, month, 1) for year, month in + product(range(1, 3), range(1, 13))] + da = xr.DataArray(np.arange(24), coords=[dates], dims=['time'], name='foo') + +xarray also includes a :py:func:`~xarray.cftime_range` function, which enables +creating a :py:class:`~xarray.CFTimeIndex` with regularly-spaced dates. For +instance, we can create the same dates and DataArray we created above using: + +.. ipython:: python + + dates = xr.cftime_range(start='0001', periods=24, freq='MS', calendar='noleap') + da = xr.DataArray(np.arange(24), coords=[dates], dims=['time'], name='foo') + +For data indexed by a :py:class:`~xarray.CFTimeIndex` xarray currently supports: + +- `Partial datetime string indexing`_ using strictly `ISO 8601-format`_ partial + datetime strings: + +.. ipython:: python + + da.sel(time='0001') + da.sel(time=slice('0001-05', '0002-02')) + +- Access of basic datetime components via the ``dt`` accessor (in this case + just "year", "month", "day", "hour", "minute", "second", "microsecond", + "season", "dayofyear", and "dayofweek"): + +.. ipython:: python + + da.time.dt.year + da.time.dt.month + da.time.dt.season + da.time.dt.dayofyear + da.time.dt.dayofweek + +- Group-by operations based on datetime accessor attributes (e.g. by month of + the year): + +.. ipython:: python + + da.groupby('time.month').sum() + +- Interpolation using :py:class:`cftime.datetime` objects: + +.. ipython:: python + + da.interp(time=[DatetimeNoLeap(1, 1, 15), DatetimeNoLeap(1, 2, 15)]) + +- Interpolation using datetime strings: + +.. ipython:: python + + da.interp(time=['0001-01-15', '0001-02-15']) + +- Differentiation: + +.. ipython:: python + + da.differentiate('time') + +- Serialization: + +.. ipython:: python + + da.to_netcdf('example-no-leap.nc') + xr.open_dataset('example-no-leap.nc') + +- And resampling along the time dimension for data indexed by a :py:class:`~xarray.CFTimeIndex`: + +.. ipython:: python + + da.resample(time='81T', closed='right', label='right', base=3).mean() + +.. note:: + + + For some use-cases it may still be useful to convert from + a :py:class:`~xarray.CFTimeIndex` to a :py:class:`pandas.DatetimeIndex`, + despite the difference in calendar types. The recommended way of doing this + is to use the built-in :py:meth:`~xarray.CFTimeIndex.to_datetimeindex` + method: + + .. ipython:: python + :okwarning: + + modern_times = xr.cftime_range('2000', periods=24, freq='MS', calendar='noleap') + da = xr.DataArray(range(24), [('time', modern_times)]) + da + datetimeindex = da.indexes['time'].to_datetimeindex() + da['time'] = datetimeindex + + However in this case one should use caution to only perform operations which + do not depend on differences between dates (e.g. differentiation, + interpolation, or upsampling with resample), as these could introduce subtle + and silent errors due to the difference in calendar types between the dates + encoded in your data and the dates stored in memory. + +.. _Timestamp-valid range: https://pandas.pydata.org/pandas-docs/stable/timeseries.html#timestamp-limitations +.. _ISO 8601-format: https://en.wikipedia.org/wiki/ISO_8601 +.. _partial datetime string indexing: https://pandas.pydata.org/pandas-docs/stable/timeseries.html#partial-string-indexing diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 260c20d0b31..6cf2720a033 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -13,42 +13,91 @@ What's New import xarray as xr np.random.seed(123456) -.. _whats-new.0.12.0: +.. _whats-new.0.12.1: -v0.12.0 (unreleased) +v0.12.1 (unreleased) -------------------- -Breaking changes -~~~~~~~~~~~~~~~~ +Enhancements +~~~~~~~~~~~~ -- Remove support for Python 2. This is the first version of xarray that is - Python 3 only. (:issue:`1876`). - By `Joe Hamman `_. -- The `compat` argument to `Dataset` and the `encoding` argument to - `DataArray` are deprecated and will be removed in a future release. + +Bug fixes +~~~~~~~~~ + +- ``swap_dims`` would create incorrect ``indexes`` (:issue:`2842`). + By `Stephan Hoyer `_. + + +.. _whats-new.0.12.0: + +v0.12.0 (15 March 2019) +----------------------- + +Highlights include: + +- Removed support for Python 2. This is the first version of xarray that is + Python 3 only! +- New :py:meth:`~xarray.DataArray.coarsen` and + :py:meth:`~xarray.DataArray.integrate` methods. See :ref:`comput.coarsen` + and :ref:`compute.using_coordinates` for details. +- Many improvements to cftime support. See below for details. + +Deprecations +~~~~~~~~~~~~ + +- The ``compat`` argument to ``Dataset`` and the ``encoding`` argument to + ``DataArray`` are deprecated and will be removed in a future release. (:issue:`1188`) By `Maximilian Roos `_. -- `cyordereddict` is no longer used as an optional dependency (:issue:`2744`). - By `Joe Hamman `_. -Enhancements -~~~~~~~~~~~~ +cftime related enhancements +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Resampling of standard and non-standard calendars indexed by + :py:class:`~xarray.CFTimeIndex` is now possible. (:issue:`2191`). + By `Jwen Fai Low `_ and + `Spencer Clark `_. + +- Taking the mean of arrays of :py:class:`cftime.datetime` objects, and + by extension, use of :py:meth:`~xarray.DataArray.coarsen` with + :py:class:`cftime.datetime` coordinates is now possible. By `Spencer Clark + `_. - Internal plotting now supports ``cftime.datetime`` objects as time series. (:issue:`2164`) By `Julius Busecke `_ and `Spencer Clark `_. + +- :py:meth:`~xarray.cftime_range` now supports QuarterBegin and QuarterEnd offsets (:issue:`2663`). + By `Jwen Fai Low `_ + +- :py:meth:`~xarray.open_dataset` now accepts a ``use_cftime`` argument, which + can be used to require that ``cftime.datetime`` objects are always used, or + never used when decoding dates encoded with a standard calendar. This can be + used to ensure consistent date types are returned when using + :py:meth:`~xarray.open_mfdataset` (:issue:`1263`) and/or to silence + serialization warnings raised if dates from a standard calendar are found to + be outside the :py:class:`pandas.Timestamp`-valid range (:issue:`2754`). By + `Spencer Clark `_. + +- :py:meth:`pandas.Series.dropna` is now supported for a + :py:class:`pandas.Series` indexed by a :py:class:`~xarray.CFTimeIndex` + (:issue:`2688`). By `Spencer Clark `_. + +Other enhancements +~~~~~~~~~~~~~~~~~~ + +- Added ability to open netcdf4/hdf5 file-like objects with ``open_dataset``. + Requires (h5netcdf>0.7 and h5py>2.9.0). (:issue:`2781`) + By `Scott Henderson `_ - Add ``data=False`` option to ``to_dict()`` methods. (:issue:`2656`) By `Ryan Abernathey `_ -- :py:meth:`~xarray.DataArray.coarsen` and - :py:meth:`~xarray.Dataset.coarsen` are newly added. +- :py:meth:`DataArray.coarsen` and + :py:meth:`Dataset.coarsen` are newly added. See :ref:`comput.coarsen` for details. (:issue:`2525`) By `Keisuke Fujii `_. -- Taking the mean of arrays of :py:class:`cftime.datetime` objects, and - by extension, use of :py:meth:`~xarray.DataArray.coarsen` with - :py:class:`cftime.datetime` coordinates is now possible. By `Spencer Clark - `_. - Upsampling an array via interpolation with resample is now dask-compatible, as long as the array is not chunked along the resampling dimension. By `Spencer Clark `_. @@ -57,32 +106,14 @@ Enhancements report showing what exactly differs between the two objects (dimensions / coordinates / variables / attributes) (:issue:`1507`). By `Benoit Bovy `_. -- Resampling of standard and non-standard calendars indexed by - :py:class:`~xarray.CFTimeIndex` is now possible. (:issue:`2191`). - By `Jwen Fai Low `_ and - `Spencer Clark `_. - Add ``tolerance`` option to ``resample()`` methods ``bfill``, ``pad``, ``nearest``. (:issue:`2695`) By `Hauke Schulz `_. -- :py:meth:`~xarray.DataArray.integrate` and - :py:meth:`~xarray.Dataset.integrate` are newly added. - See :ref:`_compute.using_coordinates` for the detail. +- :py:meth:`DataArray.integrate` and + :py:meth:`Dataset.integrate` are newly added. + See :ref:`compute.using_coordinates` for the detail. (:issue:`1332`) By `Keisuke Fujii `_. -- :py:meth:`pandas.Series.dropna` is now supported for a - :py:class:`pandas.Series` indexed by a :py:class:`~xarray.CFTimeIndex` - (:issue:`2688`). By `Spencer Clark `_. -- :py:meth:`~xarray.cftime_range` now supports QuarterBegin and QuarterEnd offsets (:issue:`2663`). - By `Jwen Fai Low `_ -- :py:meth:`~xarray.open_dataset` now accepts a ``use_cftime`` argument, which - can be used to require that ``cftime.datetime`` objects are always used, or - never used when decoding dates encoded with a standard calendar. This can be - used to ensure consistent date types are returned when using - :py:meth:`~xarray.open_mfdataset` (:issue:`1263`) and/or to silence - serialization warnings raised if dates from a standard calendar are found to - be outside the :py:class:`pandas.Timestamp`-valid range (:issue:`2754`). By - `Spencer Clark `_. - - Added :py:meth:`~xarray.Dataset.drop_dims` (:issue:`1949`). By `Kevin Squire `_. @@ -96,15 +127,15 @@ Bug fixes from higher frequencies to lower frequencies. Datapoints outside the bounds of the original time coordinate are now filled with NaN (:issue:`2197`). By `Spencer Clark `_. -- Line plots with the `x` argument set to a non-dimensional coord now plot the correct data for 1D DataArrays. - (:issue:`27251). By `Tom Nicholas `_. +- Line plots with the ``x`` argument set to a non-dimensional coord now plot the correct data for 1D DataArrays. + (:issue:`27251`). By `Tom Nicholas `_. - Subtracting a scalar ``cftime.datetime`` object from a :py:class:`CFTimeIndex` now results in a :py:class:`pandas.TimedeltaIndex` instead of raising a ``TypeError`` (:issue:`2671`). By `Spencer Clark `_. - backend_kwargs are no longer ignored when using open_dataset with pynio engine (:issue:'2380') - By 'Jonathan Joyce '_. + By `Jonathan Joyce `_. - Fix ``open_rasterio`` creating a WKT CRS instead of PROJ.4 with ``rasterio`` 1.0.14+ (:issue:`2715`). By `David Hoese `_. diff --git a/xarray/backends/api.py b/xarray/backends/api.py index 61efcfdedf2..afb69f6e9e9 100644 --- a/xarray/backends/api.py +++ b/xarray/backends/api.py @@ -75,6 +75,34 @@ def _get_default_engine_netcdf(): return engine +def _get_engine_from_magic_number(filename_or_obj): + # check byte header to determine file type + if isinstance(filename_or_obj, bytes): + magic_number = filename_or_obj[:8] + else: + if filename_or_obj.tell() != 0: + raise ValueError("file-like object read/write pointer not at zero " + "please close and reopen, or use a context " + "manager") + magic_number = filename_or_obj.read(8) + filename_or_obj.seek(0) + + if magic_number.startswith(b'CDF'): + engine = 'scipy' + elif magic_number.startswith(b'\211HDF\r\n\032\n'): + engine = 'h5netcdf' + if isinstance(filename_or_obj, bytes): + raise ValueError("can't open netCDF4/HDF5 as bytes " + "try passing a path or file-like object") + else: + if isinstance(filename_or_obj, bytes) and len(filename_or_obj) > 80: + filename_or_obj = filename_or_obj[:80] + b'...' + raise ValueError('{} is not a valid netCDF file ' + 'did you mean to pass a string for a path instead?' + .format(filename_or_obj)) + return engine + + def _get_default_engine(path, allow_remote=False): if allow_remote and is_remote_uri(path): engine = _get_default_engine_remote_uri() @@ -170,8 +198,8 @@ def open_dataset(filename_or_obj, group=None, decode_cf=True, Strings and Path objects are interpreted as a path to a netCDF file or an OpenDAP URL and opened with python-netCDF4, unless the filename ends with .gz, in which case the file is gunzipped and opened with - scipy.io.netcdf (only netCDF3 supported). File-like objects are opened - with scipy.io.netcdf (only netCDF3 supported). + scipy.io.netcdf (only netCDF3 supported). Byte-strings or file-like + objects are opened by scipy.io.netcdf (netCDF3) or h5py (netCDF4/HDF). group : str, optional Path to the netCDF4 group in the given file to open (only works for netCDF4 files). @@ -202,7 +230,7 @@ def open_dataset(filename_or_obj, group=None, decode_cf=True, decode_coords : bool, optional If True, decode the 'coordinates' attribute to identify coordinates in the resulting dataset. - engine : {'netcdf4', 'scipy', 'pydap', 'h5netcdf', 'pynio', 'cfgrib', + engine : {'netcdf4', 'scipy', 'pydap', 'h5netcdf', 'pynio', 'cfgrib', \ 'pseudonetcdf'}, optional Engine to use when reading files. If not provided, the default engine is chosen based on available dependencies, with a preference for @@ -247,16 +275,30 @@ def open_dataset(filename_or_obj, group=None, decode_cf=True, dataset : Dataset The newly created dataset. + Notes + ----- + ``open_dataset`` opens the file with read-only access. When you modify + values of a Dataset, even one linked to files on disk, only the in-memory + copy you are manipulating in xarray is modified: the original file on disk + is never touched. + See Also -------- open_mfdataset """ + engines = [None, 'netcdf4', 'scipy', 'pydap', 'h5netcdf', 'pynio', + 'cfgrib', 'pseudonetcdf'] + if engine not in engines: + raise ValueError('unrecognized engine for open_dataset: {}\n' + 'must be one of: {}' + .format(engine, engines)) + if autoclose is not None: warnings.warn( 'The autoclose argument is no longer used by ' 'xarray.open_dataset() and is now ignored; it will be removed in ' - 'xarray v0.12. If necessary, you can control the maximum number ' - 'of simultaneous open files with ' + 'a future version of xarray. If necessary, you can control the ' + 'maximum number of simultaneous open files with ' 'xarray.set_options(file_cache_maxsize=...).', FutureWarning, stacklevel=2) @@ -309,18 +351,9 @@ def maybe_decode_store(store, lock=False): if isinstance(filename_or_obj, backends.AbstractDataStore): store = filename_or_obj - ds = maybe_decode_store(store) - elif isinstance(filename_or_obj, str): - if (isinstance(filename_or_obj, bytes) and - filename_or_obj.startswith(b'\x89HDF')): - raise ValueError('cannot read netCDF4/HDF5 file images') - elif (isinstance(filename_or_obj, bytes) and - filename_or_obj.startswith(b'CDF')): - # netCDF3 file images are handled by scipy - pass - elif isinstance(filename_or_obj, str): - filename_or_obj = _normalize_path(filename_or_obj) + elif isinstance(filename_or_obj, str): + filename_or_obj = _normalize_path(filename_or_obj) if engine is None: engine = _get_default_engine(filename_or_obj, @@ -345,18 +378,19 @@ def maybe_decode_store(store, lock=False): elif engine == 'cfgrib': store = backends.CfGribDataStore( filename_or_obj, lock=lock, **backend_kwargs) - else: - raise ValueError('unrecognized engine for open_dataset: %r' - % engine) - with close_on_error(store): - ds = maybe_decode_store(store) else: - if engine is not None and engine != 'scipy': - raise ValueError('can only read file-like objects with ' - "default engine or engine='scipy'") - # assume filename_or_obj is a file-like object - store = backends.ScipyDataStore(filename_or_obj) + if engine not in [None, 'scipy', 'h5netcdf']: + raise ValueError("can only read bytes or file-like objects " + "with engine='scipy' or 'h5netcdf'") + engine = _get_engine_from_magic_number(filename_or_obj) + if engine == 'scipy': + store = backends.ScipyDataStore(filename_or_obj, **backend_kwargs) + elif engine == 'h5netcdf': + store = backends.H5NetCDFStore(filename_or_obj, group=group, + lock=lock, **backend_kwargs) + + with close_on_error(store): ds = maybe_decode_store(store) # Ensure source filename always stored in dataset object (GH issue #2550) @@ -383,8 +417,8 @@ def open_dataarray(filename_or_obj, group=None, decode_cf=True, Strings and Paths are interpreted as a path to a netCDF file or an OpenDAP URL and opened with python-netCDF4, unless the filename ends with .gz, in which case the file is gunzipped and opened with - scipy.io.netcdf (only netCDF3 supported). File-like objects are opened - with scipy.io.netcdf (only netCDF3 supported). + scipy.io.netcdf (only netCDF3 supported). Byte-strings or file-like + objects are opened by scipy.io.netcdf (netCDF3) or h5py (netCDF4/HDF). group : str, optional Path to the netCDF4 group in the given file to open (only works for netCDF4 files). @@ -411,7 +445,7 @@ def open_dataarray(filename_or_obj, group=None, decode_cf=True, decode_coords : bool, optional If True, decode the 'coordinates' attribute to identify coordinates in the resulting dataset. - engine : {'netcdf4', 'scipy', 'pydap', 'h5netcdf', 'pynio', 'cfgrib'}, + engine : {'netcdf4', 'scipy', 'pydap', 'h5netcdf', 'pynio', 'cfgrib'}, \ optional Engine to use when reading files. If not provided, the default engine is chosen based on available dependencies, with a preference for @@ -550,7 +584,7 @@ def open_mfdataset(paths, chunks=None, concat_dim=_CONCAT_DIM_DEFAULT, If provided, call this function on each dataset prior to concatenation. You can find the file-name from which each dataset was loaded in ``ds.encoding['source']``. - engine : {'netcdf4', 'scipy', 'pydap', 'h5netcdf', 'pynio', 'cfgrib'}, + engine : {'netcdf4', 'scipy', 'pydap', 'h5netcdf', 'pynio', 'cfgrib'}, \ optional Engine to use when reading files. If not provided, the default engine is chosen based on available dependencies, with a preference for @@ -597,6 +631,13 @@ def open_mfdataset(paths, chunks=None, concat_dim=_CONCAT_DIM_DEFAULT, ------- xarray.Dataset + Notes + ----- + ``open_mfdataset`` opens files with read-only access. When you modify values + of a Dataset, even one linked to files on disk, only the in-memory copy you + are manipulating in xarray is modified: the original file on disk is never + touched. + See Also -------- auto_combine diff --git a/xarray/backends/lru_cache.py b/xarray/backends/lru_cache.py index e407c384aaf..4be6efea7c0 100644 --- a/xarray/backends/lru_cache.py +++ b/xarray/backends/lru_cache.py @@ -1,8 +1,9 @@ import collections +import collections.abc import threading -class LRUCache(collections.MutableMapping): +class LRUCache(collections.abc.MutableMapping): """Thread-safe LRUCache based on an OrderedDict. All dict operations (__getitem__, __setitem__, __contains__) update the diff --git a/xarray/coding/cftime_offsets.py b/xarray/coding/cftime_offsets.py index a74c735224b..d724554b458 100644 --- a/xarray/coding/cftime_offsets.py +++ b/xarray/coding/cftime_offsets.py @@ -41,15 +41,19 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import re +import typing from datetime import timedelta from functools import partial -from typing import ClassVar, Optional import numpy as np +from ..core.pycompat import TYPE_CHECKING from .cftimeindex import CFTimeIndex, _parse_iso8601_with_reso from .times import format_cftime_datetime +if TYPE_CHECKING: + from typing import ClassVar, Optional + def get_date_type(calendar): """Return the cftime date type for a given calendar name.""" diff --git a/xarray/core/computation.py b/xarray/core/computation.py index f9fd9022de9..451d95ee542 100644 --- a/xarray/core/computation.py +++ b/xarray/core/computation.py @@ -4,21 +4,22 @@ import functools import itertools import operator +import typing from collections import Counter, OrderedDict from distutils.version import LooseVersion from typing import ( AbstractSet, Any, Callable, Iterable, List, Mapping, Optional, Sequence, - Tuple, TYPE_CHECKING, Union, -) + Tuple, Union) import numpy as np from . import duck_array_ops, utils from .alignment import deep_align from .merge import expand_and_merge_variables -from .pycompat import dask_array_type +from .pycompat import TYPE_CHECKING, dask_array_type from .utils import is_dict_like from .variable import Variable + if TYPE_CHECKING: from .dataset import Dataset diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 96b42f19555..7cd856db5b4 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -5,9 +5,9 @@ import numpy as np import pandas as pd +from ..plot.plot import _PlotMethods from . import ( computation, dtypes, groupby, indexing, ops, resample, rolling, utils) -from ..plot.plot import _PlotMethods from .accessors import DatetimeAccessor from .alignment import align, reindex_like_indexers from .common import AbstractArray, DataWithCoords @@ -1385,8 +1385,9 @@ def transpose(self, *dims): Notes ----- - Although this operation returns a view of this array's data, it is - not lazy -- the data will be fully loaded. + This operation returns a view of this array's data. It is + lazy for dask-backed DataArrays but not for numpy-backed DataArrays + -- the data will be fully loaded. See Also -------- @@ -2437,10 +2438,10 @@ def integrate(self, dim, datetime_unit=None): ---------- dim: str, or a sequence of str Coordinate(s) used for the integration. - datetime_unit - Can be specify the unit if datetime coordinate is used. One of - {'Y', 'M', 'W', 'D', 'h', 'm', 's', 'ms', 'us', 'ns', 'ps', 'fs', - 'as'} + datetime_unit: str, optional + Can be used to specify the unit if datetime coordinate is used. + One of {'Y', 'M', 'W', 'D', 'h', 'm', 's', 'ms', 'us', 'ns', + 'ps', 'fs', 'as'} Returns ------- diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index f3e6cac1c5b..3bb54e80456 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -1,45 +1,44 @@ import copy import functools import sys +import typing import warnings from collections import OrderedDict, defaultdict from collections.abc import Mapping from distutils.version import LooseVersion from numbers import Number from typing import ( - Any, Callable, Dict, List, Optional, Set, Tuple, TypeVar, TYPE_CHECKING, - Union, -) + Any, Callable, Dict, List, Optional, Set, Tuple, TypeVar, Union) import numpy as np import pandas as pd import xarray as xr +from ..coding.cftimeindex import _parse_array_of_cftime_strings from . import ( alignment, dtypes, duck_array_ops, formatting, groupby, indexing, ops, pdcompat, resample, rolling, utils) -from ..coding.cftimeindex import _parse_array_of_cftime_strings from .alignment import align from .common import ( ALL_DIMS, DataWithCoords, ImplementsDatasetReduce, _contains_datetime_like_objects) from .coordinates import ( DatasetCoordinates, LevelCoordinatesSource, assert_coordinate_consistent, - remap_label_indexers, -) + remap_label_indexers) from .duck_array_ops import datetime_to_numeric from .indexes import Indexes, default_indexes, isel_variable_and_index from .merge import ( dataset_merge_method, dataset_update_method, merge_data_and_coords, merge_variables) from .options import OPTIONS, _get_keep_attrs -from .pycompat import dask_array_type +from .pycompat import TYPE_CHECKING, dask_array_type from .utils import ( - Frozen, SortedKeysDict, _check_inplace, - decode_numpy_dict_values, either_dict_or_kwargs, ensure_us_time_resolution, - hashable, maybe_wrap_array) + Frozen, SortedKeysDict, _check_inplace, decode_numpy_dict_values, + either_dict_or_kwargs, ensure_us_time_resolution, hashable, is_dict_like, + maybe_wrap_array) from .variable import IndexVariable, Variable, as_variable, broadcast_variables + if TYPE_CHECKING: from .dataarray import DataArray @@ -2308,24 +2307,20 @@ def swap_dims(self, dims_dict, inplace=None): coord_names.update(dims_dict.values()) variables = OrderedDict() + indexes = OrderedDict() for k, v in self.variables.items(): dims = tuple(dims_dict.get(dim, dim) for dim in v.dims) if k in result_dims: var = v.to_index_variable() + if k in self.indexes: + indexes[k] = self.indexes[k] + else: + indexes[k] = var.to_index() else: var = v.to_base_variable() var.dims = dims variables[k] = var - indexes = OrderedDict() - for k, v in self.indexes.items(): - if k in dims_dict: - new_name = dims_dict[k] - new_index = variables[k].to_index() - indexes[new_name] = new_index - else: - indexes[k] = v - return self._replace_with_new_dims(variables, coord_names, indexes=indexes, inplace=inplace) @@ -2848,8 +2843,9 @@ def transpose(self, *dims): Notes ----- - Although this operation returns a view of each array's data, it - is not lazy -- the data will be fully loaded into memory. + This operation returns a view of each array's data. It is + lazy for dask-backed DataArrays but not for numpy-backed DataArrays + -- the data will be fully loaded into memory. See Also -------- diff --git a/xarray/core/groupby.py b/xarray/core/groupby.py index 1fa1c159fbc..e8e2f1b08d4 100644 --- a/xarray/core/groupby.py +++ b/xarray/core/groupby.py @@ -616,8 +616,9 @@ def reduce(self, func, dim=None, axis=None, if self._obj.ndim > 1: warnings.warn( "Default reduction dimension will be changed to the " - "grouped dimension after xarray 0.12. To silence this " - "warning, pass dim=xarray.ALL_DIMS explicitly.", + "grouped dimension in a future version of xarray. To " + "silence this warning, pass dim=xarray.ALL_DIMS " + "explicitly.", FutureWarning, stacklevel=2) if keep_attrs is None: @@ -731,8 +732,9 @@ def reduce(self, func, dim=None, keep_attrs=None, **kwargs): # the deprecation process. Do not forget to remove _reduce_method warnings.warn( "Default reduction dimension will be changed to the " - "grouped dimension after xarray 0.12. To silence this " - "warning, pass dim=xarray.ALL_DIMS explicitly.", + "grouped dimension in a future version of xarray. To " + "silence this warning, pass dim=xarray.ALL_DIMS " + "explicitly.", FutureWarning, stacklevel=2) elif dim is None: dim = self._group_dim diff --git a/xarray/core/merge.py b/xarray/core/merge.py index 3039eecb2f8..363fdfc2337 100644 --- a/xarray/core/merge.py +++ b/xarray/core/merge.py @@ -1,18 +1,19 @@ +import typing from collections import OrderedDict - -from typing import ( - Any, Dict, List, Mapping, Optional, Set, Tuple, TYPE_CHECKING, Union, -) +from typing import Any, Dict, List, Mapping, Optional, Set, Tuple, Union import pandas as pd from .alignment import deep_align +from .pycompat import TYPE_CHECKING from .utils import Frozen from .variable import ( Variable, as_variable, assert_unique_multiindex_level_names) + if TYPE_CHECKING: from .dataset import Dataset + PANDAS_TYPES = (pd.Series, pd.DataFrame, pd.Panel) _VALID_COMPAT = Frozen({'identical': 0, diff --git a/xarray/core/pycompat.py b/xarray/core/pycompat.py index bd2075fa300..0df0e727303 100644 --- a/xarray/core/pycompat.py +++ b/xarray/core/pycompat.py @@ -1,4 +1,6 @@ # flake8: noqa +import sys +import typing import numpy as np @@ -10,3 +12,7 @@ dask_array_type = (dask.array.Array,) except ImportError: # pragma: no cover dask_array_type = () + +# Ensure we have some more recent additions to the typing module. +# Note that TYPE_CHECKING itself is not available on Python 3.5.1. +TYPE_CHECKING = sys.version >= '3.5.3' and typing.TYPE_CHECKING diff --git a/xarray/core/utils.py b/xarray/core/utils.py index fd1330a4e1f..349c8f98dc5 100644 --- a/xarray/core/utils.py +++ b/xarray/core/utils.py @@ -20,7 +20,8 @@ def _check_inplace(inplace, default=False): inplace = default else: warnings.warn('The inplace argument has been deprecated and will be ' - 'removed in xarray 0.12.0.', FutureWarning, stacklevel=3) + 'removed in a future version of xarray.', + FutureWarning, stacklevel=3) return inplace diff --git a/xarray/core/variable.py b/xarray/core/variable.py index b675317d83d..d6b64e7d458 100644 --- a/xarray/core/variable.py +++ b/xarray/core/variable.py @@ -1,8 +1,8 @@ import functools import itertools +import typing from collections import OrderedDict, defaultdict from datetime import timedelta -from typing import Tuple, Type, Union import numpy as np import pandas as pd @@ -15,9 +15,14 @@ BasicIndexer, OuterIndexer, PandasIndexAdapter, VectorizedIndexer, as_indexable) from .options import _get_keep_attrs -from .pycompat import dask_array_type, integer_types -from .utils import (OrderedSet, either_dict_or_kwargs, - decode_numpy_dict_values, ensure_us_time_resolution) +from .pycompat import TYPE_CHECKING, dask_array_type, integer_types +from .utils import ( + OrderedSet, decode_numpy_dict_values, either_dict_or_kwargs, + ensure_us_time_resolution) + +if TYPE_CHECKING: + from typing import Tuple, Type, Union + try: import dask.array as da @@ -1133,8 +1138,8 @@ def transpose(self, *dims): Notes ----- - Although this operation returns a view of this variable's data, it is - not lazy -- the data will be fully loaded. + This operation returns a view of this variable's data. It is + lazy for dask-backed Variables but not for numpy-backed Variables. See Also -------- @@ -1597,7 +1602,7 @@ def rank(self, dim, pct=False): "prior to calling this method.") axis = self.get_axis_num(dim) - func = bn.nanrankdata if self.dtype.kind is 'f' else bn.rankdata + func = bn.nanrankdata if self.dtype.kind == 'f' else bn.rankdata ranked = func(self.data, axis=axis) if pct: count = np.sum(~np.isnan(self.data), axis=axis, keepdims=True) diff --git a/xarray/tests/__init__.py b/xarray/tests/__init__.py index 281fc662197..4ebcc29a61e 100644 --- a/xarray/tests/__init__.py +++ b/xarray/tests/__init__.py @@ -77,6 +77,12 @@ def LooseVersion(vstring): has_cfgrib, requires_cfgrib = _importorskip('cfgrib') # some special cases +has_h5netcdf07, requires_h5netcdf07 = _importorskip('h5netcdf', + minversion='0.7') +has_h5py29, requires_h5py29 = _importorskip('h5py', minversion='2.9.0') +has_h5fileobj = has_h5netcdf07 and has_h5py29 +requires_h5fileobj = pytest.mark.skipif( + not has_h5fileobj, reason='requires h5py>2.9.0 & h5netcdf>0.7') has_scipy_or_netCDF4 = has_scipy or has_netCDF4 requires_scipy_or_netCDF4 = pytest.mark.skipif( not has_scipy_or_netCDF4, reason='requires scipy or netCDF4') diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index f610dba1352..a20ba2df229 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -35,7 +35,7 @@ requires_cftime, requires_dask, requires_h5netcdf, requires_netCDF4, requires_pathlib, requires_pseudonetcdf, requires_pydap, requires_pynio, requires_rasterio, requires_scipy, requires_scipy_or_netCDF4, - requires_zarr) + requires_zarr, requires_h5fileobj) from .test_coding_times import (_STANDARD_CALENDARS, _NON_STANDARD_CALENDARS, _ALL_CALENDARS) from .test_dataset import create_test_data @@ -1770,7 +1770,7 @@ def test_engine(self): open_dataset(tmp_file, engine='foobar') netcdf_bytes = data.to_netcdf() - with raises_regex(ValueError, 'can only read'): + with raises_regex(ValueError, 'unrecognized engine'): open_dataset(BytesIO(netcdf_bytes), engine='foobar') def test_cross_engine_read_write_netcdf3(self): @@ -1955,6 +1955,52 @@ def test_dump_encodings_h5py(self): assert actual.x.encoding['compression_opts'] is None +@requires_h5fileobj +class TestH5NetCDFFileObject(TestH5NetCDFData): + engine = 'h5netcdf' + + def test_open_badbytes(self): + with raises_regex(ValueError, "HDF5 as bytes"): + with open_dataset(b'\211HDF\r\n\032\n', engine='h5netcdf'): + pass + with raises_regex(ValueError, "not a valid netCDF"): + with open_dataset(b'garbage'): + pass + with raises_regex(ValueError, "can only read bytes"): + with open_dataset(b'garbage', engine='netcdf4'): + pass + with raises_regex(ValueError, "not a valid netCDF"): + with open_dataset(BytesIO(b'garbage'), engine='h5netcdf'): + pass + + def test_open_twice(self): + expected = create_test_data() + expected.attrs['foo'] = 'bar' + with raises_regex(ValueError, 'read/write pointer not at zero'): + with create_tmp_file() as tmp_file: + expected.to_netcdf(tmp_file, engine='h5netcdf') + with open(tmp_file, 'rb') as f: + with open_dataset(f, engine='h5netcdf'): + with open_dataset(f, engine='h5netcdf'): + pass + + def test_open_fileobj(self): + # open in-memory datasets instead of local file paths + expected = create_test_data().drop('dim3') + expected.attrs['foo'] = 'bar' + with create_tmp_file() as tmp_file: + expected.to_netcdf(tmp_file, engine='h5netcdf') + + with open(tmp_file, 'rb') as f: + with open_dataset(f, engine='h5netcdf') as actual: + assert_identical(expected, actual) + + f.seek(0) + with BytesIO(f.read()) as bio: + with open_dataset(bio, engine='h5netcdf') as actual: + assert_identical(expected, actual) + + @requires_h5netcdf @requires_dask @pytest.mark.filterwarnings('ignore:deallocating CachingFileManager') @@ -3444,11 +3490,6 @@ def new_dataset_and_coord_attrs(): with create_tmp_file() as tmp_file: ds.to_netcdf(tmp_file) - ds, attrs = new_dataset_and_attrs() - attrs['test'] = np.arange(12).reshape(3, 4) - with create_tmp_file() as tmp_file: - ds.to_netcdf(tmp_file) - ds, attrs = new_dataset_and_attrs() attrs['test'] = 'This is a string' with create_tmp_file() as tmp_file: @@ -3459,11 +3500,6 @@ def new_dataset_and_coord_attrs(): with create_tmp_file() as tmp_file: ds.to_netcdf(tmp_file) - ds, attrs = new_dataset_and_attrs() - attrs['test'] = np.arange(12).reshape(3, 4) - with create_tmp_file() as tmp_file: - ds.to_netcdf(tmp_file) - @requires_scipy_or_netCDF4 class TestDataArrayToNetCDF(object): diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py index ab05f19dbbe..4975071dad8 100644 --- a/xarray/tests/test_dataarray.py +++ b/xarray/tests/test_dataarray.py @@ -2037,7 +2037,7 @@ def test_groupby_warning(self): with pytest.warns(FutureWarning): grouped.sum() - @pytest.mark.skipif(LooseVersion(xr.__version__) < LooseVersion('0.12'), + @pytest.mark.skipif(LooseVersion(xr.__version__) < LooseVersion('0.13'), reason="not to forget the behavior change") def test_groupby_sum_default(self): array = self.make_groupby_example_array() diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index 8e8c6c4b419..777a8e84a3f 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -2002,14 +2002,11 @@ def test_swap_dims(self): assert_identical(expected, actual) assert isinstance(actual.variables['y'], IndexVariable) assert isinstance(actual.variables['x'], Variable) + assert actual.indexes['y'].equals(pd.Index(list('abc'))) roundtripped = actual.swap_dims({'y': 'x'}) assert_identical(original.set_coords('y'), roundtripped) - actual = original.copy() - actual = actual.swap_dims({'x': 'y'}) - assert_identical(expected, actual) - with raises_regex(ValueError, 'cannot swap'): original.swap_dims({'y': 'x'}) with raises_regex(ValueError, 'replacement dimension'): diff --git a/xarray/tutorial.py b/xarray/tutorial.py index 3f92bd9a400..f54cf7b3889 100644 --- a/xarray/tutorial.py +++ b/xarray/tutorial.py @@ -91,16 +91,17 @@ def open_dataset(name, cache=True, cache_dir=_default_cache_dir, def load_dataset(*args, **kwargs): """ - `load_dataset` will be removed in version 0.12. The current behavior of - this function can be achived by using `tutorial.open_dataset(...).load()`. + `load_dataset` will be removed a future version of xarray. The current + behavior of this function can be achived by using + `tutorial.open_dataset(...).load()`. See Also -------- open_dataset """ warnings.warn( - "load_dataset` will be removed in xarray version 0.12. The current " - "behavior of this function can be achived by using " + "load_dataset` will be removed in a future version of xarray. The " + "current behavior of this function can be achived by using " "`tutorial.open_dataset(...).load()`.", DeprecationWarning, stacklevel=2) return open_dataset(*args, **kwargs).load()