Repository: pydata/xarray

Issue:
Dataset.copy docstring has a typo
The docstring of [Dataset.copy](https://github.com/pydata/xarray/blob/9f4474d657193f1c7c9aac25bb2edf94755a8593/xarray/core/dataset.py#L715-L730) states that the default of deep is True, but in the code it is False. 


Discussion:
pep8speaks: Hello @georgegeddes! Thanks for submitting the PR.

 - There are no PEP8 issues in the file [`xarray/core/dataset.py`](https://github.com/georgegeddes/xarray/blob/4badc77d9770efaf48dc7a63965c5b3f12148024/xarray/core/dataset.py) !


shoyer: thanks for catching this!

I'm trying to remember if there is any good reason for these methods having different defaults....

I suppose it could make sense if `Dataset.copy()` is modeled off `dict.copy()` (which does shallow copies) and `DataArray.copy()` is modeled off `numpy.ndarray.copy()` (which does deep copies).

Patch:
diff --git a/asv_bench/benchmarks/dataset_io.py b/asv_bench/benchmarks/dataset_io.py
index da18d541..3e070e13 100644
--- a/asv_bench/benchmarks/dataset_io.py
+++ b/asv_bench/benchmarks/dataset_io.py
@@ -168,7 +168,7 @@ class IOReadSingleNetCDF4Dask(IOSingleNetCDF):
         ds = ds.isel(**self.vinds).load()
 
     def time_load_dataset_netcdf4_with_block_chunks_multiprocessing(self):
-        with dask.set_options(get=dask.multiprocessing.get):
+        with dask.config.set(scheduler="multiprocessing"):
             xr.open_dataset(self.filepath, engine='netcdf4',
                             chunks=self.block_chunks).load()
 
@@ -177,7 +177,7 @@ class IOReadSingleNetCDF4Dask(IOSingleNetCDF):
                         chunks=self.time_chunks).load()
 
     def time_load_dataset_netcdf4_with_time_chunks_multiprocessing(self):
-        with dask.set_options(get=dask.multiprocessing.get):
+        with dask.config.set(scheduler="multiprocessing"):
             xr.open_dataset(self.filepath, engine='netcdf4',
                             chunks=self.time_chunks).load()
 
@@ -194,7 +194,7 @@ class IOReadSingleNetCDF3Dask(IOReadSingleNetCDF4Dask):
         self.ds.to_netcdf(self.filepath, format=self.format)
 
     def time_load_dataset_scipy_with_block_chunks(self):
-        with dask.set_options(get=dask.multiprocessing.get):
+        with dask.config.set(scheduler="multiprocessing"):
             xr.open_dataset(self.filepath, engine='scipy',
                             chunks=self.block_chunks).load()
 
@@ -209,7 +209,7 @@ class IOReadSingleNetCDF3Dask(IOReadSingleNetCDF4Dask):
         ds = ds.isel(**self.vinds).load()
 
     def time_load_dataset_scipy_with_time_chunks(self):
-        with dask.set_options(get=dask.multiprocessing.get):
+        with dask.config.set(scheduler="multiprocessing"):
             xr.open_dataset(self.filepath, engine='scipy',
                             chunks=self.time_chunks).load()
 
@@ -349,7 +349,7 @@ class IOReadMultipleNetCDF4Dask(IOMultipleNetCDF):
                           chunks=self.block_chunks).load()
 
     def time_load_dataset_netcdf4_with_block_chunks_multiprocessing(self):
-        with dask.set_options(get=dask.multiprocessing.get):
+        with dask.config.set(scheduler="multiprocessing"):
             xr.open_mfdataset(self.filenames_list, engine='netcdf4',
                               chunks=self.block_chunks).load()
 
@@ -358,7 +358,7 @@ class IOReadMultipleNetCDF4Dask(IOMultipleNetCDF):
                           chunks=self.time_chunks).load()
 
     def time_load_dataset_netcdf4_with_time_chunks_multiprocessing(self):
-        with dask.set_options(get=dask.multiprocessing.get):
+        with dask.config.set(scheduler="multiprocessing"):
             xr.open_mfdataset(self.filenames_list, engine='netcdf4',
                               chunks=self.time_chunks).load()
 
@@ -367,7 +367,7 @@ class IOReadMultipleNetCDF4Dask(IOMultipleNetCDF):
                           chunks=self.block_chunks)
 
     def time_open_dataset_netcdf4_with_block_chunks_multiprocessing(self):
-        with dask.set_options(get=dask.multiprocessing.get):
+        with dask.config.set(scheduler="multiprocessing"):
             xr.open_mfdataset(self.filenames_list, engine='netcdf4',
                               chunks=self.block_chunks)
 
@@ -376,7 +376,7 @@ class IOReadMultipleNetCDF4Dask(IOMultipleNetCDF):
                           chunks=self.time_chunks)
 
     def time_open_dataset_netcdf4_with_time_chunks_multiprocessing(self):
-        with dask.set_options(get=dask.multiprocessing.get):
+        with dask.config.set(scheduler="multiprocessing"):
             xr.open_mfdataset(self.filenames_list, engine='netcdf4',
                               chunks=self.time_chunks)
 
@@ -392,22 +392,22 @@ class IOReadMultipleNetCDF3Dask(IOReadMultipleNetCDF4Dask):
                           format=self.format)
 
     def time_load_dataset_scipy_with_block_chunks(self):
-        with dask.set_options(get=dask.multiprocessing.get):
+        with dask.config.set(scheduler="multiprocessing"):
             xr.open_mfdataset(self.filenames_list, engine='scipy',
                               chunks=self.block_chunks).load()
 
     def time_load_dataset_scipy_with_time_chunks(self):
-        with dask.set_options(get=dask.multiprocessing.get):
+        with dask.config.set(scheduler="multiprocessing"):
             xr.open_mfdataset(self.filenames_list, engine='scipy',
                               chunks=self.time_chunks).load()
 
     def time_open_dataset_scipy_with_block_chunks(self):
-        with dask.set_options(get=dask.multiprocessing.get):
+        with dask.config.set(scheduler="multiprocessing"):
             xr.open_mfdataset(self.filenames_list, engine='scipy',
                               chunks=self.block_chunks)
 
     def time_open_dataset_scipy_with_time_chunks(self):
-        with dask.set_options(get=dask.multiprocessing.get):
+        with dask.config.set(scheduler="multiprocessing"):
             xr.open_mfdataset(self.filenames_list, engine='scipy',
                               chunks=self.time_chunks)
 
diff --git a/doc/whats-new.rst b/doc/whats-new.rst
index 61da801b..9a55b938 100644
--- a/doc/whats-new.rst
+++ b/doc/whats-new.rst
@@ -106,7 +106,9 @@ Bug fixes
   (:issue:`2484`).  By `Spencer Clark <https://github.com/spencerkclark>`_.
 - Adding a TimedeltaIndex to, or subtracting a TimedeltaIndex from a
   CFTimeIndex is now allowed (:issue:`2484`).
-  By `Spencer Clark <https://github.com/spencerkclark>`_. 
+  By `Spencer Clark <https://github.com/spencerkclark>`_.
+- Avoid use of Dask's deprecated ``get=`` parameter in tests
+  by `Matthew Rocklin <https://github.com/mrocklin/>`_.
 
 .. _whats-new.0.10.9:
 
diff --git a/xarray/backends/api.py b/xarray/backends/api.py
index 3fb7338e..ca440872 100644
--- a/xarray/backends/api.py
+++ b/xarray/backends/api.py
@@ -12,7 +12,7 @@ from .. import Dataset, backends, conventions
 from ..core import indexing
 from ..core.combine import auto_combine
 from ..core.pycompat import basestring, path_type
-from ..core.utils import close_on_error, is_remote_uri
+from ..core.utils import close_on_error, is_remote_uri, is_grib_path
 from .common import ArrayWriter
 from .locks import _get_scheduler
 
@@ -21,29 +21,71 @@ DATAARRAY_NAME = '__xarray_dataarray_name__'
 DATAARRAY_VARIABLE = '__xarray_dataarray_variable__'
 
 
-def _get_default_engine(path, allow_remote=False):
-    if allow_remote and is_remote_uri(path):  # pragma: no cover
+def _get_default_engine_remote_uri():
+    try:
+        import netCDF4
+        engine = 'netcdf4'
+    except ImportError:  # pragma: no cover
         try:
-            import netCDF4
-            engine = 'netcdf4'
+            import pydap  # flake8: noqa
+            engine = 'pydap'
         except ImportError:
-            try:
-                import pydap  # flake8: noqa
-                engine = 'pydap'
-            except ImportError:
-                raise ValueError('netCDF4 or pydap is required for accessing '
-                                 'remote datasets via OPeNDAP')
+            raise ValueError('netCDF4 or pydap is required for accessing '
+                             'remote datasets via OPeNDAP')
+    return engine
+
+
+def _get_default_engine_grib():
+    msgs = []
+    try:
+        import Nio  # flake8: noqa
+        msgs += ["set engine='pynio' to access GRIB files with PyNIO"]
+    except ImportError:  # pragma: no cover
+        pass
+    try:
+        import cfgrib  # flake8: noqa
+        msgs += ["set engine='cfgrib' to access GRIB files with cfgrib"]
+    except ImportError:  # pragma: no cover
+        pass
+    if msgs:
+        raise ValueError(' or\n'.join(msgs))
     else:
+        raise ValueError('PyNIO or cfgrib is required for accessing '
+                         'GRIB files')
+
+
+def _get_default_engine_gz():
+    try:
+        import scipy  # flake8: noqa
+        engine = 'scipy'
+    except ImportError:  # pragma: no cover
+        raise ValueError('scipy is required for accessing .gz files')
+    return engine
+
+
+def _get_default_engine_netcdf():
+    try:
+        import netCDF4  # flake8: noqa
+        engine = 'netcdf4'
+    except ImportError:  # pragma: no cover
         try:
-            import netCDF4  # flake8: noqa
-            engine = 'netcdf4'
-        except ImportError:  # pragma: no cover
-            try:
-                import scipy.io.netcdf  # flake8: noqa
-                engine = 'scipy'
-            except ImportError:
-                raise ValueError('cannot read or write netCDF files without '
-                                 'netCDF4-python or scipy installed')
+            import scipy.io.netcdf  # flake8: noqa
+            engine = 'scipy'
+        except ImportError:
+            raise ValueError('cannot read or write netCDF files without '
+                             'netCDF4-python or scipy installed')
+    return engine
+
+
+def _get_default_engine(path, allow_remote=False):
+    if allow_remote and is_remote_uri(path):
+        engine = _get_default_engine_remote_uri()
+    elif is_grib_path(path):
+        engine = _get_default_engine_grib()
+    elif path.endswith('.gz'):
+        engine = _get_default_engine_gz()
+    else:
+        engine = _get_default_engine_netcdf()
     return engine
 
 
@@ -270,13 +312,6 @@ def open_dataset(filename_or_obj, group=None, decode_cf=True,
         elif isinstance(filename_or_obj, basestring):
             filename_or_obj = _normalize_path(filename_or_obj)
 
-        if filename_or_obj.endswith('.gz'):
-            if engine is not None and engine != 'scipy':
-                raise ValueError('can only read gzipped netCDF files with '
-                                 "default engine or engine='scipy'")
-            else:
-                engine = 'scipy'
-
         if engine is None:
             engine = _get_default_engine(filename_or_obj,
                                          allow_remote=True)
diff --git a/xarray/backends/rasterio_.py b/xarray/backends/rasterio_.py
index 5746b4e7..7a343a65 100644
--- a/xarray/backends/rasterio_.py
+++ b/xarray/backends/rasterio_.py
@@ -169,7 +169,7 @@ def open_rasterio(filename, parse_coordinates=None, chunks=None, cache=None,
 
         from affine import Affine
         da = xr.open_rasterio('path_to_file.tif')
-        transform = Affine(*da.attrs['transform'])
+        transform = Affine.from_gdal(*da.attrs['transform'])
         nx, ny = da.sizes['x'], da.sizes['y']
         x, y = np.meshgrid(np.arange(nx)+0.5, np.arange(ny)+0.5) * transform
 
diff --git a/xarray/core/common.py b/xarray/core/common.py
index c74b1fa0..6c03775a 100644
--- a/xarray/core/common.py
+++ b/xarray/core/common.py
@@ -343,6 +343,7 @@ class DataWithCoords(SupportsArithmetic, AttrAccessMixin):
         See also
         --------
         Dataset.assign
+        Dataset.swap_dims
         """
         data = self.copy(deep=False)
         results = self._calc_assign_results(kwargs)
diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py
index c8586d1d..756cd795 100644
--- a/xarray/core/dataset.py
+++ b/xarray/core/dataset.py
@@ -727,7 +727,7 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords,
         ----------
         deep : bool, optional
             Whether each component variable is loaded into memory and copied onto
-            the new object. Default is True.
+            the new object. Default is False.
         data : dict-like, optional
             Data to use in the new object. Each item in `data` must have same
             shape as corresponding data variable in original. When `data` is
@@ -1112,6 +1112,10 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords,
         Returns
         -------
         Dataset
+
+        See also
+        --------
+        Dataset.swap_dims
         """
         # TODO: allow inserting new coordinates with this method, like
         # DataFrame.set_index?
@@ -2282,6 +2286,7 @@ class Dataset(Mapping, ImplementsDatasetReduce, DataWithCoords,
         See Also
         --------
         Dataset.reset_index
+        Dataset.swap_dims
         """
         indexes = either_dict_or_kwargs(indexes, indexes_kwargs, 'set_index')
         variables, coord_names = merge_indexes(indexes, self._variables,
diff --git a/xarray/core/groupby.py b/xarray/core/groupby.py
index 3842c642..dc23eae8 100644
--- a/xarray/core/groupby.py
+++ b/xarray/core/groupby.py
@@ -423,6 +423,7 @@ class GroupBy(SupportsArithmetic):
         See also
         --------
         Dataset.assign_coords
+        Dataset.swap_dims
         """
         return self.apply(lambda ds: ds.assign_coords(**kwargs))
 
diff --git a/xarray/core/utils.py b/xarray/core/utils.py
index c39a07e1..5c9d8bfb 100644
--- a/xarray/core/utils.py
+++ b/xarray/core/utils.py
@@ -5,6 +5,7 @@ from __future__ import absolute_import, division, print_function
 import contextlib
 import functools
 import itertools
+import os.path
 import re
 import warnings
 from collections import Iterable, Mapping, MutableMapping, MutableSet
@@ -504,6 +505,11 @@ def is_remote_uri(path):
     return bool(re.search('^https?\://', path))
 
 
+def is_grib_path(path):
+    _, ext = os.path.splitext(path)
+    return ext in ['.grib', '.grb', '.grib2', '.grb2']
+
+
 def is_uniform_spaced(arr, **kwargs):
     """Return True if values of an array are uniformly spaced and sorted.
 


Test patch:
diff --git a/xarray/tests/__init__.py b/xarray/tests/__init__.py
index 56ecfa30..a45f71bb 100644
--- a/xarray/tests/__init__.py
+++ b/xarray/tests/__init__.py
@@ -93,7 +93,7 @@ if has_dask:
     if LooseVersion(dask.__version__) < '0.18':
         dask.set_options(get=dask.get)
     else:
-        dask.config.set(scheduler='sync')
+        dask.config.set(scheduler='single-threaded')
 try:
     import_seaborn()
     has_seaborn = True
diff --git a/xarray/tests/test_backends_api.py b/xarray/tests/test_backends_api.py
new file mode 100644
index 00000000..ed49dd72
--- /dev/null
+++ b/xarray/tests/test_backends_api.py
@@ -0,0 +1,22 @@
+
+import pytest
+
+from xarray.backends.api import _get_default_engine
+from . import requires_netCDF4, requires_scipy
+
+
+@requires_netCDF4
+@requires_scipy
+def test__get_default_engine():
+    engine_remote = _get_default_engine('http://example.org/test.nc',
+                                        allow_remote=True)
+    assert engine_remote == 'netcdf4'
+
+    engine_gz = _get_default_engine('/example.gz')
+    assert engine_gz == 'scipy'
+
+    with pytest.raises(ValueError):
+        _get_default_engine('/example.grib')
+
+    engine_default = _get_default_engine('/example')
+    assert engine_default == 'netcdf4'
diff --git a/xarray/tests/test_dask.py b/xarray/tests/test_dask.py
index e56f751b..62ce7d07 100644
--- a/xarray/tests/test_dask.py
+++ b/xarray/tests/test_dask.py
@@ -26,7 +26,7 @@ dd = pytest.importorskip('dask.dataframe')
 class DaskTestCase(object):
     def assertLazyAnd(self, expected, actual, test):
 
-        with (dask.config.set(get=dask.get)
+        with (dask.config.set(scheduler='single-threaded')
               if LooseVersion(dask.__version__) >= LooseVersion('0.18.0')
               else dask.set_options(get=dask.get)):
             test(actual, expected)
@@ -456,7 +456,11 @@ class TestDataArrayAndDataset(DaskTestCase):
             count[0] += 1
             return dask.get(*args, **kwargs)
 
-        ds.load(get=counting_get)
+        if dask.__version__ < '0.19.4':
+            ds.load(get=counting_get)
+        else:
+            ds.load(scheduler=counting_get)
+
         assert count[0] == 1
 
     def test_stack(self):
@@ -831,7 +835,9 @@ def test_basic_compute():
                 dask.multiprocessing.get,
                 dask.local.get_sync,
                 None]:
-        with (dask.config.set(get=get)
+        with (dask.config.set(scheduler=get)
+              if LooseVersion(dask.__version__) >= LooseVersion('0.19.4')
+              else dask.config.set(scheduler=get)
               if LooseVersion(dask.__version__) >= LooseVersion('0.18.0')
               else dask.set_options(get=get)):
             ds.compute()
diff --git a/xarray/tests/test_utils.py b/xarray/tests/test_utils.py
index 34f401dd..33021fc5 100644
--- a/xarray/tests/test_utils.py
+++ b/xarray/tests/test_utils.py
@@ -200,6 +200,22 @@ def test_repr_object():
     assert repr(obj) == 'foo'
 
 
+def test_is_remote_uri():
+    assert utils.is_remote_uri('http://example.com')
+    assert utils.is_remote_uri('https://example.com')
+    assert not utils.is_remote_uri(' http://example.com')
+    assert not utils.is_remote_uri('example.nc')
+
+
+def test_is_grib_path():
+    assert not utils.is_grib_path('example.nc')
+    assert not utils.is_grib_path('example.grib ')
+    assert utils.is_grib_path('example.grib')
+    assert utils.is_grib_path('example.grib2')
+    assert utils.is_grib_path('example.grb')
+    assert utils.is_grib_path('example.grb2')
+
+
 class Test_is_uniform_and_sorted(object):
 
     def test_sorted_uniform(self):