Repository: corteva/rioxarray

Issue:
open_rasterio throws AttributeError after updating new xarray version 0.17.0
<!-- Please search existing issues to avoid creating duplicates. -->
rioxarray.open_rasterio(raster) was working fine. Today I updated xarray to the new release 0.17.0 and now rioxarray.open_rasterio thows an AttributeError

#### Code Sample, a copy-pastable example if possible
```python
rioxarray.open_rasterio(f'{maskdir}/{mask_tif}')


---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~/miniconda3/lib/python3.8/site-packages/xarray/core/common.py in __setattr__(self, name, value)
    267         try:
--> 268             object.__setattr__(self, name, value)
    269         except AttributeError as e:

AttributeError: 'DataArray' object has no attribute '_file_obj'

The above exception was the direct cause of the following exception:

AttributeError                            Traceback (most recent call last)
<ipython-input-5-93443f96a841> in <module>
      5 print(mask_tif)
      6 # Extract the water mask and set background to nan
----> 7 mask = rioxarray.open_rasterio(f'{maskdir}/{mask_tif}')

~/miniconda3/lib/python3.8/site-packages/rioxarray/_io.py in open_rasterio(filename, parse_coordinates, chunks, cache, lock, masked, mask_and_scale, variable, group, default_name, **open_kwargs)
    866 
    867     # Make the file closeable
--> 868     result._file_obj = manager
    869 
    870     return result

~/miniconda3/lib/python3.8/site-packages/xarray/core/common.py in __setattr__(self, name, value)
    274             ):
    275                 raise
--> 276             raise AttributeError(
    277                 "cannot set attribute %r on a %r object. Use __setitem__ style"
    278                 "assignment (e.g., `ds['name'] = ...`) instead of assigning variables."

AttributeError: cannot set attribute '_file_obj' on a 'DataArray' object. Use __setitem__ styleassignment (e.g., `ds['name'] = ...`) instead of assigning variables.
```

#### Environment Information
Tested on Linux  
rioxarray                 0.3.0              conda-forge
xarray                    0.17.0             conda-forge

</details>


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/corteva/rioxarray/pull/255?src=pr&el=h1) Report
> Merging [#255](https://codecov.io/gh/corteva/rioxarray/pull/255?src=pr&el=desc) (6263e95) into [master](https://codecov.io/gh/corteva/rioxarray/commit/0804791a44f65ac4f303dd286e94b3eaee81f72b?el=desc) (0804791) will **decrease** coverage by `0.56%`.
> The diff coverage is `80.00%`.

[![Impacted file tree graph](https://codecov.io/gh/corteva/rioxarray/pull/255/graphs/tree.svg?width=650&height=150&src=pr&token=C8QK7VO1OF)](https://codecov.io/gh/corteva/rioxarray/pull/255?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #255      +/-   ##
==========================================
- Coverage   93.75%   93.19%   -0.57%     
==========================================
  Files          12       12              
  Lines        1346     1352       +6     
==========================================
- Hits         1262     1260       -2     
- Misses         84       92       +8     
```


| [Impacted Files](https://codecov.io/gh/corteva/rioxarray/pull/255?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [rioxarray/\_io.py](https://codecov.io/gh/corteva/rioxarray/pull/255/diff?src=pr&el=tree#diff-cmlveGFycmF5L19pby5weQ==) | `85.47% <66.66%> (-2.03%)` | :arrow_down: |
| [rioxarray/merge.py](https://codecov.io/gh/corteva/rioxarray/pull/255/diff?src=pr&el=tree#diff-cmlveGFycmF5L21lcmdlLnB5) | `96.15% <100.00%> (ø)` | |
| [rioxarray/raster\_array.py](https://codecov.io/gh/corteva/rioxarray/pull/255/diff?src=pr&el=tree#diff-cmlveGFycmF5L3Jhc3Rlcl9hcnJheS5weQ==) | `97.54% <100.00%> (+<0.01%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/corteva/rioxarray/pull/255?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/corteva/rioxarray/pull/255?src=pr&el=footer). Last update [0804791...83837bc](https://codecov.io/gh/corteva/rioxarray/pull/255?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/history.rst b/docs/history.rst
index 3a05ae6e..9bd50498 100644
--- a/docs/history.rst
+++ b/docs/history.rst
@@ -3,6 +3,7 @@ History
 
 Latest
 ------
+- BUG: Compatibility changes with xarray 0.17 (issue #254)
 
 0.3.0
 ------
diff --git a/rioxarray/_io.py b/rioxarray/_io.py
index 4d4644e1..930ab767 100644
--- a/rioxarray/_io.py
+++ b/rioxarray/_io.py
@@ -865,6 +865,12 @@ def open_rasterio(
         result = _prepare_dask(result, riods, filename, chunks)
 
     # Make the file closeable
-    result._file_obj = manager
-
+    try:
+        # xarray 0.17 +
+        result.set_close(manager.close)
+    except AttributeError:
+        result._file_obj = manager
+    result.rio._manager = manager
+    # add file path to encoding
+    result.encoding["source"] = riods.name
     return result
diff --git a/rioxarray/merge.py b/rioxarray/merge.py
index f2e4c8e4..32bb8d61 100644
--- a/rioxarray/merge.py
+++ b/rioxarray/merge.py
@@ -33,7 +33,7 @@ class RasterioDatasetDuck:
         self.res = (abs(res[0]), abs(res[1]))
         self.transform = xds.rio.transform(recalc=True)
         try:
-            rio_file = xds._file_obj.acquire()
+            rio_file = xds.rio._manager.acquire()
             self.profile = rio_file.profile
             self.colormap = rio_file.colormap
         except AttributeError:
diff --git a/rioxarray/raster_array.py b/rioxarray/raster_array.py
index f5704dd1..f77531fd 100644
--- a/rioxarray/raster_array.py
+++ b/rioxarray/raster_array.py
@@ -136,7 +136,7 @@ def _clip_from_disk(xds, geometries, all_touched, drop, invert):
     """
     try:
         out_image, out_transform = rasterio.mask.mask(
-            xds._file_obj.acquire(),
+            xds.rio._manager.acquire(),
             geometries,
             all_touched=all_touched,
             invert=invert,
@@ -199,6 +199,7 @@ class RasterArray(XRasterBase):
         super().__init__(xarray_obj)
         # properties
         self._nodata = None
+        self._manager = None  # https://github.com/corteva/rioxarray/issues/254
 
     def set_nodata(self, input_nodata, inplace=True):
         """
@@ -278,7 +279,7 @@ class RasterArray(XRasterBase):
         # look in places used by `xarray.open_rasterio`
         if self._nodata is None:
             try:
-                self._nodata = self._obj._file_obj.acquire().nodata
+                self._nodata = self._manager.acquire().nodata
             except AttributeError:
                 try:
                     self._nodata = self._obj.attrs["nodatavals"][0]
@@ -874,7 +875,7 @@ class RasterArray(XRasterBase):
         # get the output profile from the rasterio object
         # if opened with xarray.open_rasterio()
         try:
-            out_profile = self._obj._file_obj.acquire().profile
+            out_profile = self._manager.acquire().profile
         except AttributeError:
             out_profile = {}
         out_profile.update(profile_kwargs)


Test patch:
diff --git a/test/integration/test_integration__io.py b/test/integration/test_integration__io.py
index a1596691..922b1272 100644
--- a/test/integration/test_integration__io.py
+++ b/test/integration/test_integration__io.py
@@ -265,7 +265,9 @@ def test_open_rasterio_mask_chunk_clip():
         assert str(type(xdi.data)) == "<class 'dask.array.core.Array'>"
         assert xdi.chunks == ((1,), (245,), (574,))
         assert np.isnan(xdi.values).sum() == 52119
-        assert xdi.encoding == {"_FillValue": 0.0}
+        test_encoding = dict(xdi.encoding)
+        assert test_encoding.pop("source").endswith("small_dem_3m_merged.tif")
+        assert test_encoding == {"_FillValue": 0.0}
         attrs = dict(xdi.attrs)
         assert_almost_equal(
             tuple(xdi.rio._cached_transform())[:6],
@@ -301,7 +303,9 @@ def test_open_rasterio_mask_chunk_clip():
         # test data array
         clipped = xdi.rio.clip(geometries, comp_subset.rio.crs)
         _assert_xarrays_equal(clipped, comp_subset)
-        assert clipped.encoding == {"_FillValue": 0.0}
+        test_encoding = dict(clipped.encoding)
+        assert test_encoding.pop("source").endswith("small_dem_3m_merged.tif")
+        assert test_encoding == {"_FillValue": 0.0}
 
         # test dataset
         clipped_ds = xdi.to_dataset(name="test_data").rio.clip(
@@ -309,7 +313,9 @@ def test_open_rasterio_mask_chunk_clip():
         )
         comp_subset_ds = comp_subset.to_dataset(name="test_data")
         _assert_xarrays_equal(clipped_ds, comp_subset_ds)
-        assert clipped_ds.test_data.encoding == {"_FillValue": 0.0}
+        test_encoding = dict(clipped.encoding)
+        assert test_encoding.pop("source").endswith("small_dem_3m_merged.tif")
+        assert test_encoding == {"_FillValue": 0.0}
 
 
 ##############################################################################
@@ -873,13 +879,17 @@ def test_open_cog():
 
 
 def test_mask_and_scale():
+    test_file = os.path.join(TEST_INPUT_DATA_DIR, "tmmx_20190121.nc")
     with pytest.warns(SerializationWarning):
-        with rioxarray.open_rasterio(
-            os.path.join(TEST_INPUT_DATA_DIR, "tmmx_20190121.nc"), mask_and_scale=True
-        ) as rds:
+        with rioxarray.open_rasterio(test_file, mask_and_scale=True) as rds:
             assert np.nanmin(rds.air_temperature.values) == 248.7
             assert np.nanmax(rds.air_temperature.values) == 302.1
-            assert rds.air_temperature.encoding == {
+            test_encoding = dict(rds.air_temperature.encoding)
+            source = test_encoding.pop("source")
+            assert source.startswith("netcdf:") and source.endswith(
+                "tmmx_20190121.nc:air_temperature"
+            )
+            assert test_encoding == {
                 "_Unsigned": "true",
                 "add_offset": 220.0,
                 "scale_factor": 0.1,
@@ -905,7 +915,12 @@ def test_no_mask_and_scale():
     ) as rds:
         assert np.nanmin(rds.air_temperature.values) == 287
         assert np.nanmax(rds.air_temperature.values) == 821
-        assert rds.air_temperature.encoding == {
+        test_encoding = dict(rds.air_temperature.encoding)
+        source = test_encoding.pop("source")
+        assert source.startswith("netcdf:") and source.endswith(
+            "tmmx_20190121.nc:air_temperature"
+        )
+        assert test_encoding == {
             "_FillValue": 32767.0,
             "missing_value": 32767,
         }