Repository: wright-group/WrightTools

Issue:
Create_collection should handle posix path arguments
```
>>> d = wt.Collection()
>>> d.create_collection('path/to/collection')
<WrightTools.Collection 'path/to/collection' () at /tmp/cynpj247.wt5::/path/to/collection>
>>> d
<WrightTools.Collection 'collection' ('path/to/collection',) at /tmp/cynpj247.wt5::/>
>>> d.path/to/collection
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    d.path/to/collection
NameError: name 'to' is not defined
>>> d['path/to/collection']
<WrightTools.Collection 'path/to/collection' () at /tmp/cynpj247.wt5::/path/to/collection>
>>> d['path']
<HDF5 group "/path" (1 members)>
>>> d['path']['to']
<HDF5 group "/path/to" (1 members)>
>>> d['path']['to']['collection']
<HDF5 group "/path/to/collection" (0 members)>
>>> d.path
<HDF5 group "/path" (1 members)>
>>> d.path.to
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    d.path.to
AttributeError: 'Group' object has no attribute 'to'
>>> d.name
'/'
>>> d.path
<HDF5 group "/path" (1 members)>
>>> d.item_names
('path/to/collection',)
>>> collection.print_tree()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    collection.print_tree()
NameError: name 'collection' is not defined
>>> d.print_tree()
collection (/tmp/cynpj247.wt5)
└── 0: path/to/collection
>>> h5py.open('test.h5')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    h5py.open('test.h5')
AttributeError: module 'h5py' has no attribute 'open'
>>> f = h5py.File('test.h5')
>>> f.create_group('path/to/group')
<HDF5 group "/path/to/group" (0 members)>
>>> f.items()
ItemsViewHDF5(<HDF5 file "test.h5" (mode r+)>)
>>> list(f.items())
[('path', <HDF5 group "/path" (1 members)>)]
>>> 
```


Discussion:
two problems:

1) items list not made properly

2) intermediate groups created as Groups, not Collections
Probably also applies to create_data (though that may be fixed via the same means)

Similar, but opposite problem when defining channels/variables with "/" in them, should be converted to `__d__`, I think In that case (if people want arbitrary HDF groups/datasets as metadata, I won't enforce that they can't, but I also think that is discouraged (outside of `attrs`, It is their responsibility to ensure clashes do not happen, and when creating channels/variables from our defined methods, we should ensure that exactly one hdf object is created, and it is our own kind)

Resolving patch:
diff --git a/WrightTools/_group.py b/WrightTools/_group.py
--- a/WrightTools/_group.py
+++ b/WrightTools/_group.py
@@ -45,20 +45,26 @@ class Group(h5py.Group, metaclass=MetaClass):
     class_name = "Group"
 
     def __init__(self, file=None, parent=None, name=None, **kwargs):
+        from .collection import Collection
+
         if file is None:
             return
         # parent
         if parent is None:
             parent = ""
         if parent == "":
-            parent = posixpath.sep
-            path = posixpath.sep
+            parent = pathlib.PurePosixPath("/")
+            path = pathlib.PurePosixPath("/")
         else:
-            path = posixpath.sep.join([parent, name])
+            path = pathlib.PurePosixPath(parent) / name
+        name = path.name if path.name else name
+        parent = path.parent
         self.filepath = file.filename
-        file.require_group(parent)
-        file.require_group(path)
-        h5py.Group.__init__(self, bind=file[path].id)
+        if name != "" and parent.name != "":
+            # Ensure that the parent Collection object is made first
+            Collection(file, parent=parent.parent, name=parent.name, edit_local=True)
+        file.require_group(str(path))
+        h5py.Group.__init__(self, bind=file[str(path)].id)
         self.__n = 0
         self.fid = self.file.id
         self.natural_name = name
@@ -91,7 +97,7 @@ def __init__(self, file=None, parent=None, name=None, **kwargs):
         self.__version__
         self.item_names
 
-        parent = file[parent]
+        parent = file[str(parent)]
         if parent.name == self.name:
             pass  # at root, dont add to item_names
         elif self.natural_name.encode() not in parent.attrs["item_names"]:
@@ -147,6 +153,9 @@ def __new__(cls, *args, **kwargs):
         natural_name = args[2] if len(args) > 2 else kwargs.get("name", cls.class_name.lower())
         edit_local = args[3] if len(args) > 3 else kwargs.pop("edit_local", False)
         file = None
+        if isinstance(filepath, h5py.File):
+            file = filepath
+            filepath = file.filename
         tmpfile = None
         if isinstance(parent, h5py.Group):
             filepath = parent.filepath
@@ -176,8 +185,9 @@ def __new__(cls, *args, **kwargs):
         # construct fullpath
         if parent is None:
             parent = ""
-            name = posixpath.sep
+            name = "/"
         else:
+            parent = str(parent)
             if not parent.endswith("/"):
                 parent += "/"
             name = natural_name


Test patch:
diff --git a/tests/collection/collection.py b/tests/collection/collection.py
--- a/tests/collection/collection.py
+++ b/tests/collection/collection.py
@@ -8,6 +8,11 @@ def test_create_collection():
     child1 = col.create_collection()
     with pytest.warns(wt.exceptions.ObjectExistsWarning):
         child2 = col.create_collection()
+    child3 = col.create_collection("path/to/collection")
+    assert child3.natural_name == "collection"
+    assert child3.parent.natural_name == "to"
+    assert "path" in col.item_names
+    assert isinstance(child3.parent, wt.Collection)
     assert child1 == child2
     assert child1.natural_name == "collection"
 
@@ -17,6 +22,11 @@ def test_create_data():
     child1 = col.create_data()
     with pytest.warns(wt.exceptions.ObjectExistsWarning):
         child2 = col.create_data()
+    child3 = col.create_data("path/to/data")
+    assert child3.natural_name == "data"
+    assert child3.parent.natural_name == "to"
+    assert "path" in col.item_names
+    assert isinstance(child3.parent, wt.Collection)
     assert child1 == child2
     assert child1.natural_name == "data"