Repository: scalableminds/webknossos-libs

Issue:
Convenience for wkcuber.api
To open/create a dataset with the cool new high-level API the following code is required:

```python
from wkcuber.api.Dataset import WKDataset
from pathlib import Path

ds1 = WKDataset.create(Path("path") / "to" / "dataset1", scale=(128,128,128))
ds2 = WKDataset.open(Path("path") / "to" / "dataset2")

```

For one-off scripts, I think that could be a bit more convenient, if we had an API like this

```python
from wkcuber import WKDataset

ds1 = WKDataset.create("path/to/dataset1", scale=(128, 128, 128))
ds2 = WKDataset.open("path/to/dataset2")
```

Any thoughts? @rschwanhold @jstriebel @philippotto 



Discussion:
Context: I have been using the API a couple of times now to convert datasets for users, where the automatic conversion didn't succeed yet.
Sounds fair for me. For public-facing apis, that are not only used in vx or other internal code, allowing `Union[Path, str]` makes sense.
Yes, fully agree :+1: 
Is this limited to the Dataset API or are there more cases like this?
I think `WKDataset` is the primary entry point for the API, so there would be the biggest benefit. I don't think we need to expose the whole API in this manner. 

Resolving patch:
diff --git a/wkcuber/api/Dataset.py b/wkcuber/api/Dataset.py
--- a/wkcuber/api/Dataset.py
+++ b/wkcuber/api/Dataset.py
@@ -111,7 +111,8 @@ def copy_job(args: Tuple[View, View, int]) -> None:
 
 class AbstractDataset(Generic[LayerT]):
     @abstractmethod
-    def __init__(self, dataset_path: Path) -> None:
+    def __init__(self, dataset_path: Union[str, Path]) -> None:
+        dataset_path = Path(dataset_path)
         properties: Properties = self._get_properties_type()._from_json(
             dataset_path / Properties.FILE_NAME
         )
@@ -290,7 +291,7 @@ def delete_layer(self, layer_name: str) -> None:
         # delete files on disk
         rmtree(join(self.path, layer_name))
 
-    def add_symlink_layer(self, foreign_layer_path: Path) -> LayerT:
+    def add_symlink_layer(self, foreign_layer_path: Union[str, Path]) -> LayerT:
         foreign_layer_path = Path(os.path.abspath(foreign_layer_path))
         layer_name = foreign_layer_path.name
         if layer_name in self.layers.keys():
@@ -396,9 +397,10 @@ def copy_dataset(
 
     def to_wk_dataset(
         self,
-        new_dataset_path: Path,
+        new_dataset_path: Union[str, Path],
         scale: Optional[Tuple[float, float, float]] = None,
     ) -> "WKDataset":
+        new_dataset_path = Path(new_dataset_path)
         if scale is None:
             scale = self.properties.scale
         new_ds = WKDataset.create(new_dataset_path, scale=scale)
@@ -407,10 +409,11 @@ def to_wk_dataset(
 
     def to_tiff_dataset(
         self,
-        new_dataset_path: Path,
+        new_dataset_path: Union[str, Path],
         scale: Optional[Tuple[float, float, float]] = None,
         pattern: Optional[str] = None,
     ) -> "TiffDataset":
+        new_dataset_path = Path(new_dataset_path)
         if scale is None:
             scale = self.properties.scale
         new_ds = TiffDataset.create(new_dataset_path, scale=scale, pattern=pattern)
@@ -419,11 +422,12 @@ def to_tiff_dataset(
 
     def to_tiled_tiff_dataset(
         self,
-        new_dataset_path: Path,
+        new_dataset_path: Union[str, Path],
         tile_size: Tuple[int, int],
         scale: Optional[Tuple[float, float, float]] = None,
         pattern: Optional[str] = None,
     ) -> "TiledTiffDataset":
+        new_dataset_path = Path(new_dataset_path)
         if scale is None:
             scale = self.properties.scale
         new_ds = TiledTiffDataset.create(
@@ -444,16 +448,18 @@ def _get_type(self) -> Type["AbstractDataset"]:
 class WKDataset(AbstractDataset[WKLayer]):
     @classmethod
     def create(
-        cls, dataset_path: Path, scale: Tuple[float, float, float]
+        cls, dataset_path: Union[str, Path], scale: Tuple[float, float, float]
     ) -> "WKDataset":
+        dataset_path = Path(dataset_path)
         name = basename(normpath(dataset_path))
         properties = WKProperties(dataset_path / Properties.FILE_NAME, name, scale)
         return cast(WKDataset, WKDataset.create_with_properties(properties))
 
     @classmethod
     def get_or_create(
-        cls, dataset_path: Path, scale: Tuple[float, float, float]
+        cls, dataset_path: Union[str, Path], scale: Tuple[float, float, float]
     ) -> "WKDataset":
+        dataset_path = Path(dataset_path)
         if (
             dataset_path / Properties.FILE_NAME
         ).exists():  # use the properties file to check if the Dataset exists
@@ -465,7 +471,7 @@ def get_or_create(
         else:
             return cls.create(dataset_path, scale)
 
-    def __init__(self, dataset_path: Path) -> None:
+    def __init__(self, dataset_path: Union[str, Path]) -> None:
         super().__init__(dataset_path)
         self._data_format = "wkw"
         assert isinstance(self.properties, WKProperties)
@@ -488,10 +494,11 @@ class TiffDataset(AbstractDataset[TiffLayer]):
     @classmethod
     def create(
         cls,
-        dataset_path: Path,
+        dataset_path: Union[str, Path],
         scale: Tuple[float, float, float],
         pattern: Optional[str] = None,
     ) -> "TiffDataset":
+        dataset_path = Path(dataset_path)
         if pattern is None:
             pattern = "{zzzzz}.tif"
         validate_pattern(pattern)
@@ -508,13 +515,13 @@ def create(
     @classmethod
     def get_or_create(
         cls,
-        dataset_path: Path,
+        dataset_path: Union[str, Path],
         scale: Tuple[float, float, float],
         pattern: str = None,
     ) -> "TiffDataset":
-        if os.path.exists(
-            join(dataset_path, Properties.FILE_NAME)
-        ):  # use the properties file to check if the Dataset exists
+        dataset_path = Path(dataset_path)
+        if (dataset_path / Properties.FILE_NAME).exists():
+            # use the properties file to check if the Dataset exists
             ds = TiffDataset(dataset_path)
             assert tuple(ds.properties.scale) == tuple(
                 scale
@@ -530,7 +537,7 @@ def get_or_create(
             else:
                 return cls.create(dataset_path, scale, pattern)
 
-    def __init__(self, dataset_path: Path) -> None:
+    def __init__(self, dataset_path: Union[str, Path]) -> None:
         super().__init__(dataset_path)
         self.data_format = "tiff"
         assert isinstance(self.properties, TiffProperties)
@@ -553,11 +560,12 @@ class TiledTiffDataset(AbstractDataset[TiledTiffLayer]):
     @classmethod
     def create(
         cls,
-        dataset_path: Path,
+        dataset_path: Union[str, Path],
         scale: Tuple[float, float, float],
         tile_size: Tuple[int, int],
         pattern: Optional[str] = None,
     ) -> "TiledTiffDataset":
+        dataset_path = Path(dataset_path)
         if pattern is None:
             pattern = "{xxxxx}/{yyyyy}/{zzzzz}.tif"
         validate_pattern(pattern)
@@ -576,14 +584,14 @@ def create(
     @classmethod
     def get_or_create(
         cls,
-        dataset_path: Path,
+        dataset_path: Union[str, Path],
         scale: Tuple[float, float, float],
         tile_size: Tuple[int, int],
         pattern: str = None,
     ) -> "TiledTiffDataset":
-        if os.path.exists(
-            join(dataset_path, Properties.FILE_NAME)
-        ):  # use the properties file to check if the Dataset exists
+        dataset_path = Path(dataset_path)
+        if (dataset_path / Properties.FILE_NAME).exists():
+            # use the properties file to check if the Dataset exists
             ds = TiledTiffDataset(dataset_path)
             assert tuple(ds.properties.scale) == tuple(
                 scale
@@ -603,7 +611,7 @@ def get_or_create(
             else:
                 return cls.create(dataset_path, scale, tile_size, pattern)
 
-    def __init__(self, dataset_path: Path) -> None:
+    def __init__(self, dataset_path: Union[str, Path]) -> None:
         super().__init__(dataset_path)
         self.data_format = "tiled_tiff"
         assert isinstance(self.properties, TiffProperties)


Test patch: