Repository: ecmwf/earthkit-data

Issue:
engine kwargs dictionaries are modified in place
### What happened?

`earthkit.data` modifies engine kwargs in place, causing unexpected side effects in downstream code.

### What are the steps to reproduce the bug?

```python
import earthkit.data

dataset = "reanalysis-era5-pressure-levels"
request = {
    "product_type": ["reanalysis"],
    "variable": ["geopotential"],
    "year": ["2024"],
    "month": ["03"],
    "day": ["01"],
    "time": ["13:00"],
    "pressure_level": ["1000"],
    "data_format": "grib",
}
kwargs = {"squeeze": True}
ek_ds = earthkit.data.from_source("cds", dataset, **request)
xr_ds = ek_ds.to_xarray(engine="cfgrib", xarray_open_dataset_kwargs=kwargs)
assert kwargs == {"squeeze": True}
```

### Version

v0.13.5

### Platform (OS and architecture)

Darwin MacBook-Pro-di-Bopen.local 24.3.0 Darwin Kernel Version 24.3.0: Thu Jan  2 20:24:24 PST 2025; root:xnu-11215.81.4~3/RELEASE_ARM64_T6030 arm64

### Relevant log output

```shell
AssertionError                            Traceback (most recent call last)
Cell In[3], line 17
     15 ek_ds = earthkit.data.from_source("cds", dataset, **request)
     16 xr_ds = ek_ds.to_xarray(engine="cfgrib", xarray_open_dataset_kwargs=kwargs)
---> 17 assert kwargs == {'squeeze': True}

AssertionError:
```

### Accompanying data

_No response_

### Organisation

B-Open / EQC

Discussion:
@sandorkertesz: @malmans2 Thank you for reporting this issue. Will be fixed in the next hotfix release.@sandorkertesz: Fixed in #671 @sandorkertesz: 0.13.8 has been released with a fix for this issue.@malmans2: Thanks!

Solution patch:
diff --git a/src/earthkit/data/readers/grib/xarray.py b/src/earthkit/data/readers/grib/xarray.py
index 0043920..9e68f8d 100644
--- a/src/earthkit/data/readers/grib/xarray.py
+++ b/src/earthkit/data/readers/grib/xarray.py
@@ -7,6 +7,7 @@
 # nor does it submit to any jurisdiction.
 #
 
+import copy
 import logging
 
 from earthkit.data.utils import ensure_dict
@@ -344,6 +345,7 @@ class XarrayMixIn:
             raise ValueError(f"Unsupported engine: {engine}. Please use one of {list(engines.keys())}")
 
         user_xarray_open_dataset_kwargs = ensure_dict(xarray_open_dataset_kwargs)
+        user_xarray_open_dataset_kwargs = copy.deepcopy(user_xarray_open_dataset_kwargs)
         if user_xarray_open_dataset_kwargs and kwargs:
             raise ValueError("Cannot specify extra keyword arguments when xarray_open_dataset_kwargs is set.")
 


Test patch:
diff --git a/tests/xr_engine/test_xr_engine.py b/tests/xr_engine/test_xr_engine.py
index 59a9e45..0910f78 100644
--- a/tests/xr_engine/test_xr_engine.py
+++ b/tests/xr_engine/test_xr_engine.py
@@ -23,6 +23,17 @@ sys.path.insert(0, here)
 from xr_engine_fixtures import load_grib_data  # noqa: E402
 
 
+@pytest.mark.cache
+@pytest.mark.parametrize("engine", ["earthkit", "cfgrib"])
+def test_xr_engine_kwargs_unchanged(engine):
+    ds = from_source("url", earthkit_remote_test_data_file("test-data/xr_engine/level/pl_small.grib"))
+
+    _kwargs = {"squeeze": True}
+    res = ds.to_xarray(engine=engine, xarray_open_dataset_kwargs=_kwargs)
+    assert res is not None
+    assert _kwargs == {"squeeze": True}
+
+
 @pytest.mark.cache
 @pytest.mark.parametrize(
     "file",