Repository: wright-group/WrightTools

Issue:
raise a warning when dupicate objects "created" by users
When you create two or more collections, `root.create_collection()` but fail to specify the `name` kwarg, and you assign each to a different variable (`var1` and `var2`), then unintentional behavior can result in the longterm e.g. trying to add data to the individual collections using from pycmds `wt.data.from_PyCMDS(p, parent=var1)` and `wt.data.from_PyCMDS(p, parent=var2)` will throw an error when run together if `var1` and `var2` are both collections created without specifying their names. 

As an end user this behavior is a bit wonky.
Note, this could be something to think about in create_data too...


Discussion:
@darienmorrow what behavior would you like to see?

1. name becomes a required argument
2. collections get a randomly generated name
3. collections without a name argument have an appended index so the first one is `collection000`, second is `collection001` etc

I'm not sure I like any of these...
I think my preference is the last, but it's not great
Maybe the answer is to check uniqueness of the collection name.
and raise a warning?

Resolving patch:
diff --git a/WrightTools/collection/_collection.py b/WrightTools/collection/_collection.py
--- a/WrightTools/collection/_collection.py
+++ b/WrightTools/collection/_collection.py
@@ -7,6 +7,7 @@
 import numpy as np
 
 from .. import data as wt_data
+from .. import exceptions as wt_exceptions
 from .._group import Group
 
 
@@ -88,6 +89,9 @@ def create_collection(self, name="collection", position=None, **kwargs):
         WrightTools Collection
             New child.
         """
+        if name in self.item_names:
+            wt_exceptions.ObjectExistsWarning.warn(name)
+            return self[name]
         collection = Collection(
             filepath=self.filepath, parent=self.name, name=name, edit_local=True, **kwargs
         )
@@ -115,6 +119,10 @@ def create_data(self, name="data", position=None, **kwargs):
         WrightTools Data
             New child.
         """
+        if name in self.item_names:
+            wt_exceptions.ObjectExistsWarning.warn(name)
+            return self[name]
+
         if name == "":
             data = None
             natural_name = "".encode()
diff --git a/WrightTools/data/_data.py b/WrightTools/data/_data.py
--- a/WrightTools/data/_data.py
+++ b/WrightTools/data/_data.py
@@ -472,6 +472,10 @@ def create_channel(self, name, values=None, shape=None, units=None, **kwargs):
         Channel
             Created channel.
         """
+        if name in self.channel_names:
+            wt_exceptions.ObjectExistsWarning.warn(name)
+            return self[name]
+
         require_kwargs = {}
         if values is None:
             if shape is None:
@@ -514,6 +518,9 @@ def create_variable(self, name, values=None, shape=None, units=None, **kwargs):
         WrightTools Variable
             New child variable.
         """
+        if name in self.variable_names:
+            wt_exceptions.ObjectExistsWarning.warn(name)
+            return self[name]
         if values is None:
             if shape is None:
                 shape = self.shape
diff --git a/WrightTools/exceptions.py b/WrightTools/exceptions.py
--- a/WrightTools/exceptions.py
+++ b/WrightTools/exceptions.py
@@ -136,5 +136,13 @@ def warn(filepath, expected):
             Expected file suffix.
         """
         filesuffix = os.path.basename(filepath).split(".")[-1]
-        message = "file {0} has type {1} (expected {2})".format(filepath, filesuffix, "txt")
+        message = "file {0} has type {1} (expected {2})".format(filepath, filesuffix, expected)
         warnings.warn(message, WrongFileTypeWarning)
+
+
+class ObjectExistsWarning(WrightToolsWarning):
+    """Warn that an HDF5 object already exists when a new one is requested."""
+
+    def warn(name):
+        message = "object '{0}' already exists, returning existing copy".format(name)
+        warnings.warn(message, ObjectExistsWarning)


Test patch:
diff --git a/tests/collection/collection.py b/tests/collection/collection.py
new file mode 100755
--- /dev/null
+++ b/tests/collection/collection.py
@@ -0,0 +1,26 @@
+#! /usr/bin/env python3
+import WrightTools as wt
+import pytest
+
+
+def test_create_collection():
+    col = wt.Collection()
+    child1 = col.create_collection()
+    with pytest.warns(wt.exceptions.ObjectExistsWarning):
+        child2 = col.create_collection()
+    assert child1 == child2
+    assert child1.natural_name == "collection"
+
+
+def test_create_data():
+    col = wt.Collection()
+    child1 = col.create_data()
+    with pytest.warns(wt.exceptions.ObjectExistsWarning):
+        child2 = col.create_data()
+    assert child1 == child2
+    assert child1.natural_name == "data"
+
+
+if __name__ == "__main__":
+    test_create_collection()
+    test_create_data()
diff --git a/tests/data/data.py b/tests/data/data.py
new file mode 100755
--- /dev/null
+++ b/tests/data/data.py
@@ -0,0 +1,27 @@
+#! /usr/bin/env python3
+
+import WrightTools as wt
+import pytest
+
+
+def test_create_variable():
+    data = wt.Data()
+    child1 = data.create_variable("hi", shape=(10, 10))
+    with pytest.warns(wt.exceptions.ObjectExistsWarning):
+        child2 = data.create_variable("hi", shape=(10, 1))
+    assert child1 == child2
+    assert child2.shape == (10, 10)
+
+
+def test_create_channel():
+    data = wt.Data()
+    child1 = data.create_channel("hi", shape=(10, 10))
+    with pytest.warns(wt.exceptions.ObjectExistsWarning):
+        child2 = data.create_channel("hi", shape=(10, 1))
+    assert child1 == child2
+    assert child2.shape == (10, 10)
+
+
+if __name__ == "__main__":
+    test_create_variable()
+    test_create_channel()