Repository: pydata/xarray

Issue:
xr.merge fails when passing dict
#### Code Sample

```python
import xarray as xr

a = xr.DataArray([1, 2], dims=('foo', ), name='a')
b = xr.DataArray([3], dims=('bar', ), name='b')

objects = dict(a=a, b=b)
xr.merge(objects)

```
#### Problem description

This returns an `AttributeError`, while the documentation states that a dict is a valid input.

``` python
def merge(objects, compat='no_conflicts', join='outer'):
    """Merge any number of xarray objects into a single Dataset as variables.

    Parameters
    ----------
    objects : Iterable[Union[xarray.Dataset, xarray.DataArray, dict]]
        Merge together all variables from these objects. If any of them are
        DataArray objects, they must have a name.

...

```


#### Expected Output

Either the docs or the `dict_like_objects` loop should be changed. 

#### Output of ``xr.show_versions()``

<details>
INSTALLED VERSIONS
------------------
commit: None
python: 3.7.1 | packaged by conda-forge | (default, Feb 18 2019, 01:42:00) 
[GCC 7.3.0]
python-bits: 64
OS: Linux
OS-release: 4.4.138-59-default
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_GB.UTF-8
LOCALE: en_GB.UTF-8
libhdf5: 1.10.4
libnetcdf: 4.6.2

xarray: 0.12.1
pandas: 0.24.2
numpy: 1.16.2
scipy: 1.2.1
netCDF4: 1.5.0.1
pydap: None
h5netcdf: 0.7.1
h5py: 2.9.0
Nio: None
zarr: None
cftime: 1.0.3.4
nc_time_axis: 1.2.0
PseudonetCDF: None
rasterio: 1.0.22
cfgrib: 0.9.6.2
iris: 2.2.0
bottleneck: 1.2.1
dask: 1.1.5
distributed: 1.26.1
matplotlib: 3.0.3
cartopy: 0.17.0
seaborn: 0.9.0
setuptools: 41.0.0
pip: 19.0.3
conda: None
pytest: 4.4.0
IPython: 7.4.0
sphinx: 2.0.1

</details>


Discussion:
dcherian: LGTM. Thanks @mathause 
pep8speaks: Hello @mathause! Thanks for updating this PR. We checked the lines you've touched for [PEP 8](https://www.python.org/dev/peps/pep-0008) issues, and found:

* In the file [`xarray/core/merge.py`](https://github.com/pydata/xarray/blob/906127c2516f1187b1750ca8f70cd95386aafa99/xarray/core/merge.py):

> [Line 540:30](https://github.com/pydata/xarray/blob/906127c2516f1187b1750ca8f70cd95386aafa99/xarray/core/merge.py#L540): [E127](https://duckduckgo.com/?q=pep8%20E127) continuation line over-indented for visual indent





Patch:
diff --git a/doc/whats-new.rst b/doc/whats-new.rst
index b01c53e7..1dc6e0ce 100644
--- a/doc/whats-new.rst
+++ b/doc/whats-new.rst
@@ -48,6 +48,8 @@ Enhancements
   helpful for avoiding file-lock errors when trying to write to files opened
   using ``open_dataset()`` or ``open_dataarray()``. (:issue:`2887`)
   By `Dan Nowacki <https://github.com/dnowacki-usgs>`_.
+- Better warning message when supplying invalid objects to ``xr.merge``
+  (:issue:`2948`).  By `Mathias Hauser <https://github.com/mathause>`_.
 - Added ``strftime`` method to ``.dt`` accessor, making it simpler to hand a
   datetime ``DataArray`` to other code expecting formatted dates and times.
   (:issue:`2090`). By `Alan Brammer <https://github.com/abrammer>`_ and
@@ -72,6 +74,8 @@ Bug fixes
   By `Deepak Cherian <https://github.com/dcherian`_.
 - A deep copy deep-copies the coords (:issue:`1463`)
   By `Martin Pletcher <https://github.com/pletchm>`_.
+- Increased support for `missing_value` (:issue:`2871`)
+  By `Deepak Cherian <https://github.com/dcherian>`_.
 - Removed usages of `pytest.config`, which is deprecated (:issue:`2988`)
   By `Maximilian Roos <https://github.com/max-sixty>`_.
 - Fixed performance issues with cftime installed (:issue:`3000`)
diff --git a/xarray/coding/variables.py b/xarray/coding/variables.py
index 8f5ffe8a..c23e45e4 100644
--- a/xarray/coding/variables.py
+++ b/xarray/coding/variables.py
@@ -8,6 +8,7 @@ import pandas as pd
 
 from ..core import dtypes, duck_array_ops, indexing
 from ..core.pycompat import dask_array_type
+from ..core.utils import equivalent
 from ..core.variable import Variable
 
 
@@ -145,11 +146,24 @@ class CFMaskCoder(VariableCoder):
     def encode(self, variable, name=None):
         dims, data, attrs, encoding = unpack_for_encoding(variable)
 
-        if encoding.get('_FillValue') is not None:
+        fv = encoding.get('_FillValue')
+        mv = encoding.get('missing_value')
+
+        if fv is not None and mv is not None and not equivalent(fv, mv):
+            raise ValueError("Variable {!r} has multiple fill values {}. "
+                             "Cannot encode data. "
+                             .format(name, [fv, mv]))
+
+        if fv is not None:
             fill_value = pop_to(encoding, attrs, '_FillValue', name=name)
             if not pd.isnull(fill_value):
                 data = duck_array_ops.fillna(data, fill_value)
 
+        if mv is not None:
+            fill_value = pop_to(encoding, attrs, 'missing_value', name=name)
+            if not pd.isnull(fill_value) and fv is None:
+                data = duck_array_ops.fillna(data, fill_value)
+
         return Variable(dims, data, attrs, encoding)
 
     def decode(self, variable, name=None):
diff --git a/xarray/conventions.py b/xarray/conventions.py
index 5f41639e..3f8f76b0 100644
--- a/xarray/conventions.py
+++ b/xarray/conventions.py
@@ -82,7 +82,8 @@ def maybe_encode_nonstring_dtype(var, name=None):
         if dtype != var.dtype:
             if np.issubdtype(dtype, np.integer):
                 if (np.issubdtype(var.dtype, np.floating) and
-                        '_FillValue' not in var.attrs):
+                        '_FillValue' not in var.attrs and
+                        'missing_value' not in var.attrs):
                     warnings.warn('saving variable %s with floating '
                                   'point data as an integer dtype without '
                                   'any _FillValue to use for NaNs' % name,
diff --git a/xarray/core/merge.py b/xarray/core/merge.py
index 421ac39e..c2c6aee7 100644
--- a/xarray/core/merge.py
+++ b/xarray/core/merge.py
@@ -533,9 +533,14 @@ def merge(objects, compat='no_conflicts', join='outer', fill_value=dtypes.NA):
     from .dataarray import DataArray
     from .dataset import Dataset
 
-    dict_like_objects = [
-        obj.to_dataset() if isinstance(obj, DataArray) else obj
-        for obj in objects]
+    dict_like_objects = list()
+    for obj in objects:
+        if not (isinstance(obj, (DataArray, Dataset, dict))):
+            raise TypeError("objects must be an iterable containing only "
+                            "Dataset(s), DataArray(s), and dictionaries.")
+
+        obj = obj.to_dataset() if isinstance(obj, DataArray) else obj
+        dict_like_objects.append(obj)
 
     variables, coord_names, dims = merge_core(dict_like_objects, compat, join,
                                               fill_value=fill_value)


Test patch:
diff --git a/xarray/tests/test_coding.py b/xarray/tests/test_coding.py
index 95c8ebc0..9f937ac7 100644
--- a/xarray/tests/test_coding.py
+++ b/xarray/tests/test_coding.py
@@ -6,7 +6,7 @@ import pytest
 import xarray as xr
 from xarray.coding import variables
 
-from . import assert_identical, requires_dask
+from . import assert_equal, assert_identical, requires_dask
 
 with suppress(ImportError):
     import dask.array as da
@@ -20,6 +20,23 @@ def test_CFMaskCoder_decode():
     assert_identical(expected, encoded)
 
 
+def test_CFMaskCoder_missing_value():
+    expected = xr.DataArray(np.array([[26915, 27755, -9999, 27705],
+                                      [25595, -9999, 28315, -9999]]),
+                            dims=['npts', 'ntimes'],
+                            name='tmpk')
+    expected.attrs['missing_value'] = -9999
+
+    decoded = xr.decode_cf(expected.to_dataset())
+    encoded, _ = xr.conventions.cf_encoder(decoded, decoded.attrs)
+
+    assert_equal(encoded['tmpk'], expected.variable)
+
+    decoded.tmpk.encoding['_FillValue'] = -9940
+    with pytest.raises(ValueError):
+        encoded, _ = xr.conventions.cf_encoder(decoded, decoded.attrs)
+
+
 @requires_dask
 def test_CFMaskCoder_decode_dask():
     original = xr.Variable(('x',), [0, -1, 1], {'_FillValue': -1}).chunk()
diff --git a/xarray/tests/test_merge.py b/xarray/tests/test_merge.py
index c45195ea..20e0fae8 100644
--- a/xarray/tests/test_merge.py
+++ b/xarray/tests/test_merge.py
@@ -67,6 +67,15 @@ class TestMergeFunction:
         with raises_regex(ValueError, 'indexes .* not equal'):
             xr.merge([ds, other], join='exact')
 
+    def test_merge_wrong_input_error(self):
+        with raises_regex(TypeError, "objects must be an iterable"):
+            xr.merge([1])
+        ds = xr.Dataset(coords={'x': [1, 2]})
+        with raises_regex(TypeError, "objects must be an iterable"):
+            xr.merge({'a': ds})
+        with raises_regex(TypeError, "objects must be an iterable"):
+            xr.merge([ds, 1])
+
     def test_merge_no_conflicts_single_var(self):
         ds1 = xr.Dataset({'a': ('x', [1, 2]), 'x': [0, 1]})
         ds2 = xr.Dataset({'a': ('x', [2, 3]), 'x': [1, 2]})