Repository: dask/dask

Issue:
[FEA] Allow shallow or deep copies of dask dataframes
`Dask.dataframe.copy` is currently strictly shallow, which may cause metadata issues when cuDF/pandas objects are scattered across workers and then downstream operations pass `deep=False` as keyword arguments.

It would be useful to parameterize `copy` to accept the deep keyword, and allow either deep or shallow copies:

https://github.com/dask/dask/blob/33ad832dfc80aee72250fe45d3dfc53ebd987f33/dask/dataframe/core.py#L356-L362


Discussion:
> and then pass deep=False to operations.

Can I ask for more information about what you mean here?  Are you running operations that mutate the dataframe in-place?  If so, that kind of behavior is generally not well supported.
i think this came from a cudf operation which has `deep=False` but the keyword `deep` is currently not supported.  Is that right @beckernick ? 
Yes, that's correct. This came up when trying to use a pre-scattered dask dataframe object, that did not like the cuDF operation that wanted to pass `deep=False` to copy as part of its API.
Accepting `deep` in DataFrame.copy seems OK to me.

I'm not sure what we should do able non-default values. Do we want to accept whatever and just ignore the user-provided value (which makes it easier to write code that works with any dataframe). Or should we raise if the non-default value is provided, as ignoring user-input is generally a bad idea?
Would an acceptable solution here, be to add the `deep` kwarg and only accept `deep=False` and rais otherwise? I think that's what @TomAugspurger is suggesting above. 

Resolving patch:
diff --git a/dask/dataframe/core.py b/dask/dataframe/core.py
--- a/dask/dataframe/core.py
+++ b/dask/dataframe/core.py
@@ -404,12 +404,23 @@ def __getstate__(self):
     def __setstate__(self, state):
         self.dask, self._name, self._meta, self.divisions = state
 
-    def copy(self):
+    def copy(self, deep=False):
         """Make a copy of the dataframe
 
         This is strictly a shallow copy of the underlying computational graph.
         It does not affect the underlying data
+
+        Parameters
+        ----------
+        deep : boolean, default False
+            The deep value must be `False` and it is declared as a parameter just for
+            compatibility with third-party libraries like cuDF
         """
+        if deep is not False:
+            raise ValueError(
+                "The `deep` value must be False. This is strictly a shallow copy "
+                "of the underlying computational graph."
+            )
         return new_dd_object(self.dask, self._name, self._meta, self.divisions)
 
     def __array__(self, dtype=None, **kwargs):


Test patch:
diff --git a/dask/dataframe/tests/test_dataframe.py b/dask/dataframe/tests/test_dataframe.py
--- a/dask/dataframe/tests/test_dataframe.py
+++ b/dask/dataframe/tests/test_dataframe.py
@@ -3856,12 +3856,20 @@ def test_copy():
 
     a = dd.from_pandas(df, npartitions=2)
     b = a.copy()
+    c = a.copy(deep=False)
 
     a["y"] = a.x * 2
 
     assert_eq(b, df)
+    assert_eq(c, df)
 
-    df["y"] = df.x * 2
+    deep_err = (
+        "The `deep` value must be False. This is strictly a shallow copy "
+        "of the underlying computational graph."
+    )
+    for deep in [True, None, ""]:
+        with pytest.raises(ValueError, match=deep_err):
+            a.copy(deep=deep)
 
 
 def test_del():