Repository: iBridges-for-iRODS/iBridges (language: python)

Issue:
Extend ibridges/__init__.py
Should contain:

**from data_operations**
- upload
- download
- sync
- get_dataobject
- get_collection
- obj_replicas
- get_size

**modules**
- permissions
- resources
- rules
- tickets

Adjust tutorials/manuals


Discussion / hints:

Fix patch:
diff --git a/.flake8 b/.flake8
deleted file mode 100644
index 07c2714e..00000000
--- a/.flake8
+++ /dev/null
@@ -1,5 +0,0 @@
-[flake8]
-max-line-length = 120
-per-file-ignores =
-    # imported but unused
-    __init__.py: F401
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 5b41375d..54c1abfb 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -43,6 +43,10 @@ jobs:
       run: |
         pylint ibridges
 
+    - name: Check types with MyPy
+      run: |
+        mypy ibridges
+
     - name: Test with pytest
       run: |
         pip install pytest
diff --git a/ibridges/__init__.py b/ibridges/__init__.py
index 5d1416cf..676815ae 100644
--- a/ibridges/__init__.py
+++ b/ibridges/__init__.py
@@ -1,6 +1,12 @@
 """iBridges package that implements an API for iRods."""
 
-from ibridges.irodsconnector.session import Session
-from ibridges.utils.path import IrodsPath
+from ibridges.data_operations import download, get_collection, get_dataobject, upload
+from ibridges.meta import MetaData
+from ibridges.path import IrodsPath
+from ibridges.search import search_data
+from ibridges.session import Session
+from ibridges.sync import sync_data
+from ibridges.tickets import Tickets
 
-__all__ = ["Session", "IrodsPath"]
+__all__ = ["Session", "IrodsPath", "download", "get_collection", "get_dataobject", "upload",
+           "MetaData", "Tickets", "search_data", "sync_data"]
diff --git a/ibridges/irodsconnector/data_operations.py b/ibridges/data_operations.py
similarity index 86%
rename from ibridges/irodsconnector/data_operations.py
rename to ibridges/data_operations.py
index a5dbe530..d9cbd2d8 100644
--- a/ibridges/irodsconnector/data_operations.py
+++ b/ibridges/data_operations.py
@@ -1,5 +1,4 @@
-""" collections and data objects
-"""
+"""Collections and data objects."""
 import os
 import warnings
 from pathlib import Path
@@ -10,9 +9,9 @@
 import irods.exception
 from irods.models import DataObject
 
-from ibridges.irodsconnector import keywords as kw
-from ibridges.irodsconnector.session import Session
-from ibridges.utils.path import IrodsPath
+from ibridges import keywords as kw
+from ibridges.path import IrodsPath
+from ibridges.session import Session
 
 
 def get_dataobject(session: Session,
@@ -21,6 +20,8 @@ def get_dataobject(session: Session,
 
     Parameters
     ----------
+    session :
+        Session with connection to the server to get the data object from.
     path : str
         Name of an iRODS data object.
 
@@ -40,8 +41,9 @@ def get_dataobject(session: Session,
     raise irods.exception.DataObjectDoesNotExist(path)
 
 def obj_replicas(obj: irods.data_object.iRODSDataObject) -> list[tuple[int, str, str, int, str]]:
-    """Retrieves information about replicas (copies of the file on different resources)
-    of the data object in the iRODS system.
+    """Retrieve information about replicas (copies of the file on different resources).
+
+    It does so for a data object in the iRODS system.
 
     Parameters
     ----------
@@ -50,10 +52,11 @@ def obj_replicas(obj: irods.data_object.iRODSDataObject) -> list[tuple[int, str,
 
     Returns
     -------
-    list(tuple(int, str, str, int, str))
+    list(tuple(int, str, str, int, str)):
         List with tuple where each tuple contains replica index/number, resource name on which
         the replica is stored about one replica, replica checksum, replica size,
         replica status of the replica
+
     """
     #replicas = []
     repl_states = {
@@ -74,6 +77,8 @@ def get_collection(session: Session,
 
     Parameters
     ----------
+    session :
+        Session to get the collection from.
     path : str
         Name of an iRODS collection.
 
@@ -92,13 +97,11 @@ def get_collection(session: Session,
     raise irods.exception.CollectionDoesNotExist(path)
 
 def is_dataobject(item) -> bool:
-    """Determine if item is an iRODS data object
-    """
+    """Determine if item is an iRODS data object."""
     return isinstance(item, irods.data_object.iRODSDataObject)
 
 def is_collection(item) -> bool:
-    """Determine if item is an iRODS collection
-    """
+    """Determine if item is an iRODS collection."""
     return isinstance(item, irods.collection.iRODSCollection)
 
 def _obj_put(session: Session, local_path: Union[str, Path], irods_path: Union[str, IrodsPath],
@@ -107,12 +110,18 @@ def _obj_put(session: Session, local_path: Union[str, Path], irods_path: Union[s
 
     Parameters
     ----------
+    session :
+        Session to upload the object.
     local_path : str or Path
         Path of local file.
     irods_path : str or IrodsPath
         Path of iRODS data object or collection.
     resc_name : str
         Optional resource name.
+    overwrite :
+        Whether to overwrite the object if it exists.
+    options :
+        TODO: this seems currently unused?
 
     """
     local_path = Path(local_path)
@@ -126,12 +135,15 @@ def _obj_put(session: Session, local_path: Union[str, Path], irods_path: Union[s
                            irods_path / local_path.name).dataobject_exists() \
                  or irods_path.dataobject_exists()
 
-    options = {
+    if options is None:
+        options = {}
+    options.update({
         kw.ALL_KW: '',
         kw.NUM_THREADS_KW: kw.NUM_THREADS,
         kw.REG_CHKSUM_KW: '',
         kw.VERIFY_CHKSUM_KW: ''
-    }
+    })
+
     if resc_name not in ['', None]:
         options[kw.RESC_NAME_KW] = resc_name
     if overwrite or not obj_exists:
@@ -145,10 +157,14 @@ def _obj_get(session: Session, irods_path: Union[str, IrodsPath], local_path: Un
 
     Parameters
     ----------
+    session :
+        Session to get the object from.
     irods_path : str or IrodsPath
         Path of iRODS data object.
     local_path : str or Path
         Path of local file or directory/folder.
+    overwrite :
+        Whether to overwrite the local file if it exists.
     options : dict
         iRODS transfer options.
 
@@ -168,9 +184,7 @@ def _obj_get(session: Session, irods_path: Union[str, IrodsPath], local_path: Un
     session.irods_session.data_objects.get(str(irods_path), local_path, **options)
 
 def _create_irods_dest(local_path: Path, irods_path: IrodsPath):
-    """ Assmbles the irods destination paths for upload of a folder
-    """
-
+    """Assembles the irods destination paths for upload of a folder."""
     upload_path = irods_path.joinpath(local_path.name)
     paths = [(root.removeprefix(str(local_path)), f)
              for root, _, files in os.walk(local_path) for f in files]
@@ -185,10 +199,12 @@ def _upload_collection(session: Session, local_path: Union[str, Path],
                        irods_path: Union[str, IrodsPath],
                        overwrite: bool = False, resc_name: str = '',
                        options: Optional[dict] = None):
-    """Upload a local directory to iRODS
+    """Upload a local directory to iRODS.
 
     Parameters
     ----------
+    session :
+        Session to upload the collection to.
     local_path : Path
         Absolute path to the directory to upload
     irods_path : IrodsPath
@@ -199,6 +215,7 @@ def _upload_collection(session: Session, local_path: Union[str, Path],
         Name of the resource to which data is uploaded, by default the server will decide
     options : dict
         More options for the upload
+
     """
     local_path = Path(local_path)
     irods_path = IrodsPath(session, irods_path)
@@ -215,8 +232,7 @@ def _upload_collection(session: Session, local_path: Union[str, Path],
             warnings.warn(f'Upload: Object already exists\n\tSkipping {source}')
 
 def _create_local_dest(session: Session, irods_path: IrodsPath, local_path: Path):
-    """Assmbles the local destination paths for download of a collection
-    """
+    """Assembles the local destination paths for download of a collection."""
     # get all data objects
     coll = get_collection(session, irods_path)
     all_objs = _get_data_objects(session, coll)
@@ -233,10 +249,12 @@ def _create_local_dest(session: Session, irods_path: IrodsPath, local_path: Path
 
 def _download_collection(session: Session, irods_path: Union[str, IrodsPath], local_path: Path,
                          overwrite: bool = False, options: Optional[dict] = None):
-    """Download a collection to the local filesystem
+    """Download a collection to the local filesystem.
 
     Parameters
     ----------
+    session :
+        Session to download the collection from.
     irods_path : IrodsPath
         Absolute irods source path pointing to a collection
     local_path : Path
@@ -245,8 +263,8 @@ def _download_collection(session: Session, irods_path: Union[str, IrodsPath], lo
         Overwrite existing local data
     options : dict
         More options for the download
-    """
 
+    """
     irods_path = IrodsPath(session, irods_path)
     if not irods_path.collection_exists():
         raise ValueError("irods_path must be a collection.")
@@ -264,10 +282,12 @@ def _download_collection(session: Session, irods_path: Union[str, IrodsPath], lo
 
 def upload(session: Session, local_path: Union[str, Path], irods_path: Union[str, IrodsPath],
            overwrite: bool = False, resc_name: str = '', options: Optional[dict] = None):
-    """Upload a local directory  or file to iRODS
+    """Upload a local directory or file to iRODS.
 
     Parameters
     ----------
+    session :
+        Session to upload the data to.
     local_path : Path
         Absolute path to the directory to upload
     irods_path : IrodsPath
@@ -278,8 +298,8 @@ def upload(session: Session, local_path: Union[str, Path], irods_path: Union[str
         Name of the resource to which data is uploaded, by default the server will decide
     options : dict
         More options for the upload
-    """
 
+    """
     local_path = Path(local_path)
     try:
         if local_path.is_dir():
@@ -292,10 +312,12 @@ def upload(session: Session, local_path: Union[str, Path], irods_path: Union[str
 
 def download(session: Session, irods_path: Union[str, IrodsPath], local_path: Union[str, Path],
              overwrite: bool = False, _resc_name: str = '', options: Optional[dict] = None):
-    """Download a collection or data object to the local filesystem
+    """Download a collection or data object to the local filesystem.
 
     Parameters
     ----------
+    session :
+        Session to download the collection from.
     irods_path : IrodsPath
         Absolute irods source path pointing to a collection
     local_path : Path
@@ -304,6 +326,7 @@ def download(session: Session, irods_path: Union[str, IrodsPath], local_path: Un
         Overwrite existing local data
     options : dict
         More options for the download
+
     """
     irods_path = IrodsPath(session, irods_path)
     local_path = Path(local_path)
@@ -319,17 +342,18 @@ def download(session: Session, irods_path: Union[str, IrodsPath], local_path: Un
 
 def get_size(session: Session, item: Union[irods.data_object.iRODSDataObject,
                                irods.collection.iRODSCollection]) -> int:
-    """Collect the sizes of a data object or a
-    collection.
+    """Collect the sizes of a data object or a collection.
 
     Parameters
     ----------
+    session :
+        Session with the connection to the item.
     item : iRODSDataObject or iRODSCollection
-
+        Collection or data object to get the size of.
 
     Returns
     -------
-    int
+    int :
         Total size [bytes] of the iRODS object or all iRODS objects in the collection.
 
     """
@@ -339,20 +363,22 @@ def get_size(session: Session, item: Union[irods.data_object.iRODSDataObject,
     return sum(size for _, _, size, _ in all_objs)
 
 def _get_data_objects(session: Session,
-                      coll: irods.collection.iRODSCollection) -> list[str, str, int, str]:
+                      coll: irods.collection.iRODSCollection) -> list[tuple[str, str, int, str]]:
     """Retrieve all data objects in a collection and all its subcollections.
 
     Parameters
     ----------
+    session:
+        Session to get the data objects with.
     coll : irods.collection.iRODSCollection
-        The collection to search for all data pbjects
+        The collection to search for all data objects
 
     Returns
     -------
     list of all data objects
-        [(cllection path, name, size, checksum)]
-    """
+        [(collection path, name, size, checksum)]
 
+    """
     # all objects in the collection
     objs = [(obj.collection.path, obj.name, obj.size, obj.checksum)
             for obj in coll.data_objects]
@@ -373,8 +399,11 @@ def create_collection(session: Session,
 
     Parameters
     ----------
+    session:
+        Session to create the collection for.
     coll_path: IrodsPath
         Collection path
+
     """
     try:
         return session.irods_session.collections.create(str(coll_path))
diff --git a/ibridges/interactive.py b/ibridges/interactive.py
index b828abd0..8f2c3152 100644
--- a/ibridges/interactive.py
+++ b/ibridges/interactive.py
@@ -1,17 +1,16 @@
-"""Interactive authentication with iRODS server.
-"""
+"""Interactive authentication with iRODS server."""
 
 import json
 import os
-from pathlib import Path
-from typing import Union
 from getpass import getpass
+from pathlib import Path
+from typing import Optional, Union
 
-from ibridges.irodsconnector.session import Session
+from ibridges.session import Session
 
 DEFAULT_IENV_PATH = Path(os.path.expanduser("~")).joinpath(".irods", "irods_environment.json")
 
-def interactive_auth(password: str = None,
+def interactive_auth(password: Optional[str] = None,
                      irods_env_path: Union[str, Path] = DEFAULT_IENV_PATH) -> Session:
     """Interactive authentication with iRODS server.
 
diff --git a/ibridges/irodsconnector/__init__.py b/ibridges/irodsconnector/__init__.py
deleted file mode 100644
index 33dcc72c..00000000
--- a/ibridges/irodsconnector/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-""" irodsConnector irods modules
-"""
diff --git a/ibridges/irodsconnector/keywords.py b/ibridges/keywords.py
similarity index 98%
rename from ibridges/irodsconnector/keywords.py
rename to ibridges/keywords.py
index 9a3b1f70..20b220ac 100644
--- a/ibridges/irodsconnector/keywords.py
+++ b/ibridges/keywords.py
@@ -1,4 +1,4 @@
-"""Keywords and definitions"""
+"""Keywords and definitions."""
 import irods.column as cm
 import irods.keywords as kw
 import irods.models as imodels
diff --git a/ibridges/irodsconnector/meta.py b/ibridges/meta.py
similarity index 72%
rename from ibridges/irodsconnector/meta.py
rename to ibridges/meta.py
index 7827ecf8..0c4d1d50 100644
--- a/ibridges/irodsconnector/meta.py
+++ b/ibridges/meta.py
@@ -1,5 +1,4 @@
-""" metadata operations
-"""
+"""metadata operations."""
 from typing import Iterator, Optional, Sequence, Union
 
 import irods.exception
@@ -7,16 +6,25 @@
 
 
 class MetaData():
-    """Irods metadata operations """
+    """Irods metadata operations."""
 
     def __init__(self, item):
+        """Initialize the metadata object.
+
+        Parameters
+        ----------
+        item
+            The data object or collection to attach the metadata object to.
+
+        """
         self.item = item
 
     def __iter__(self) -> Iterator:
-        for m in self.item.metadata.items():
-            yield m
+        """Iterate over all metadata key/value/units pairs."""
+        yield from self.item.metadata.items()
 
     def __contains__(self, val: Union[str, Sequence]) -> bool:
+        """Check whether a key, key/val, key/val/units pairs are in the metadata."""
         if isinstance(val, str):
             val = [val]
         all_attrs = ["name", "value", "units"][:len(val)]
@@ -32,8 +40,12 @@ def __contains__(self, val: Union[str, Sequence]) -> bool:
         return False
 
     def __repr__(self) -> str:
-        """Create a sorted representation of the metadata"""
+        """Create a sorted representation of the metadata."""
+        return f"MetaData<{self.item.path}>"
+
 
+    def __str__(self) -> str:
+        """Return a string showing all metadata entries."""
         # Sort the list of items name -> value -> units, where None is the lowest
         meta_list = list(self)
         meta_list = sorted(meta_list, key=lambda m: (m.units is None, m.units))
@@ -44,22 +56,23 @@ def __repr__(self) -> str:
             meta_str += f" - {{name: {m.name}, value: {m.value}, units: {m.units}}}\n"
         return meta_str
 
-    def __str__(self) -> str:
-        return self.__repr__()
-
     def add(self, key: str, value: str, units: Optional[str] = None):
-        """
-        Adds metadata to all items
+        """Add metadata to an item.
+
+        This will never overwrite an existing entry.
 
         Parameters
         ----------
-        items: list of iRODS data objects or iRODS collections
-        key: string
-        value: string
-        units: (optional) string
+        key:
+            Key of the new entry to add to the item.
+        value:
+            Value of the new entry to add to the item.
+        units:
+            The units of the new entry.
 
         Throws:
             CATALOG_ALREADY_HAS_ITEM_BY_THAT_NAME
+
         """
         try:
             if (key, value, units) in self:
@@ -80,29 +93,34 @@ def set(self, key: str, value: str, units: Optional[str] = None):
 
         Parameters
         ----------
-        items: list of iRODS data objects or iRODS collections
-        key: string
-        value: string
-        units: (optional) string
+        key:
+            Key of the new entry to add to the item.
+        value:
+            Value of the new entry to add to the item.
+        units:
+            The units of the new entry.
 
         Throws: CAT_NO_ACCESS_PERMISSION
+
         """
         self.delete(key, None)
         self.add(key, value, units)
 
     def delete(self, key: str, value: Optional[str], units: Optional[str] = None):
-        """
-        Deletes a metadata entry of all items
+        """Delete a metadata entry of an item.
 
         Parameters
         ----------
-        items: list of iRODS data objects or iRODS collections
-        key: string
-        value: string
-        units: (optional) string
+        key:
+            Key of the new entry to add to the item.
+        value:
+            Value of the new entry to add to the item.
+        units:
+            The units of the new entry.
 
         Throws:
             CAT_SUCCESS_BUT_WITH_NO_INFO: metadata did not exist
+
         """
         try:
             if value is None:
@@ -121,7 +139,6 @@ def delete(self, key: str, value: Optional[str], units: Optional[str] = None):
                              "path '{item.path}'.") from error
 
     def clear(self):
-        """Delete all metadata belonging to the item.
-        """
+        """Delete all metadata belonging to the item."""
         for meta in self:
             self.item.metadata.remove(meta)
diff --git a/ibridges/utils/path.py b/ibridges/path.py
similarity index 74%
rename from ibridges/utils/path.py
rename to ibridges/path.py
index e6d64a61..ec121018 100644
--- a/ibridges/utils/path.py
+++ b/ibridges/path.py
@@ -1,6 +1,4 @@
-"""
-A classes to handle iRODS and local (Win, linux) paths.
-"""
+"""A class to handle iRODS and local (Win, linux) paths."""
 from __future__ import annotations
 
 from pathlib import PurePosixPath
@@ -14,6 +12,18 @@ class IrodsPath():
     _current_working_path = ""
 
     def __init__(self, session, *args):
+        """Initialize IrodsPath object similar to the Path object.
+
+        It does take an extra argument in session.
+
+        Parameters
+        ----------
+        session:
+            Session that is used for the ipath.
+        args:
+            Specification of the path. For example: "x/z" or "x", "z".
+
+        """
         self.session = session
         assert hasattr(session, "irods_session")
         # We don't want recursive IrodsPaths, so we take the
@@ -24,10 +34,7 @@ def __init__(self, session, *args):
         super().__init__()
 
     def absolute_path(self) -> str:
-        """
-        Return the path if the path starts with '/zone/home', otherwise
-        concatenate the '/zone/home' prefix to the current path.
-        """
+        """Return the path if the absolute irods path."""
         # absolute path
         if len(self._path.parts) == 0:
             return self.session.home
@@ -41,15 +48,19 @@ def absolute_path(self) -> str:
 
 
     def __str__(self) -> str:
+        """Get the absolute path if converting to string."""
         return self.absolute_path()
 
     def __repr__(self) -> str:
+        """Representation of the IrodsPath object in line with a Path object."""
         return f"IrodsPath({', '.join(self._path.parts)})"
 
     def __truediv__(self, other) -> IrodsPath:
+        """Ensure that we can append just like the Path object."""
         return self.__class__(self.session, self._path, other)
 
     def __getattribute__(self, attr):
+        """Make the IrodsPath transparent so that some Path functionality is available."""
         if attr in ["name", "parts"]:
             return self._path.__getattribute__(attr)
         return super().__getattribute__(attr)
@@ -60,22 +71,23 @@ def joinpath(self, *args):
         Returns
         -------
             The concatenated path.
+
         """
         return IrodsPath(self.session, self._path, *args)
 
     @property
     def parent(self):
-        """Return the parent directory of the current directory
+        """Return the parent directory of the current directory.
 
         Returns
         -------
             The parent just above the current directory
+
         """
         return IrodsPath(self.session, self._path.parent)
 
     def remove(self):
-        """Removes the data behind an iRODS path
-        """
+        """Remove the data behind an iRODS path."""
         try:
             if self.collection_exists():
                 coll = self.session.irods_session.collections.get(str(self))
@@ -89,13 +101,13 @@ def remove(self):
 
     @staticmethod
     def create_collection(session,  coll_path: str) -> irods.collection.iRODSCollection:
-        """Create a collection and all collections in its path. Return he collection. If the
-        collection already exists, return ir.
+        """Create a collection and all collections in its path.
 
-        Return
+        Return:
         ------
         irods.collection.iRODSCollection
-            The
+            The newly created collection.
+
         """
         try:
             return session.irods_session.collections.create(str(coll_path))
@@ -104,36 +116,28 @@ def create_collection(session,  coll_path: str) -> irods.collection.iRODSCollect
                 "While creating collection '{coll_path}': iRODS server forbids action.") from exc
 
     def rename(self):
-        """
-        Rename the collection or data object
-        """
+        """Rename the collection or data object."""
         raise NotImplementedError("Rename not implemented yet.")
 
     def collection_exists(self) -> bool:
-        """
-        Check if the path points to an iRODS collection
-        """
+        """Check if the path points to an iRODS collection."""
         return self.session.irods_session.collections.exists(str(self))
 
     def dataobject_exists(self) -> bool:
-        """
-        Check if the path points to an iRODS data object
-        """
+        """Check if the path points to an iRODS data object."""
         return self.session.irods_session.data_objects.exists(str(self))
 
     def exists(self) -> bool:
-        """
-        Check if the path already exists on the iRODS server
-        """
+        """Check if the path already exists on the iRODS server."""
         return self.dataobject_exists() or self.collection_exists()
 
     def walk(self, depth: int):
-        """
-        Walk on a collection.
+        """Walk on a collection.
 
         Parameters
         ----------
         depth : int
             Stops after depth many iterations, even if the tree is deeper.
+
         """
         raise NotImplementedError("Walk method not implemented yet.")
diff --git a/ibridges/irodsconnector/permissions.py b/ibridges/permissions.py
similarity index 79%
rename from ibridges/irodsconnector/permissions.py
rename to ibridges/permissions.py
index 7536157c..ceb65d8d 100644
--- a/ibridges/irodsconnector/permissions.py
+++ b/ibridges/permissions.py
@@ -1,4 +1,4 @@
-""" permission operations """
+"""permission operations."""
 from collections import defaultdict
 from typing import Iterator, Optional
 
@@ -9,17 +9,28 @@
 
 
 class Permissions():
-    """Irods permissions operations"""
+    """Irods permissions operations."""
 
     def __init__(self, session, item) -> None:
+        """Initialize the permissions object.
+
+        Parameters
+        ----------
+        session
+            Session that contains the item.
+        item
+            Data object or collection to create adjust the permissions for.
+
+        """
         self.session = session
         self.item = item
 
     def __iter__(self) -> Iterator:
-        for perm in self.session.irods_session.acls.get(self.item):
-            yield perm
+        """Iterate over all ACLs."""
+        yield from self.session.irods_session.acls.get(self.item)
 
     def __str__(self) -> str:
+        """Create a string table of all currently set permissions."""
         acl_dict = defaultdict(list)
         for perm in self:
             acl_dict[f'{perm.user_name}#{perm.user_zone}'].append(
@@ -37,7 +48,7 @@ def __str__(self) -> str:
 
     @property
     def available_permissions(self) -> dict:
-        """Get available permissions"""
+        """Get available permissions."""
         return self.session.irods_session.available_permissions.codes
 
     def set(self, perm: str, user: Optional[str] = None, zone: Optional[str] = None,
diff --git a/ibridges/irodsconnector/resources.py b/ibridges/resources.py
similarity index 88%
rename from ibridges/irodsconnector/resources.py
rename to ibridges/resources.py
index 6e0641db..ea1096a9 100644
--- a/ibridges/irodsconnector/resources.py
+++ b/ibridges/resources.py
@@ -1,24 +1,24 @@
-""" resource operations
-"""
+"""resource operations."""
 from typing import Optional
 
 import irods.exception
 import irods.resource
 
-from ibridges.irodsconnector import keywords as kw
-from ibridges.irodsconnector.session import Session
+from ibridges import keywords as kw
+from ibridges.session import Session
 
 
 class Resources():
-    """Irods Resource operations """
+    """Irods Resource operations."""
 
     def __init__(self, session: Session):
-        """ iRODS resource initialization
+        """IRODS resource initialization.
+
+        Parameters
+        ----------
+        session : Session
+            Instance of the Session class
 
-            Parameters
-            ----------
-            session : Session
-                instance of the Session class
         """
         self._resources: Optional[dict] = None
         self.session = session
@@ -36,7 +36,8 @@ def get_resource(self, resc_name: str) -> irods.resource.iRODSResource:
         iRODSResource
             Instance of the resource with `resc_name`.
 
-        Raises:
+        Raises
+        ------
             irods.exception.ResourceDoesNotExist
 
         """
@@ -102,7 +103,7 @@ def get_resource_children(self, resc: irods.resource.iRODSResource) -> list:
         return resc.children + children
 
     def resources(self, update: bool = False) -> dict:
-        """iRODS resources and their metadata.
+        """IRODS resources and their metadata.
 
         Parameters
         ----------
@@ -142,13 +143,16 @@ def resources(self, update: bool = False) -> dict:
         return self._resources
 
     @property
-    def root_resources(self) -> list:
-        """
-        Filter resources for all root resources (data can only be written to root resources).
+    def root_resources(self) -> list[tuple]:
+        """Filter resources for all root resources.
+
+        Data can only be written to root resources.
         Return their names, their status and their free space.
 
-        Returns:
+        Returns
+        -------
         List [(resource_name, status, free_space, context)]
+
         """
         parents = [(key, val) for key, val in self.resources().items() if not val['parent']]
         return [(resc[0], resc[1]["status"], resc[1]["free_space"], resc[1]["context"])
diff --git a/ibridges/irodsconnector/rules.py b/ibridges/rules.py
similarity index 96%
rename from ibridges/irodsconnector/rules.py
rename to ibridges/rules.py
index 6cf8e9a0..6b82ed77 100644
--- a/ibridges/irodsconnector/rules.py
+++ b/ibridges/rules.py
@@ -1,11 +1,10 @@
-""" rule operations
-"""
+"""Rule operations."""
 import logging
 
 import irods.exception
 import irods.rule
 
-from ibridges.irodsconnector.session import Session
+from ibridges.session import Session
 
 
 def execute_rule(session: Session, rule_file: str, params: dict,
diff --git a/ibridges/search.py b/ibridges/search.py
index cac2dc16..2e5ca5dc 100644
--- a/ibridges/search.py
+++ b/ibridges/search.py
@@ -1,20 +1,24 @@
-""" Data query
-"""
+"""Data query."""
 from typing import Optional, Union
 
-from ibridges.irodsconnector import keywords as kw
-from ibridges.irodsconnector.session import Session
-from ibridges.utils.path import IrodsPath
+from ibridges import keywords as kw
+from ibridges.path import IrodsPath
+from ibridges.session import Session
 
-def search(session: Session, path: Optional[Union[str, IrodsPath]] = None,
+
+def search_data(session: Session, path: Optional[Union[str, IrodsPath]] = None,
            checksum: Optional[str] = None, key_vals: Optional[dict] = None) -> list[dict]:
-    """Retrieves all collections and data objects (the absolute collection path,
+    """Retrieve all collections and data objects.
+
+    (the absolute collection path,
     data object or collection name) to the given user-defined and system metadata.
     By Default all accessible collections and data objects will be returned.
     Wildcard: %
 
     Parameters
     ----------
+    session:
+        Session to search with.
     path: str
         (Partial) path or IrodsPath
     checksum: str
@@ -26,7 +30,7 @@ def search(session: Session, path: Optional[Union[str, IrodsPath]] = None,
     -------
     list: [dict]
         List of dictionaries with keys: COLL_NAME (absolute path of the collection),
-                                        DATA_NAME (name of the data object), 
+                                        DATA_NAME (name of the data object),
                                         D_DATA_CHECKSUM (checksum of the data object)
         The latter two keys are only present of the found item is a data object.
 
diff --git a/ibridges/irodsconnector/session.py b/ibridges/session.py
similarity index 87%
rename from ibridges/irodsconnector/session.py
rename to ibridges/session.py
index 527dd44e..a4348c5c 100644
--- a/ibridges/irodsconnector/session.py
+++ b/ibridges/session.py
@@ -1,36 +1,37 @@
-""" session operations
-"""
+"""session operations."""
 import json
 import os
 import warnings
+from pathlib import Path
+from typing import Optional, Union
 
 import irods.session
 from irods.exception import NetworkException
 
-from ibridges.irodsconnector.keywords import exceptions
+from ibridges.keywords import exceptions
 
 
 class Session:
-    """Irods session authentication.
+    """Irods session authentication."""
 
-    """
-
-    def __init__(self, irods_env: dict = None, irods_env_path: str = None,
-                 password: str = None, irods_home: str = None):
-        """ iRODS authentication with Python client.
+    def __init__(self, irods_env: Optional[dict] = None,
+                 irods_env_path: Optional[Union[str, Path]] = None,
+                 password: Optional[str] = None, irods_home: Optional[str] = None):
+        """IRODS authentication with Python client.
 
         Parameters
         ----------
-        irods_conf: dict
+        irods_env: dict
             Dictionary from irods_environment.json
         irods_env_path:
-            File to read the dictionary from if irods_conf is not supplied.
+            File to read the dictionary from if irods_env is not supplied.
         password : str
             Plain text password.
         irods_home:
             Override the home directory of irods. Otherwise attempt to retrive the value
             from the irods environment dictionary. If it is not there either, then use
             /{zone}/home/{username}.
+
         """
         if irods_env is None and irods_env_path is None:
             raise ValueError("CONNECTION ERROR: no irods environment given.")
@@ -40,11 +41,14 @@ def __init__(self, irods_env: dict = None, irods_env_path: str = None,
             if os.path.isfile(irods_env_path):
                 with open(irods_env_path, "r", encoding="utf-8") as f:
                     irods_env = json.load(f)
+                if not isinstance(irods_env, dict):
+                    raise TypeError(f"Error reading environment file '{irods_env_path}': "
+                                    f"expected dictionary, got {type(irods_env)}.")
             else:
                 raise ValueError(f"CONNECTION ERROR: {irods_env_path} path does not exist.")
 
         self._password = password
-        self._irods_env = irods_env
+        self._irods_env: dict = irods_env  # type: ignore
         self._irods_env_path = irods_env_path
         self._irods_session = self.connect()
         if irods_home is not None:
@@ -62,6 +66,7 @@ def home(self) -> str:
         Returns
         -------
             The current working directory in the current session.
+
         """
         return self._irods_env["irods_home"]
 
@@ -70,11 +75,12 @@ def home(self, value):
         self._irods_env["irods_home"] = value
 
     def __del__(self):
+        """Clean up the irods session as well."""
         del self.irods_session
 
     @property
     def irods_session(self) -> irods.session.iRODSSession:
-        """iRODS session creation.
+        """IRODS session creation.
 
         Returns
         -------
@@ -86,8 +92,7 @@ def irods_session(self) -> irods.session.iRODSSession:
 
     @irods_session.deleter
     def irods_session(self):
-        """Properly delete iRODS session.
-        """
+        """Properly delete iRODS session."""
         if self._irods_session is not None:
             # In case the iRODS session is not fully there.
             try:
@@ -100,8 +105,6 @@ def irods_session(self):
             self._irods_session = None
 
     # Authentication workflow methods
-    #
-
     def has_valid_irods_session(self) -> bool:
         """Check if the iRODS session is valid.
 
@@ -114,9 +117,7 @@ def has_valid_irods_session(self) -> bool:
         return self.server_version != ()
 
     def connect(self):
-        """Establish an iRODS session.
-
-        """
+        """Establish an iRODS session."""
         user = self._irods_env.get('irods_user_name', '')
         if user == 'anonymous':
             # TODOx: implement and test for SSL enabled iRODS
@@ -173,10 +174,7 @@ def authenticate_using_auth_file(self):
             raise e
 
     def write_pam_password(self):
-        """Store the password in the iRODS
-        authentication file in obfuscated form.
-
-        """
+        """Store the password in the iRODS authentication file in obfuscated form."""
         connection = self._irods_session.pool.get_connection()
         pam_passwords = self._irods_session.pam_pw_negotiated
         if len(pam_passwords):
@@ -235,7 +233,7 @@ def port(self) -> str:
 
     @property
     def server_version(self) -> tuple:
-        """Retrieve version of the iRODS server
+        """Retrieve version of the iRODS server.
 
         Returns
         -------
diff --git a/ibridges/irodsconnector/sync.py b/ibridges/sync.py
similarity index 91%
rename from ibridges/irodsconnector/sync.py
rename to ibridges/sync.py
index 6d34428f..0be2df14 100644
--- a/ibridges/irodsconnector/sync.py
+++ b/ibridges/sync.py
@@ -1,4 +1,5 @@
-"""
+"""Synchronize data between local and remote copies.
+
 'sync' synchronizes the data between a local copy (local file system) and the copy stored in
 iRODS. The command can be in one of the two modes: synchronization of data from the client's
 local file system to iRODS, or from iRODS to the local file system. It broadly mirros the
@@ -6,71 +7,84 @@
 """
 
 from __future__ import annotations
-import os
+
 import base64
-from typing import Union, NamedTuple, Optional
-from pathlib import Path
+import os
 from hashlib import sha256
-from tqdm import tqdm
+from pathlib import Path
+from typing import NamedTuple, Optional, Union
+
 from irods.collection import iRODSCollection
-from ibridges import Session
-from ibridges.utils.path import IrodsPath
-from ibridges.irodsconnector.data_operations import get_collection, get_dataobject, \
-    create_collection, upload, download
+from tqdm import tqdm
+
+from ibridges.data_operations import (
+    create_collection,
+    download,
+    get_collection,
+    get_dataobject,
+    upload,
+)
+from ibridges.path import IrodsPath
+from ibridges.session import Session
+
 
 class FileObject(NamedTuple):
-    """
-    Object to hold attributes from local and remote files.
-    """
+    """Object to hold attributes from local and remote files."""
+
     name: str
     path: str
     size: int
     checksum: Optional[str]
 
 class FolderObject:
-    """Object to hold attributes from local and remote folders/collections.
-
-    Attributes
-    ----------
-    path : str
-        Path, relative to source or target root
-    n_files : int
-        Number of files in folder
-    n_folders : int
-        Number of subfolders in folder
-
-    Methods
-    -------
-    is_empty()
-        Check whether folder is empty.
-    """
+    """Object to hold attributes from local and remote folders/collections."""
 
     def __init__(self,
                  path: str = '',
                  n_files: int = 0,
                  n_folders: int = 0) -> None:
+        """Initialize FolderObject.
+
+        Attributes
+        ----------
+        path : str
+            Path, relative to source or target root
+        n_files : int
+            Number of files in folder
+        n_folders : int
+            Number of subfolders in folder
+
+        Methods
+        -------
+        is_empty()
+            Check whether folder is empty.
+
+        """
         self.path=path
         self.n_files=n_files
         self.n_folders=n_folders
 
     def is_empty(self):
-        """ Check to see if folder has anything (files or subfolders) in it. """
+        """Check to see if folder has anything (files or subfolders) in it."""
         return (self.n_files+self.n_folders)==0
 
     def __repr__(self):
+        """Give a nicer representation for debug purposes."""
         return f"{self.__class__.__name__}(path='{self.path}', n_files={self.n_files}, \
             n_folders={self.n_folders})"
 
     def __eq__(self, other: object):
+        """Check whether two folders (paths) are the same."""
         if not isinstance(other, FolderObject):
             return NotImplemented
 
         return self.path==other.path
 
     def __hash__(self):
+        """Hash the path as a hash for the folder."""
         return hash(self.path)
 
-def sync(session: Session,   #pylint: disable=too-many-arguments
+def sync_data(session: Session,   #pylint: disable=too-many-arguments
          source: Union[str, Path, IrodsPath],
          target: Union[str, Path, IrodsPath],
          max_level: Optional[int] = None,
@@ -79,10 +93,9 @@ def sync(session: Session,   #pylint: disable=too-many-arguments
          copy_empty_folders: bool = False,
          verify_checksum: bool = True,
          on_checksum_fail: str = 'error') -> None:
+    """Synchronize the data between a local copy (local file system) and the copy stored in iRODS.
 
-    """
-    Synchronize the data between a local copy (local file system) and the copy stored in iRODS. The
-    command can be in one of the two modes: synchronization of data from the client's local file
+    The command can be in one of the two modes: synchronization of data from the client's local file
     system to iRODS, or from iRODS to the local file system. The mode is determined by the type of
     the values for `source` and `target` (IrodsPath or str/Path).
 
@@ -117,8 +130,8 @@ def sync(session: Session,   #pylint: disable=too-many-arguments
         Behaviour when a checksum verification fails. 'warn' prints an error and procedes; 'error'
         terminates the program with an exception (default). To ignore checksum verification errors,
         set `verify_checksum` to False.
-    """
 
+    """
     _param_checks(source, target, on_checksum_fail)
 
     if isinstance(source, IrodsPath):
@@ -182,7 +195,7 @@ def sync(session: Session,   #pylint: disable=too-many-arguments
             copy_empty_folders=copy_empty_folders)
         _copy_irods_to_local(
             session=session,
-            source=source,
+            source=source,  # type: ignore
             target=Path(target),
             objects=files_diff,
             dry_run=dry_run,
diff --git a/ibridges/irodsconnector/tickets.py b/ibridges/tickets.py
similarity index 85%
rename from ibridges/irodsconnector/tickets.py
rename to ibridges/tickets.py
index aa3f3dfc..57b030c0 100644
--- a/ibridges/irodsconnector/tickets.py
+++ b/ibridges/tickets.py
@@ -1,5 +1,4 @@
-""" ticket operations
-"""
+"""Ticket operations."""
 from collections import namedtuple
 from datetime import date, datetime
 from typing import Iterable, Optional, Union
@@ -7,21 +6,21 @@
 import irods.ticket
 from irods.models import TicketQuery
 
-import ibridges.irodsconnector.keywords as kw
-from ibridges.irodsconnector.session import Session
+import ibridges.keywords as kw
+from ibridges.session import Session
 
 TicketData = namedtuple('TicketData', ["name", "type", "path", "expiration_date"])
 
 class Tickets():
-    """Irods Ticket operations """
+    """Irods Ticket operations."""
 
     def __init__(self, session: Session):
-        """ iRODS data operations initialization
+        """IRODS data operations initialization.
 
-            Parameters
-            ----------
-            session : session.Session
-                instance of the Session class
+        Parameters
+        ----------
+        session : session.Session
+            instance of the Session class
 
         """
         self.session = session
@@ -30,8 +29,9 @@ def __init__(self, session: Session):
     def create_ticket(self, obj_path: str,
                       ticket_type: Optional[str] = 'read',
                       expiry_date: Optional[Union[str, datetime, date]] = None) -> tuple:
-        """Create an iRODS ticket to allow read access to the object
-        referenced by `obj_path`.
+        """Create an iRODS ticket.
+
+        This allows read access to the object referenced by `obj_path`.
 
         Parameters
         ----------
@@ -39,7 +39,7 @@ def create_ticket(self, obj_path: str,
             Collection or data object path to create a ticket for.
         ticket_type: str
             read or write, default read
-        expiry_string : str
+        expiry_date : str
             Optional expiration date in the form: strftime('%Y-%m-%d.%H:%M:%S')
 
         Returns
@@ -69,8 +69,8 @@ def create_ticket(self, obj_path: str,
         return ticket.ticket, expiration_set
 
     def __iter__(self) -> Iterable[TicketData]:
-        for tick_data in self.update_tickets():
-            yield tick_data
+        """Iterate over all ticket data."""
+        yield from self.update_tickets()
 
     @property
     def all_ticket_strings(self) -> list[str]:
@@ -93,7 +93,7 @@ def delete_ticket(self, ticket: irods.ticket.Ticket, check: bool = False):
             raise KeyError(f"Cannot delete ticket: ticket '{ticket}' does not exist (anymore).")
 
     def update_tickets(self) -> list[TicketData]:
-        """retrieves all tickets and their metadata belonging to the user.
+        """Retrieve all tickets and their metadata belonging to the user.
 
         Parameters
         ----------
@@ -104,6 +104,7 @@ def update_tickets(self) -> list[TicketData]:
         -------
         list
             [(ticket string, ticket type, irods obj/coll path, expiry data in epoche)]
+
         """
         user = self.session.username
         self._all_tickets = []
@@ -118,18 +119,17 @@ def update_tickets(self) -> list[TicketData]:
         return self._all_tickets
 
     def clear(self):
-        """Delete all tickets.
-        """
+        """Delete all tickets."""
         for tick_data in self.update_tickets():
             tick = self.get_ticket(tick_data.name)
             self.delete_ticket(tick)
         self.update_tickets()
 
     def _id_to_path(self, itemid: str) -> str:
-        """
-        Given an iRODS item id (data object or collection) from the
-        TicketQuery.Ticket.object_id this function retrieves the corresponding
-        iRODS path.
+        """Get IRODS path from a given an iRODS item id.
+
+        The item (data object or collection) id should come from the
+        TicketQuery.Ticket.object_id.
 
         Parameters
         ----------
@@ -142,6 +142,7 @@ def _id_to_path(self, itemid: str) -> str:
         str
             collection or data object path
             returns '' if the identifier does not exist any longer
+
         """
         data_query = self.session.irods_session.query(kw.COLL_NAME, kw.DATA_NAME)
         data_query = data_query.filter(kw.DATA_ID == itemid)
diff --git a/ibridges/utils/__init__.py b/ibridges/utils/__init__.py
deleted file mode 100644
index e1c01f68..00000000
--- a/ibridges/utils/__init__.py
+++ /dev/null
@@ -1,9 +0,0 @@
-"""iBridges utility modules
-"""
-
-# from ibridges.utils import AmberConnector
-# from ibridges.irodsconnector import context
-# from ibridges.irodsconnector import json_config
-# from ibridges.irodsconnector import elabConnector
-# from ibridges.irodsconnector import path
-# from ibridges.irodsconnector import utils
diff --git a/old/_version.py b/old/_version.py
deleted file mode 100644
index f3793872..00000000
--- a/old/_version.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# file generated by setuptools_scm
-# don't change, don't track in version control
-TYPE_CHECKING = False
-if TYPE_CHECKING:
-    from typing import Tuple, Union
-    VERSION_TUPLE = Tuple[Union[int, str], ...]
-else:
-    VERSION_TUPLE = object
-
-version: str
-__version__: str
-__version_tuple__: VERSION_TUPLE
-version_tuple: VERSION_TUPLE
-
-__version__ = version = '0.0.2a1.dev23+g8a1a3e4.d20231215'
-__version_tuple__ = version_tuple = (0, 0, 2, 'dev23', 'g8a1a3e4.d20231215')
diff --git a/old/data_query.py b/old/data_query.py
deleted file mode 100644
index 3df1a196..00000000
--- a/old/data_query.py
+++ /dev/null
@@ -1,106 +0,0 @@
-""" query operations
-"""
-from ibridges.irodsconnector import keywords as kw
-from ibridges.irodsconnector import session
-
-
-class Query(object):
-    """Irods Query operations """
-
-    def __init__(self, sess_man: session.Session):
-        """ iRODS data operations initialization
-
-            Parameters
-            ----------
-            sess_man : session.Session
-                instance of the Session class
-
-        """
-        self.sess_man = sess_man
-
-    def search(self, key_vals: dict = None) -> list:
-        """Given a dictionary with metadata attribute names as keys and
-        associated values, query for collections and data objects that
-        fullfill the criteria.  The key 'checksum' will be mapped to
-        DataObject.checksum, the key 'path' will be mapped to
-        Collection.name and the key 'object' will be mapped to
-        DataObject.name:
-
-        {
-            'checksum': '',
-            'key1': 'val1',
-            'key2': 'val2',
-            'path': '',
-            'object': '',
-        }
-
-        By Default, if `key_vals` is empty, all accessible collections
-        and data objects will be returned.
-
-        Parameters
-        ----------
-        key_vals : dict
-            Attribute name mapping to values.
-
-        Returns
-        -------
-        list: [[Collection name, Object name, checksum]]
-
-        """
-        coll_query = None
-        data_query = None
-        # data query
-        if 'checksum' in key_vals or 'object' in key_vals:
-            data_query = self.sess_man.irods_session.query(
-                kw.COLL_NAME, kw.DATA_NAME, kw.DATA_CHECKSUM)
-            if 'object' in key_vals:
-                if key_vals['object']:
-                    data_query = data_query.filter(
-                        kw.LIKE(kw.DATA_NAME, key_vals['object']))
-            if 'checksum' in key_vals:
-                if key_vals['checksum']:
-                    data_query = data_query.filter(kw.LIKE(
-                        kw.DATA_CHECKSUM, key_vals['checksum']))
-        else:
-            coll_query = self.sess_man.irods_session.query(kw.COLL_NAME)
-            data_query = self.sess_man.irods_session.query(
-                kw.COLL_NAME, kw.DATA_NAME, kw.DATA_CHECKSUM)
-
-        if 'path' in key_vals and key_vals['path']:
-            if coll_query:
-                coll_query = coll_query.filter(kw.LIKE(
-                    kw.COLL_NAME, key_vals['path']))
-            data_query = data_query.filter(kw.LIKE(
-                kw.COLL_NAME, key_vals['path']))
-        for key in key_vals:
-            if key not in ['checksum', 'path', 'object']:
-                if data_query:
-                    data_query.filter(kw.META_DATA_ATTR_NAME == key)
-                if coll_query:
-                    coll_query.filter(kw.META_COLL_ATTR_NAME == key)
-                if key_vals[key]:
-                    if data_query:
-                        data_query.filter(
-                            kw.META_DATA_ATTR_VALUE == key_vals[key])
-                    if coll_query:
-                        coll_query.filter(
-                            kw.META_COLL_ATTR_VALUE == key_vals[key])
-        results = [['', '', ''], ['', '', ''], ['', '', '']]
-        coll_batch = [[]]
-        data_batch = [[]]
-        # Return only 100 results.
-        if coll_query:
-            num_colls = len(list(coll_query))
-            results[0] = [f'Collections found: {num_colls}', '', '']
-            coll_batch = list(coll_query.get_batches())
-        if data_query:
-            num_objs = len(list(data_query))
-            results[1] = [f'Objects found: {num_objs}', '', '']
-            data_batch = list(data_query.get_batches())
-        for res in coll_batch[0][:50]:
-            results.append([res[list(res.keys())[0]], '', ''])
-        for res in data_batch[0][:50]:
-            results.append([res[list(res.keys())[0]],
-                            res[list(res.keys())[1]],
-                            res[list(res.keys())[2]]])
-        return results
diff --git a/old/irodsconnector/data_operations.py b/old/irodsconnector/data_operations.py
deleted file mode 100644
index 93332a1e..00000000
--- a/old/irodsconnector/data_operations.py
+++ /dev/null
@@ -1,301 +0,0 @@
-""" collections and data objects
-"""
-import logging
-
-import irods.collection
-import irods.data_object
-import irods.exception
-
-from ibridges import utils
-from ibridges.irodsconnector import keywords as kw
-from ibridges.irodsconnector import resources, session
-
-
-class DataOperation(object):
-    """ Irods collections and data objects operations"""
-
-    def __init__(self, resources: resources.Resources, session: session.Session):
-        """ iRODS data operations initialization
-
-            Parameters
-            ----------
-            resources : resource.Resource
-                Instance of the Resource class
-            session : session.Session
-                instance of the Session class
-
-        """
-        self.resources = resources
-        self.session = session
-
-    @staticmethod
-    def is_dataobject_or_collection(obj: None):
-        """Check if `obj` is an iRODS data object or collection.
-
-        Parameters
-        ----------
-        obj : iRODS object instance
-            iRODS instance to check.
-
-        Returns
-        -------
-        bool
-            If `obj` is an iRODS data object or collection.
-
-        """
-        return isinstance(obj, (
-            irods.data_object.iRODSDataObject,
-            irods.collection.iRODSCollection))
-
-    def dataobject_exists(self, path: str) -> bool:
-        """Check if an iRODS data object exists.
-
-        Parameters
-        ----------
-        path : str
-            Name of an iRODS data object.
-
-        Returns
-        -------
-        bool
-            Existence of the data object with `path`.
-
-        """
-        return self.session.irods_session.data_objects.exists(path)
-
-    def collection_exists(self, path: str) -> bool:
-        """Check if an iRODS collection exists.
-
-        Parameters
-        ----------
-        path : str
-            Name of an iRODS collection.
-
-        Returns
-        -------
-        bool
-            Existance of the collection with `path`.
-
-        """
-        return self.session.irods_session.collections.exists(path)
-
-    @staticmethod
-    def is_dataobject(obj) -> bool:
-        """Check if `obj` is an iRODS data object.
-
-        Parameters
-        ----------
-        obj : iRODS object instance
-            iRODS instance to check.
-
-        Returns
-        -------
-        bool
-            If `obj` is an iRODS data object.
-
-        """
-        return isinstance(obj, irods.data_object.iRODSDataObject)
-
-    @staticmethod
-    def is_collection(obj) -> bool:
-        """Check if `obj` is an iRODS collection.
-
-        Parameters
-        ----------
-        obj : iRODS object instance
-            iRODS instance to check.
-
-        Returns
-        -------
-        bool
-            If `obj` is an iRODS collection.
-
-        """
-        return isinstance(obj, irods.collection.iRODSCollection)
-
-
-    def ensure_data_object(self, data_object_name: str) -> irods.data_object.iRODSDataObject:
-        """Optimally create a data object with `data_object_name` if one does
-        not exist.
-
-        Parameters
-        ----------
-        data_object_name : str
-            Name of the data object to check/create.
-
-        Returns
-        -------
-        iRODS Data object
-            Existing or new iRODS data object.
-
-        Raises:
-            irods.exception.CAT_NO_ACCESS_PERMISSION
-
-        """
-        try:
-            if self.session.irods_session.data_objects.exists(data_object_name):
-                return self.session.irods_session.data_objects.get(data_object_name)
-            return self.session.irods_session.data_objects.create(data_object_name)
-        except irods.exception.CAT_NO_ACCESS_PERMISSION as error:
-            logging.info('ENSURE DATA OBJECT', exc_info=True)
-            raise error
-
-    def ensure_coll(self, coll_name: str) -> irods.collection.iRODSCollection:
-        """Optimally create a collection with `coll_name` if one does
-        not exist.
-
-        Parameters
-        ----------
-        coll_name : str
-            Name of the collection to check/create.
-
-        Returns
-        -------
-        iRODSCollection
-            Existing or new iRODS collection.
-
-        Raises:
-            irods.exception.CAT_NO_ACCESS_PERMISSION
-
-        """
-        try:
-            if self.session.irods_session.collections.exists(coll_name):
-                return self.session.irods_session.collections.get(coll_name)
-            return self.session.irods_session.collections.create(coll_name)
-        except irods.exception.CAT_NO_ACCESS_PERMISSION as error:
-            logging.info('ENSURE COLLECTION', exc_info=True)
-            raise error
-
-    def get_dataobject(self, path: str) -> irods.data_object.iRODSDataObject:
-        """Instantiate an iRODS data object.
-
-        Parameters
-        ----------
-        path : str
-            Name of an iRODS data object.
-
-        Returns
-        -------
-        iRODSDataObject
-            Instance of the data object with `path`.
-
-        """
-        if self.dataobject_exists(path):
-            return self.session.irods_session.data_objects.get(path)
-        raise irods.exception.DataObjectDoesNotExist(path)
-
-    def get_collection(self, path: str) -> irods.collection.iRODSCollection:
-        """Instantiate an iRODS collection.
-
-        Parameters
-        ----------
-        path : str
-            Name of an iRODS collection.
-
-        Returns
-        -------
-        iRODSCollection
-            Instance of the collection with `path`.
-
-        """
-        if self.collection_exists(path):
-            return self.session.irods_session.collections.get(path)
-        raise irods.exception.CollectionDoesNotExist(path)
-
-    def irods_put(self, local_path: str, irods_path: str, resc_name: str = ''):
-        """Upload `local_path` to `irods_path` following iRODS `options`.
-
-        Parameters
-        ----------
-        local_path : str
-            Path of local file or directory/folder.
-        irods_path : str
-            Path of iRODS data object or collection.
-        resc_name : str
-            Optional resource name.
-
-        """
-        options = {
-            kw.ALL_KW: '',
-            kw.NUM_THREADS_KW: kw.NUM_THREADS,
-            kw.REG_CHKSUM_KW: '',
-            kw.VERIFY_CHKSUM_KW: ''
-        }
-        if resc_name not in ['', None]:
-            options[kw.RESC_NAME_KW] = resc_name
-        self.session.irods_session.data_objects.put(local_path, irods_path, **options)
-
-    def irods_get(self, irods_path: str, local_path: str, options: dict = None):
-        """Download `irods_path` to `local_path` following iRODS `options`.
-
-        Parameters
-        ----------
-        irods_path : str
-            Path of iRODS data object or collection.
-        local_path : str
-            Path of local file or directory/folder.
-        options : dict
-            iRODS transfer options.
-
-        """
-        if options is None:
-            options = {}
-        options.update({
-            kw.NUM_THREADS_KW: kw.NUM_THREADS,
-            kw.VERIFY_CHKSUM_KW: '',
-            })
-        self.session.irods_session.data_objects.get(irods_path, local_path, **options)
-
-
-    def delete_data(self, item: (irods.collection.iRODSCollection, irods.data_object.iRODSDataObject)):
-        """
-        Delete a data object or a collection recursively.
-        Parameters
-        ----------
-        item: iRODS data object or collection
-            item to delete
-        """
-
-        if self.session.irods_session.collections.exists(item.path):
-            logging.info('IRODS DELETE: %s', item.path)
-            try:
-                item.remove(recurse=True, force=True)
-            except irods.exception.CAT_NO_ACCESS_PERMISSION as error:
-                logging.error('IRODS DELETE: no permissions %s', item.path)
-                raise error
-        elif self.session.irods_session.data_objects.exists(item.path):
-            logging.info('IRODS DELETE: %s', item.path)
-            try:
-                item.unlink(force=True)
-            except irods.exception.CAT_NO_ACCESS_PERMISSION as error:
-                logging.error('IRODS DELETE: no permissions %s', item.path)
-                raise error
-
-    def get_irods_size(self, path_names: list) -> int:
-        """Collect the sizes of a set of iRODS data objects and/or
-        collections and determine the total size.
-
-        Parameters
-        ----------
-        path_names : list
-            Names of logical iRODS paths.
-
-        Returns
-        -------
-        int
-            Total size [bytes] of all iRODS objects found from the
-            logical paths in `path_names`.
-
-        """
-        irods_sizes = []
-        for path_name in path_names:
-            irods_name = utils.path.IrodsPath(path_name)
-            if self.collection_exists(irods_name):
-                irods_sizes.append(
-                    utils.utils.get_coll_size(
-                        self.get_collection(irods_name)))
-            elif self.dataobject_exists(irods_name):
-                irods_sizes.append(
-                    utils.utils.get_data_size(
-                        self.get_dataobject(irods_name)))
-        return sum(irods_sizes)
diff --git a/old/irodsconnector/keywords.py b/old/irodsconnector/keywords.py
deleted file mode 100644
index 9a3b1f70..00000000
--- a/old/irodsconnector/keywords.py
+++ /dev/null
@@ -1,52 +0,0 @@
-"""Keywords and definitions"""
-import irods.column as cm
-import irods.keywords as kw
-import irods.models as imodels
-
-#from irods.exception import *
-
-# Keywords
-ALL_KW = kw.ALL_KW
-FORCE_FLAG_KW = kw.FORCE_FLAG_KW
-NUM_THREADS_KW = kw.NUM_THREADS_KW  # 'num_threads'
-DEST_RESC_NAME_KW = kw.DEST_RESC_NAME_KW
-RESC_NAME_KW = kw.RESC_NAME_KW
-TICKET_KW = kw.TICKET_KW
-VERIFY_CHKSUM_KW = kw.VERIFY_CHKSUM_KW
-REG_CHKSUM_KW = kw.REG_CHKSUM_KW
-# Map model names to iquest attribute names
-COLL_NAME = imodels.Collection.name
-COLL_ID = imodels.Collection.id
-DATA_NAME = imodels.DataObject.name
-DATA_ID = imodels.DataObject.id
-DATA_CHECKSUM = imodels.DataObject.checksum
-META_COLL_ATTR_NAME = imodels.CollectionMeta.name
-META_COLL_ATTR_VALUE = imodels.CollectionMeta.value
-META_DATA_ATTR_NAME = imodels.DataObjectMeta.name
-META_DATA_ATTR_VALUE = imodels.DataObjectMeta.value
-RESC_NAME = imodels.Resource.name
-RESC_PARENT = imodels.Resource.parent
-RESC_STATUS = imodels.Resource.status
-RESC_CONTEXT = imodels.Resource.context
-USER_GROUP_NAME = imodels.UserGroup.name
-USER_NAME = imodels.User.name
-USER_TYPE = imodels.User.type
-# Query operators
-LIKE = cm.Like
-# ASCII colors
-BLUE = '\x1b[1;34m'
-DEFAULT = '\x1b[0m'
-RED = '\x1b[1;31m'
-YEL = '\x1b[1;33m'
-# Misc
-BUFF_SIZE = 10**9
-MULTIPLIER = 1 / 10**9
-NUM_THREADS = 4
-# Excpetion mapping
-exceptions = {
-    "PAM_AUTH_PASSWORD_FAILED(None,)": "Wrong password",
-    "CAT_PASSWORD_EXPIRED(None,)": "Cached password expired",
-    "CAT_INVALID_AUTHENTICATION(None,)": "Cached password is wrong",
-    "NetworkException('Client-Server negotiation failure: CS_NEG_REFUSE,CS_NEG_REQUIRE')":
-    '"irods_client_server_policy" not set (correctly) in irods_environment.json'
-}
diff --git a/old/irodsconnector/manager.py b/old/irodsconnector/manager.py
deleted file mode 100644
index cf4e041c..00000000
--- a/old/irodsconnector/manager.py
+++ /dev/null
@@ -1,225 +0,0 @@
-""" Irods connection factory
-
-"""
-import logging
-
-import irods.collection
-import irods.data_object
-import irods.resource
-import irods.session
-
-from ibridges.connector import (
-    permissions,  # obsolete/renamed?
-    session,
-)
-
-
-class IrodsConnector():
-    """Top-level connection to the Python iRODS client
-
-    """
-
-    def __init__(self, irods_env: dict, password: str = ''):
-        """Initialize connection to iRODS functionality based on the
-        user's credentials.
-
-        Parameters
-        ----------
-        irods_env : dict
-            Dictionary dereived from an irods_environment.json
-        password : str
-            Plain text password.
-
-        """
-        self.__name__ = 'IrodsConnector'
-        self._irods_env = irods_env
-        self._password = password
-        self.session = connect()
-
-    def __del__(self):
-        del self.session
-
-    
-    @property
-    def irods_environment(self) -> dict:
-        """iRODS environment.
-
-        Returns
-        -------
-        dict or None
-            iRODS environment.
-
-        """
-        return self._irods_env
-
-    @property 
-    def password(self) -> str:
-        return self._password
-
-    @property
-    def user_info(self) -> tuple:
-        """Returns user type and groups the user who started the sessionbelongs to.
-
-        Returns
-        -------
-        (user_type, user_groups) -> (str, list)
-        """
-        query = self.session.query(kw.USER_TYPE).filter(
-                kw.LIKE(kw.USER_NAME, self.sess_man.username))
-        user_type = [list(result.values())[0] for result in query.get_results()][0]
-        query = self.session.query(kw.USER_GROUP_NAME).filter(
-                kw.LIKE(kw.USER_NAME, self.sess_man.username))
-        user_groups = [list(result.values())[0] for result in query.get_results()]
-        return user_type, user_groups
-
-
-    # Data operation/iCommands functionality
-    #
-    def collection_exists(self, path: str) -> bool:
-        return "Not implemented"
-
-    def dataobject_exists(self, path: str) -> bool:
-        return "Not implemented"
-
-    def delete_data(self, item: None):
-        return "Not implemented"
-
-    def ensure_coll(self, coll_name: str) -> irods.collection.iRODSCollection:
-        return "Not implemented"
-
-    def ensure_data_object(self, data_object_name: str) -> irods.data_object.iRODSDataObject:
-        return "Not implemented"
-
-    def get_collection(self, path: str) -> irods.collection.iRODSCollection:
-        return "Not implemented"
-
-    def get_dataobject(self, path: str) -> irods.data_object.iRODSDataObject:
-        return "Not implemented"
-
-    def get_items_size(self, irods_path_names: list) -> int:
-        """Retrieves the sizes of collections and objects and returns their cumulative size in bytes.
-        """
-        return "Not implemented"
-
-    def irods_put(self, local_path: str, irods_path: str, res_name: str = '', options: dict = None):
-        """Uploads a data object or collection to iRODS.
-        """
-        return "Not implemented"
-
-    def irods_get(self, irods_path: str, local_path: str, options: dict = None):
-        """Downloads a data object or collection from iRODS.
-        """
-        return "Not implemented"
-
-    def is_collection(self, obj) -> bool:
-        return "Not implemented"
-
-    def is_dataobject(self, obj) -> bool:
-        return "Not implemented"
-
-    # Metadata functionality
-    #
-    def add_metadata(self, items: list, key: str, value: str, units: str = None):
-        """Adds an iRODS key-value-units tag to all objects and collections in the items list.
-        """
-        return "Not implemented"
-
-    def add_multiple_metadata(self, items, avus):
-        """Adds multiple metadata fields to all items.
-        """
-        return "Not implemented"
-
-    def delete_metadata(self, items: list, key: str, value: str, units: str = None):
-        return "Not implemented"
-
-    def update_metadata(self, items: list, key: str, value: str, units: str = None):
-        return "Not implemented"
-
-    # Permission functionality
-    #
-    @property
-    def permissions(self) -> dict:
-        """Returns available iRODS permissions for data objects.
-        """
-        return "Not implemented"
-
-    def get_permissions(self, item: irods.collection | irods.data_object) -> list:
-        return "Not implemented"
-
-    def set_permissions(self, perm: str, itam: irods.collection | irods.data_object, 
-                        user: str = '', zone: str = self.session.zone,
-                        recursive: bool = False, admin: bool = False):
-        return "Not implemented"
-
-    # Resource functionality
-    #
-    @property
-    def resources(self) -> dict:
-        """Returns all iRODS resources and their metadata as dictionary.
-        """
-        return "Not implemented"
-
-    def get_free_space(self, resc_name: str, multiplier: int = 1) -> int:
-        """Returns the free_space of a resource, space is given in bytes.
-        """
-        return "Not implemented"
-
-    def get_resource(self, resc_name: str) -> irods.resource.iRODSResource:
-        return "Not implemented"
-
-    def get_resource_children(self, resc: irods.resource.iRODSResource) -> list:
-        return "Not implemented"
-
-    def resource_space(self, root_name: str) -> int:
-        """Accumulate all free space under a resource tree starting at a resource root_name
-        """
-        return "Not implemented"
-
-    # Rules functionality
-    #
-    def execute_rule(self, rule_file: str, params: dict, output: str = 'ruleExecOut') -> tuple:
-        return "Not implemented"
-
-
-    # Tickets functionality
-    #
-    def create_ticket(self, item_path: str, expiry_string: str = '') -> tuple:
-        return "Not implemented"
-
-
-    # Session functionality
-    #
-    def connect(self):
-        """Establish an iRODS session.
-
-        """
-        self._session = session.Session(self.irods_env_file, self.irods_environment.config,
-                                        self.ibridges_configuration.config, self._password) 
-        if not self.session.has_irods_session():
-            self.session.connect()
-
-    # iRODS session properties
-    #
-    @property
-    def default_resc(self) -> str:
-        return self.session.default_resc
-
-    @property
-    def host(self) -> str:
-        return self.session.host
-
-    @property
-    def port(self) -> str:
-        return self.session.port
-
-    @property
-    def server_version(self) -> tuple:
-        return self.session.server_version
-
-    @property
-    def username(self) -> str:
-        return self.session.username
-
-    @property
-    def zone(self) -> str:
-        return self.session.zone
diff --git a/old/irodsconnector/meta.py b/old/irodsconnector/meta.py
deleted file mode 100644
index f118ad6c..00000000
--- a/old/irodsconnector/meta.py
+++ /dev/null
@@ -1,104 +0,0 @@
-""" metadata operations
-"""
-from typing import Iterator, Optional
-
-import irods.exception
-import irods.meta
-
-
-class MetaData():
-    """Irods metadata operations """
-
-    def __init__(self, item):
-        self.item = item
-
-    def __iter__(self) -> Iterator:
-        for m in self.item.metadata.items():
-            yield m
-
-    def __repr__(self) -> str:
-        """Create a sorted representation of the metadata"""
-
-        # Sort the list of items name -> value -> units, where None is the lowest
-        meta_list = list(self)
-        meta_list = sorted(meta_list, key=lambda m: (m.units is None, m.units))
-        meta_list = sorted(meta_list, key=lambda m: (m.value is None, m.value))
-        meta_list = sorted(meta_list, key=lambda m: (m.name is None, m.name))
-        meta_str = ""
-        for m in meta_list:
-            meta_str += f" - {{name: {m.name}, value: {m.value}, units: {m.units}}}\n"
-        return meta_str
-
-    def __str__(self) -> str:
-        return self.__repr__()
-
-    def add(self, key: str, value: str, units: Optional[str] = None):
-        """
-        Adds metadata to all items
-
-        Parameters
-        ----------
-        items: list of iRODS data objects or iRODS collections
-        key: string
-        value: string
-        units: (optional) string
-
-        Throws:
-            CATALOG_ALREADY_HAS_ITEM_BY_THAT_NAME
-        """
-        try:
-            self.item.metadata.add(key.upper(), value, units)
-        except irods.exception.CATALOG_ALREADY_HAS_ITEM_BY_THAT_NAME as error:
-            raise ValueError("ADD META: Metadata already present") from error
-        except irods.exception.CAT_NO_ACCESS_PERMISSION as error:
-            raise ValueError("UPDATE META: no permissions") from error
-
-    def set(self, key: str, value: str, units: Optional[str] = None):
-        """Set the metadata entry.
-
-        If the metadata entry already exists, then all metadata entries with
-        the same key will be deleted before adding the new entry. An alternative
-        is using the add method to only add to the metadata entries and not
-        delete them.
-
-        Parameters
-        ----------
-        items: list of iRODS data objects or iRODS collections
-        key: string
-        value: string
-        units: (optional) string
-
-        Throws: CAT_NO_ACCESS_PERMISSION
-        """
-        self.delete(key, None)
-        self.add(key, value, units)
-
-    def delete(self, key: str, value: Optional[str], units: Optional[str] = None):
-        """
-        Deletes a metadata entry of all items
-
-        Parameters
-        ----------
-        items: list of iRODS data objects or iRODS collections
-        key: string
-        value: string
-        units: (optional) string
-
-        Throws:
-            CAT_SUCCESS_BUT_WITH_NO_INFO: metadata did not exist
-        """
-        try:
-            if value is None:
-                metas = self.item.metadata.get_all(key)
-                value_units = [(m.value, m.units) for m in metas]
-                if (value, units) not in value_units:
-                    for meta in metas:
-                        self.item.metadata.remove(meta)
-            else:
-                self.item.metadata.remove(key, value, units)
-        except irods.exception.CAT_SUCCESS_BUT_WITH_NO_INFO as error:
-            raise ValueError("Cannot delete metadata with key '{key}', value '{value}'"
-                             " and units '{units}' since it does not exist.") from error
-        except irods.exception.CAT_NO_ACCESS_PERMISSION as error:
-            raise ValueError("Cannot delete metadata due to insufficient permission for "
-                             "path '{item.path}'.") from error
diff --git a/old/irodsconnector/permission.py b/old/irodsconnector/permission.py
deleted file mode 100644
index 24dec02f..00000000
--- a/old/irodsconnector/permission.py
+++ /dev/null
@@ -1,102 +0,0 @@
-""" permission operations
-"""
-import logging  # noqa: I001
-from typing import Optional
-
-import irods.access
-import irods.collection
-import irods.exception
-
-from ibridges.irodsconnector.data_operations import DataOperation
-from ibridges.irodsconnector import session
-
-
-class Permission(object):
-    """Irods permission operations """
-    _permissions: Optional[dict[str, str]] = None
-
-    def __init__(self, data_man: DataOperation, session: session.Session):
-        """ iRODS data operations initialization
-
-            Parameters
-            ----------
-            data_man: dataOperations.DataOperation
-                instance of the Dataoperation class
-            sess_man : session.Session
-                instance of the Session class
-
-        """
-        self.session = session
-
-    @property
-    def permissions(self) -> dict:
-        """iRODS permissions mapping.
-
-        Returns
-        -------
-        dict
-            Correct permissions mapping for the current server version.
-
-        """
-        if self._permissions is None:
-            self._permissions = {
-                'null': 'none',
-                'read_object': 'read',
-                'modify_object': 'write',
-                'own': 'own',
-            }
-            if self.session.server_version < (4, 3, 0):
-                self._permissions.update(
-                    {'read object': 'read', 'modify object': 'write'})
-        return self._permissions
-
-    def get_permissions(self, item: irods.collection | irods.data_object) -> list:
-        """Discover ACLs for an iRODS collection expressed as a `path`
-        or an `obj`ect.
-
-        Parameters
-        ----------
-        item: irods.collection | irods.data_object
-
-        Returns
-        -------
-        list
-            iRODS ACL instances.
-
-        """
-        if DataOperation.is_dataobject_or_collection(item):
-            return self.session.irods_session.permissions.get(item)
-        logging.debug('Not a valid iRODS object or collection')
-        return []
-
-    def set_permissions(self, perm: str, item: irods.collection | irods.data_object,
-                        user: str = '', zone: str = '', recursive: bool = False,
-                        admin: bool = False):
-        """Set permissions (ACL) for an iRODS collection or data object.
-
-        Parameters
-        ----------
-        perm: str
-            Name of permission string: own, read, write, or null.
-        item: irods.data_object or irods.collection
-        user: str
-            Name of user.
-        zone: str
-            Name of user's zone.
-        recursive: bool
-            Apply ACL to all children of `path`.
-        admin: bool
-            If a 'rodsadmin' apply ACL for another user.
-
-        """
-        acl = irods.access.iRODSAccess(perm, item.path, user, zone)
-        try:
-            self.session.irods_session.permissions.set(acl, recursive=recursive, admin=admin)
-        except irods.exception.CAT_INVALID_USER as error:
-            logging.error('ACL: user unknown')
-            raise error
-        except irods.exception.CAT_INVALID_ARGUMENT as error:
-            logging.error(
-                'ACL: permission %s or path %s not known', perm, item.path,
-                exc_info=True)
-            raise error
diff --git a/old/irodsconnector/resources.py b/old/irodsconnector/resources.py
deleted file mode 100644
index ffde0e8d..00000000
--- a/old/irodsconnector/resources.py
+++ /dev/null
@@ -1,155 +0,0 @@
-""" resource operations
-"""
-from typing import Optional
-
-import irods.exception
-import irods.resource
-
-from ibridges.irodsconnector import keywords as kw
-from ibridges.irodsconnector import session
-
-
-class Resources(object):
-    """Irods Resource operations """
-
-    def __init__(self, session: session.Session):
-        """ iRODS resource initialization
-
-            Parameters
-            ----------
-            session : session.Session
-                instance of the Session class
-        """
-        self._resources: Optional[dict] = None
-        self.session = session
-
-    def get_resource(self, resc_name: str) -> irods.resource.iRODSResource:
-        """Instantiate an iRODS resource.
-
-        Prameters
-        ---------
-        resc_name : str
-            Name of the iRODS resource.
-
-        Returns
-        -------
-        iRODSResource
-            Instance of the resource with `resc_name`.
-
-        Raises:
-            irods.exception.ResourceDoesNotExist
-
-        """
-        try:
-            return self.session.irods_session.resources.get(resc_name)
-        except irods.exception.ResourceDoesNotExist as error:
-            return {'successful': False, 'reason': repr(error)}
-
-    def get_free_space(self, resc_name: str) -> int:
-        """Determine free space in a resource hierarchy.
-
-        Storeage resources: return annotation "free_space".
-        Coordinating resources: return sum of all free space of the resource subtree.
-        If "free_space" is not set, set value to 0.
-
-        Parameters
-        ----------
-        resc_name : str
-            Name of monolithic resource or the top of a resource tree.
-
-        Returns
-        -------
-        int
-            Number of bytes free in the resource hierarchy.
-
-        The return value can have one of two possible values if not the actual
-        free space:
-
-            -1 if the resource does not exists (typo or otherwise)
-             0 if no free space has been set in the whole resource tree
-                starting at node resc_name.
-
-        """
-        try:
-            resc = self.session.irods_session.resources.get(resc_name)
-        except irods.exception.ResourceDoesNotExist:
-            return -1
-        if resc.free_space is not None:
-            return int(resc.free_space)
-        children = self.get_resource_children(resc)
-        free_space = sum((
-            int(child.free_space) for child in children
-            if child.free_space is not None))
-        return free_space
-
-    def get_resource_children(self, resc: irods.resource.iRODSResource) -> list:
-        """Get all the children for the resource `resc`.
-
-        Parameters
-        ----------
-        resc : instance
-            iRODS resource instance.
-
-        Returns
-        -------
-        list
-            Instances of child resources.
-
-        """
-        children = []
-        for child in resc.children:
-            children.extend(self.get_resource_children(child))
-        return resc.children + children
-
-    def resources(self, update: bool = False) -> dict:
-        """iRODS resources and their metadata.
-
-        Parameters
-        ----------
-        update
-            Fetch information from iRODS server and overwrite _resources
-
-        Returns
-        -------
-        dict
-            Name, parent, status, context, and free_space of all
-            resources.
-
-        NOTE: free_space of a resource is the free_space annotated, if
-              so annotated, otherwise it is the sum of the free_space of
-              all its children.
-
-        """
-        if self._resources is None or update:
-            query = self.session.irods_session.query(
-                kw.RESC_NAME, kw.RESC_PARENT, kw.RESC_STATUS, kw.RESC_CONTEXT)
-            resc_list = []
-            for item in query.get_results():
-                name, parent, status, context = item.values()
-                free_space = 0
-                if parent is None:
-                    free_space = self.get_free_space(name)
-                metadata = {
-                    'parent': parent,
-                    'status': status,
-                    'context': context,
-                    'free_space': free_space,
-                }
-                resc_list.append((name, metadata))
-            resc_dict = dict(
-                sorted(resc_list, key=lambda item: str.casefold(item[0].decode())))
-            self._resources = resc_dict
-        return self._resources
-
-    @property
-    def root_resources(self) -> list:
-        """
-        Filter resources for all root resources (data can only be written to root resources).
-        Return their names, their status and their free space.
-
-        Returns:
-        List [(resource_name, status, free_space, context)]
-        """
-        parents = [(key, val) for key, val in self.resources().items() if not val['parent']]
-        return [(resc[0], resc[1]["status"], resc[1]["free_space"], resc[1]["context"]) 
-                for resc in parents]
diff --git a/old/irodsconnector/rules.py b/old/irodsconnector/rules.py
deleted file mode 100644
index 091ae63d..00000000
--- a/old/irodsconnector/rules.py
+++ /dev/null
@@ -1,71 +0,0 @@
-""" rule operations
-"""
-import logging
-
-import irods.exception
-import irods.rule
-
-from ibridges.irodsconnector import session
-
-
-class Rules(object):
-    """Irods Rule operations """
-
-    def __init__(self, session: session.Session):
-        """ iRODS data operations initialization
-
-            Parameters
-            ----------
-            sess_man : session.Session
-                instance of the Session class
-
-        """
-        self.session = session
-
-    def execute_rule(self, rule_file: str, params: dict, output: str = 'ruleExecOut') -> tuple:
-        """Execute an iRODS rule.
-
-        Parameters
-        ----------
-        rule_file : str, file-like
-            Name of the iRODS rule file, or a file-like object representing it.
-        params : dict
-            Rule arguments.
-        output : str
-            Rule output variable(s).
-
-        Returns
-        -------
-        tuple
-            (stdout, stderr)
-
-        `params` format example:
-        params = {  # extra quotes for string literals
-            '*obj': '"/zone/home/user"',
-            '*name': '"attr_name"',
-            '*value': '"attr_value"'
-        }
-
-        """
-        try:
-            rule = irods.rule.Rule(
-                self.session.irods_session, rule_file=rule_file, params=params, output=output,
-                instance_name='irods_rule_engine_plugin-irods_rule_language-instance')
-            out = rule.execute()
-        except irods.exception.NetworkException as error:
-            logging.info('Lost connection to iRODS server.')
-            return '', repr(error)
-        except irods.exception.SYS_HEADER_READ_LEN_ERR as error:
-            logging.info('iRODS server hiccuped.  Check the results and try again.')
-            return '', repr(error)
-        except Exception as error:
-            logging.info('RULE EXECUTION ERROR', exc_info=True)
-            return '', repr(error)
-        stdout, stderr = '', ''
-        if len(out.MsParam_PI) > 0:
-            buffers = out.MsParam_PI[0].inOutStruct
-            stdout = (buffers.stdoutBuf.buf or b'').decode()
-            # Remove garbage after terminal newline.
-            stdout = '\n'.join(stdout.split('\n')[:-1])
-            stderr = (buffers.stderrBuf.buf or b'').decode()
-        return stdout, stderr
diff --git a/old/irodsconnector/session.py b/old/irodsconnector/session.py
deleted file mode 100644
index 53bc1c88..00000000
--- a/old/irodsconnector/session.py
+++ /dev/null
@@ -1,232 +0,0 @@
-""" session operations
-"""
-import json
-import os
-import warnings
-from typing import Optional
-
-import irods.session
-from irods.exception import NetworkException
-
-from ibridges.irodsconnector.keywords import exceptions
-
-
-class Session:
-    """Irods session authentication.
-
-    """
-
-    def __init__(self, irods_env: Optional[dict] = None, irods_env_path: Optional[str] = None,
-                 password: Optional[str] = None):
-        """ iRODS authentication with Python client.
-
-        Parameters
-        ----------
-        irods_conf: dict
-            Dictionary from irods_environment.json
-        password : str
-            Plain text password.
-
-        """
-        if irods_env is None and irods_env_path is None:
-            raise Exception("CONNECTION ERROR: no irods environment given.")
-        if irods_env and irods_env_path:
-            warnings.warn("Environment dictionary will be overwritten with irods environment file")
-        if irods_env_path:
-            with open(os.path.expanduser("~/.irods/irods_environment.json"), "r") as f:
-                irods_env = json.load(f)
-
-        self._password = password
-        self._irods_env = irods_env
-        self._irods_env_path = irods_env_path
-        self._irods_session = self.connect()
-
-    def __del__(self):
-        del self.irods_session
-
-    @property
-    def irods_session(self) -> irods.session.iRODSSession:
-        """iRODS session creation.
-
-        Returns
-        -------
-        iRODSSession
-            iRODS connection based on the current environment and password.
-
-        """
-        return self._irods_session
-
-    @irods_session.deleter
-    def irods_session(self):
-        """Properly delete iRODS session.
-        """
-        if self._irods_session is not None:
-            # In case the iRODS session is not fully there.
-            try:
-                self._irods_session.cleanup()
-            except NameError:
-                pass
-            except AttributeError:
-                pass
-            del self._irods_session
-            self._irods_session = None
-
-    # Authentication workflow methods
-    #
-
-    def has_valid_irods_session(self) -> bool:
-        """Check if the iRODS session is valid.
-
-        Returns
-        -------
-        bool
-            Is the session valid?
-
-        """
-        return self.server_version != ()
-
-    def connect(self):
-        """Establish an iRODS session.
-
-        """
-        user = self._irods_env.get('irods_user_name', '')
-        if user == 'anonymous':
-            # TODO: implement and test for SSL enabled iRODS
-            # self._irods_session = iRODSSession(user='anonymous',
-            #                        password='',
-            #                        zone=zone,
-            #                        port=1247,
-            #                        host=host)
-            raise NotImplementedError
-        else:  # authentication with irods environment and password
-            if self._password == '':
-                print("Auth without password")
-                # use cached password of .irodsA built into prc
-                return self.authenticate_using_auth_file()
-            else:
-                print("Auth with password")
-                # irods environment and given password
-                return self.authenticate_using_password()
-
-    def authenticate_using_password(self):
-        try:
-            self._irods_session = irods.session.iRODSSession(password=self._password,
-                                                             **self._irods_env)
-            assert self._irods_session.server_version != ()
-            return self._irods_session
-        except ValueError as e:
-            raise Exception("Unexpected value in irods_environment.json; "+repr(e))
-        except NetworkException as e:
-            raise Exception("Host, port, irods_client_server_policy or irods_client_server_negotiation not set correctly in irods_environment.json; "+repr(e))
-        except Exception as e:
-            if repr(e) in exceptions:
-                raise Exception(exceptions[repr(e)]+"; "+repr(e))
-            else:
-                raise e
-
-    def authenticate_using_auth_file(self):
-        try:
-            self._irods_session = irods.session.iRODSSession(
-                    irods_env_file=self._irods_env_path)
-            assert self._irods_session.server_version != ()
-            return self._irods_session
-        except ValueError as e:
-            raise Exception("Unexpected value in irods_environment.json; "+repr(e))
-        except NetworkException as e:
-            raise Exception("Host, port or irods_client_server_negotiation not set correctly in irods_environment.json; "+repr(e))
-        except Exception as e:
-            if repr(e) in exceptions:
-                raise Exception(exceptions[repr(e)]+"; "+repr(e))
-            else:
-                raise e
-
-    @property
-    def default_resc(self) -> str:
-        """Default resource name from iRODS environment.
-
-        Returns
-        -------
-        str
-            Resource name.
-
-        """
-        if self._irods_session:
-            try:
-                return self._irods_session.default_resource
-            except AttributeError:
-                pass
-        raise ValueError("'irods_default_resource' not set in iRODS configuration.")
-
-    @property
-    def host(self) -> str:
-        """Retrieve hostname of the iRODS server.
-
-        Returns
-        -------
-        str
-            Hostname.
-
-        """
-        if self._irods_session:
-            return self._irods_session.host
-        return ''
-
-    @property
-    def port(self) -> str:
-        """Retrieve port of the iRODS server.
-
-        Returns
-        -------
-        str
-            Port.
-
-        """
-        if self._irods_session:
-            return self._irods_session.port
-        return ''
-
-    @property
-    def server_version(self) -> tuple:
-        """Retrieve version of the iRODS server
-
-        Returns
-        -------
-        tuple
-            Server version: (major, minor, patch).
-
-        """
-        try:
-            return self._irods_session.server_version
-        except Exception as e:
-            if repr(e) in exceptions:
-                raise Exception(exceptions[repr(e)])
-            else:
-                raise e
-
-    @property
-    def username(self) -> str:
-        """Retrieve username.
-
-        Returns
-        -------
-        str
-            Username.
-
-        """
-        if self._irods_session:
-            return self._irods_session.username
-        return ''
-
-    @property
-    def zone(self) -> str:
-        """Retrieve the zone name.
-
-        Returns
-        -------
-        str
-            Zone.
-
-        """
-        if self._irods_session:
-            return self._irods_session.zone
-        return ''
diff --git a/old/irodsconnector/tickets.py b/old/irodsconnector/tickets.py
deleted file mode 100644
index 141e8d96..00000000
--- a/old/irodsconnector/tickets.py
+++ /dev/null
@@ -1,131 +0,0 @@
-""" ticket operations
-"""
-from typing import Optional
-
-import irods.ticket
-from irods.models import TicketQuery
-
-import ibridges.irodsconnector.keywords as kw
-from ibridges.irodsconnector.session import Session
-
-
-class Tickets(object):
-    """Irods Ticket operations """
-
-    def __init__(self, session: Session):
-        """ iRODS data operations initialization
-
-            Parameters
-            ----------
-            session : session.Session
-                instance of the Session class
-
-        """
-        self.session = session
-        self._all_tickets = self.all_tickets(update = True)
-
-    def create_ticket(self, obj_path: str,
-                      ticket_type: Optional[str] = 'read',
-                      expiry_string: Optional[str] = None) -> tuple:
-        """Create an iRODS ticket to allow read access to the object
-        referenced by `obj_path`.
-
-        Parameters
-        ----------
-        obj_path : str
-            Collection or data object path to create a ticket for.
-        ticket_type: str
-            read or write, default read
-        expiry_string : str
-            Optional expiration date in the form: strftime('%Y-%m-%d.%H:%M:%S')
-
-        Returns
-        -------
-        tuple
-            Name of ticket and if expiration string successfully set:
-            (str, bool)
-
-        """
-        ticket = irods.ticket.Ticket(self.session.irods_session)
-        ticket.issue(ticket_type, obj_path)
-        expiration_set = False
-        if expiry_string is not None:
-            try:
-                expiration_set = ticket.modify('expire', expiry_string) == ticket
-            except Exception as error:
-                self.delete_ticket(ticket)
-                raise Exception('Could not set expiration date: %r', error)
-        return ticket.ticket, expiration_set
-
-    @property
-    def all_ticket_strings(self) -> list:
-        return [name for name, _, _, _ in self.all_tickets()]
-
-    def get_ticket(self, ticket_str: str) -> Optional[irods.ticket.Ticket]:
-        if ticket_str in self.all_ticket_strings:
-            return irods.ticket.Ticket(self.session.irods_session, ticket=ticket_str)
-        raise KeyError(f"Cannot obtain ticket: ticket with ticket_str '{ticket_str}' does not exist.")
-
-    def delete_ticket(self, ticket: irods.ticket.Ticket, check: bool = False):
-        if ticket.string in self.all_ticket_strings:
-            ticket.delete()
-            self.all_tickets(update=True)
-        if check:
-            raise KeyError(f"Cannot delete ticket: ticket '{ticket}' does not exist (anymore).")
-
-    def all_tickets(self, update: bool = False) -> list[tuple[str, str, str, str]]:
-        """retrieves all tickets and their metadata belonging to the user.
-
-        Parameters
-        ----------
-        update : bool
-            Refresh information from server.
-
-        Returns
-        -------
-        list
-            [(ticket string, ticket type, irods obj/coll path, expiry data in epoche)]
-        """
-        user = self.session.username
-        if update or self._all_tickets is None:
-            self._all_tickets = []
-            for row in self.session.irods_session.query(TicketQuery.Ticket).filter(
-                    TicketQuery.Owner.name == user):
-                self._all_tickets.append((row[TicketQuery.Ticket.string],
-                                          row[TicketQuery.Ticket.type],
-                                          self._id_to_path(str(row[TicketQuery.Ticket.object_id])),
-                                          row[TicketQuery.Ticket.expiry_ts]
-                                          ))
-        return self._all_tickets
-
-    def _id_to_path(self, itemid: str) -> str:
-        """
-        Given an iRODS item id (data object or collection) from the
-        TicketQuery.Ticket.object_id this function retrieves the corresponding
-        iRODS path.
-
-        Parameters
-        ----------
-        itemid : str
-            iRODS identifier for a collection or data object (str(row[TicketQuery.Ticket.object_id]))
-
-        Returns
-        -------
-        str
-            collection or data object path
-            returns '' if the identifier does not exist any longer
-        """
-        data_query = self.session.irods_session.query(kw.COLL_NAME, kw.DATA_NAME)
-        data_query = data_query.filter(kw.DATA_ID == itemid)
-
-        if len(list(data_query)) > 0:
-            res = next(data_query.get_results())
-            return list(res.values())[0] + "/" + list(res.values())[1]
-        else:
-            coll_query = self.session.irods_session.query(kw.COLL_NAME)
-            coll_query = coll_query.filter(kw.COLL_ID == itemid)
-            if len(list(coll_query)) > 0:
-                res = next(coll_query.get_results())
-                return list(res.values())[0]
-            else:
-                return ''
diff --git a/old/manager.py b/old/manager.py
deleted file mode 100644
index e77597d8..00000000
--- a/old/manager.py
+++ /dev/null
@@ -1,160 +0,0 @@
-class ibridges():
-    """
-    Implements further policies and specific behaviour on the irodsConnector.
-    """
-
-    def __init__(self, ibridges_conf: utils.path.LocalPath, 
-                 irods_conf: utils.path.LocalPath):
-        """
-        Read in configuration and make parameters accessible, 
-        Read in irods_conf as dictionary
-        """
-
-    def __del__(self):
-        """
-        Cleanup irods connection and itself
-        """
-    
-    @property
-    def ibridges_config(self):
-        """
-        Return current configuration as dictionary
-        """
-
-    @property
-    def davrods(self):
-        """
-        Return davrods server from configuration file
-        """
-    
-    @davrods.setter
-    def davrods(self, davrods_server: str):
-        """
-        Overwrite current davrods in configuration dictionary
-        """
-
-    @property
-    def check_free_space(self):
-        """
-        Return check_free_space from configration file
-        """
-
-    @check_free_space.setter
-    def check_free_space(self, check: bool):
-        """
-        Overwrite current check_free_space in configuration dictionary
-        """
-
-    @property
-    def force_transfers(self):
-        """
-        Return force_transfers from configration file
-        """
-
-    @force_transfers.setter
-    def force_transfers(self, force: bool):
-        """
-        Overwrite current force_transfers in configuration dictionary
-        """
-
-    @property
-    def irods_conf(self):
-        """
-        Returns irods_conf dictionary 
-        """
-
-    @irods_conf.setter
-    def irods_conf(self, irods_conf_path: utils.path.LocalPath):
-        """
-        Loads a new irods environment, deletes a current existing irods connection.
-        """
-
-    def connect(self):
-        """
-        Creates an irodsConnector from the irods_conf, ensures that the connection is active
-        """
-        # read cached password
-        # try to instantiate irodsConnector from irods_env_file and cached password
-        # if fail on Authentication or no cached passowrd, ask for password and instantiate
-    
-    def disconnect(self):
-        """
-        Diconnects (deletes) current irods session
-        """
-        # remove irodsConnector.session and make invalid
-
-
-    def authenticate(self, password):
-        cached_password = get_cached_password()
-
-        # if password equals cached password --> login with irods env file only
-        if cached_password == password:
-            sess = Session(irods_conf_path)
-            result = sess.connect()
-        # if password differs from cached password --> login with env + password
-        else:
-            sess = Session(irods_conf, password=password)
-            result = sess.connect()
-        return result
-
-
-    def get_cached_password(self) -> str:
-        """Scrape the cached password from the iRODS authentication file,
-        if it exists.
-
-        Returns
-        -------
-        str
-            Cached password or null string.
-
-        """
-        irods_auth_file = os.environ.get('IRODS_AUTHENTICATION_FILE', None)
-        if irods_auth_file is None:
-            irods_auth_file = context.irods_env_file.path.parent.joinpath(".irodsA")
-        if utils.path.LocalPath(irods_auth_file).exists():
-            with open(irods_auth_file, encoding='utf-8') as authfd:
-                return irods.password_obfuscation.decode(authfd.read())
-        return ''
-
-    def write_password(self):
-        """Store the password in the iRODS
-        authentication file in obfuscated form.
-
-        """
-        connection = self._irods_session.pool.get_connection()
-        pam_passwords = self._irods_session.pam_pw_negotiated
-        if len(pam_passwords):
-            irods_auth_file = self._irods_session.get_irods_password_file()
-            with open(irods_auth_file, 'w', encoding='utf-8') as authfd:
-                authfd.write(
-                    irods.password_obfuscation.encode(pam_passwords[0]))
-        else:
-            logging.info('WARNING -- unable to cache obfuscated password locally')
-        connection.release()
-
-    def diff_irods_local(self, local_paths: list, irods_paths: list) -> list:
-        """Given a list of file paths and object paths, return those with a different checksum,
-        or are not present locally
-        """
-
-    def diff_local_irods(self, irods_paths: list, local_paths: list) -> list:
-        """ given a list of objects and files, return those with a different checksum, or are not
-        present on iRODS
-        """
-
-    def writeable_resources(self, size: int = 0):
-        """Return only writeable resources.
-        size: Required space in bytes
-        """
-        # Get all root resources
-        # If ibridges config is set to force_transfers = False, only return root resources
-        # which have free_space > size
-
-    def search_data(self, key_vals: dict = None) -> list:
-        """Given a dictionary with metadata attribute names as keys and
-        associated values, query for collections and data objects that
-        fullfill the criteria.
-        """
-
-### link all functions from irodsConnector here
-
diff --git a/old/utils/amber_connector.py b/old/utils/amber_connector.py
deleted file mode 100644
index 90dc69c1..00000000
--- a/old/utils/amber_connector.py
+++ /dev/null
@@ -1,96 +0,0 @@
-"""Connector class with useful functions for AmberScript
-"""
-import json
-
-import requests
-
-
-class AmberConnector():
-    def __init__(self, token):
-        print("Amberscript connector.")
-        self._token = token
-
-
-    @property
-    def jobs(self):
-        url = "https://api.amberscript.com/api/jobs"
-        querystring = {"apiKey":self._token}
-        payload = ""
-        response = requests.request("GET", url, data=payload, params=querystring)
-        if self._status_response(response) in range(200, 300):
-            return self._content_response(response)
-        else:
-            raise Exception("HTTP ERROR "+str(self._status_response(response))+ \
-                                          str(self._content_response(response)))
-
-
-    @property
-    def glossaries(self):
-        url = "https://api.amberscript.com/api/glossary"
-        querystring = {"apiKey":self._token}
-        payload = ""
-        response = requests.request("GET", url, data=payload, params=querystring)
-        if self._status_response(response) in range(200, 300):
-            return self._content_response(response)
-        else:
-            raise Exception("HTTP ERROR "+str(self._status_response(response))+ \
-                                          str(self._content_response(response)))
-
-
-    def _status_response(self, response):
-        return response.status_code
-
-
-    def _content_response(self, response):
-        return json.loads(response.text)
-
-
-    def _text_response(self, response):
-        return response.text
-
-
-
-    def get_job_status(self, job_id):
-        url = "https://api.amberscript.com/api/jobs/status"
-        querystring = {"jobId":job_id,"apiKey":self._token}
-        payload = ""
-        response = requests.request("GET", url, data=payload, params=querystring)
-        if self._status_response(response) in range(200, 300):
-            return self._content_response(response)
-        else:
-            raise Exception("HTTP ERROR "+str(self._status_response(response))+ \
-                                          str(self._content_response(response)))
-
-    def submit_job(self, data, glossary_id = None):
-        url = 'https://api.amberscript.com/api/jobs/upload-media'
-        if glossary_id:
-            querystring = {"jobType":"direct","language":"en",
-                           "transcriptionType":"transcription", 
-                           "glossaryId":glossary_id,
-                           "apiKey":self._token}
-        else:
-            querystring = {"jobType":"direct","language":"en",
-                           "transcriptionType":"transcription",
-                           "apiKey":self._token}
-
-        files = {'file': open(data, 'rb')}
-        response = requests.post(url, files=files, verify=False, params=querystring)
-        if self._status_response(response) in range(200, 300):
-            return self._content_response(response)
-        else:
-            raise Exception(
-                "HTTP ERROR "+str(self._status_response(response))+ \
-                              str(self._content_response(response)))
-
-
-    def get_results_txt(self, job_id):
-        url = "https://api.amberscript.com/api/jobs/export-txt"
-        querystring = {"jobId":job_id, "apiKey":self._token}
-        payload = ""
-        response = requests.request("GET", url, data=payload, params=querystring)
-        if self._status_response(response) in range(200, 300):
-            return self._text_response(response)
-        else:
-            raise Exception(
-                "HTTP ERROR "+str(self._status_response(response))+ \
-                              str(self._content_response(response)))
diff --git a/old/utils/context.py b/old/utils/context.py
deleted file mode 100644
index 2638b6b3..00000000
--- a/old/utils/context.py
+++ /dev/null
@@ -1,325 +0,0 @@
-"""iBridges context: configurations and common services.
-
-"""
-from __future__ import annotations
-
-import json
-import logging
-from typing import Optional
-
-from ibridges.irodsconnector.manager import IrodsConnector
-from ibridges.utils import json_config, path
-from ibridges.utils.json_config import JsonConfig
-from ibridges.utils.path import LocalPath
-
-IBRIDGES_DIR = '~/.ibridges'
-IRODS_DIR = '~/.irods'
-DEFAULT_IBRIDGES_CONF_FILE = f'{IBRIDGES_DIR}/ibridges_config.json'
-IBRIDGES_TEMPLATE = {
-    'check_free_space': True,
-    'force_transfers': False,
-    'verbose': 'info',
-}
-MANDATORY_IRODS_KEYS = [
-    'irods_default_resource',
-    'irods_host',
-    'irods_port',
-    'irods_user_name',
-    'irods_zone_name',
-]
-
-
-class Context:
-    """The singleton context of an iBridges session including singleton
-    configurations and iBridges session instance.
-
-    """
-    _ibridges_conf_file = LocalPath("")
-    _ibridges_configuration: Optional[JsonConfig] = None
-    _instance: Optional[Context] = None
-    _irods_connector: Optional[IrodsConnector] = None
-    _irods_env_file = LocalPath('')
-    _irods_environment: Optional[JsonConfig] = None
-    application_name = ''
-
-    def __new__(cls):
-        """Give only a single new instance ever.
-
-        Returns
-        -------
-        Context
-            A singleton instance.
-
-        """
-        if cls._instance is None:
-            cls._instance = super().__new__(cls)
-        return cls._instance
-
-    def __del__(self):
-        del self.irods_connector
-
-    @property
-    def ibridges_conf_file(self) -> LocalPath:
-        """iBridges configuration filename.
-
-        Returns
-        -------
-        str
-            Name of configuration file
-
-        """
-        return self._ibridges_conf_file
-
-    @ibridges_conf_file.setter
-    def ibridges_conf_file(self, filename: str):
-        """iBridges configuration filename setter.
-
-        Parameters
-        ----------
-        filename : str
-            Name of the configuration file.
-
-        """
-        self._ibridges_conf_file = LocalPath(filename).expanduser()
-        if self._ibridges_configuration:
-            self._ibridges_configuration.filepath = self._ibridges_conf_file
-
-    @property
-    def ibridges_configuration(self) -> json_config.JsonConfig:
-        """iBridges configuration dictionary loaded from the
-        configuration file or from template.
-
-        Returns
-        -------
-        utils.json_config.JsonConfig
-            Configuration instance.
-
-        """
-        if self._ibridges_configuration is None:
-            if not self.ibridges_conf_file:
-                self.ibridges_conf_file = LocalPath(DEFAULT_IBRIDGES_CONF_FILE)
-            filepath = path.LocalPath(self.ibridges_conf_file).expanduser()
-            if not filepath.parent.is_dir():
-                filepath.parent.mkdir()
-            if not filepath.is_file():
-                filepath.write_text(json.dumps(IBRIDGES_TEMPLATE))
-            self._ibridges_configuration = json_config.JsonConfig(filepath)
-        elif self.ibridges_conf_file != self._ibridges_configuration.filepath:
-            self._ibridges_configuration.reset()
-            self._ibridges_configuration.filepath = self.ibridges_conf_file
-        # iBridges configuration check/default entry update.  Do not overwrite!
-        conf_dict = self._ibridges_configuration.config
-        for key, val in IBRIDGES_TEMPLATE.items():
-            if key not in conf_dict:
-                logging.info(
-                    'Adding missing entry to iBridges configuration: (%s, %s)',
-                    key, val)
-                conf_dict[key] = val
-        return self._ibridges_configuration
-
-    @property
-    def irods_connector(self):
-        """An iBridges connection manager.
-
-        Returns
-        -------
-        irodsConnector.manager.IrodsConnector
-            The iBridges connection manager.
-        """
-        return self._irods_connector
-
-    @irods_connector.setter
-    def irods_connector(self, connector):
-        """Connection manager setter.
-
-        Parameters
-        ----------
-        connector : irodsConnector.manager.IrodsConnector
-            The iBridges connection manager.
-
-        """
-        self._irods_connector = connector
-        import irodsConnector
-        if isinstance(connector, irodsConnector.manager.IrodsConnector):
-            self._irods_connector.ibridges_configuration = self.ibridges_configuration
-            logging.debug('self._irods_connector.ibridges_configuration')
-            self._irods_connector.irods_environment = self.irods_environment
-            logging.debug('self._irods_connector.irods_environment')
-
-    @irods_connector.deleter
-    def irods_connector(self):
-        """Connection manager deleter.
-
-        """
-        if self._irods_connector is not None:
-            del self._irods_connector
-        self._irods_connector = None
-
-    @property
-    def irods_env_file(self) -> LocalPath:
-        """iRODS environment filename.
-
-        Returns
-        -------
-        str
-            Name of environment file
-
-        """
-        logging.debug('getting: self._irods_env_file')
-        return self._irods_env_file
-
-    @irods_env_file.setter
-    def irods_env_file(self, filename: str):
-        """iRODS environment filename setter.
-
-        Parameters
-        ----------
-        filename : str
-            Name of the environment file.
-
-        """
-        self._irods_env_file = LocalPath(filename).expanduser()
-        logging.debug('setting: self._irods_env_file')
-        assert self._irods_environment is not None
-        self._irods_environment.filepath = self._irods_env_file
-
-    @property
-    def irods_environment(self) -> json_config.JsonConfig:
-        """iRODS environment dictionary loaded from the configuration
-        file.  Returns a blank, initialized instance if the
-        configuration is not set.
-
-        Returns
-        -------
-        utils.json_config.JsonConfig
-            Configuration instance.
-
-        """
-        if self._irods_environment is None:
-            self._irods_environment = json_config.JsonConfig(self.irods_env_file)
-        elif self.irods_env_file != self._irods_environment.filepath:
-            self._irods_environment.reset()
-            self._irods_environment.filepath = self.irods_env_file
-        return self._irods_environment
-
-    def ienv_is_complete(self) -> bool:
-        """Check if iRODS environment is complete.
-
-        Returns
-        -------
-        bool
-            Whether the environment is complete.
-
-        """
-        if self._irods_environment is not None:
-            env_dict = self._irods_environment.config
-            if env_dict:
-                return is_complete(
-                    env_dict, MANDATORY_IRODS_KEYS, 'iRODS environment')
-            return False
-        return False
-
-    def save_ibridges_configuration(self):
-        """Save iBridges configuration to disk.
-
-        """
-        self.ibridges_configuration.save()
-
-    def save_irods_environment(self):
-        """Save iRODS environment to disk.
-
-        """
-        self.irods_environment.save()
-
-    def reset(self):
-        """Reset existing instances of dynamic class members
-
-        """
-        self.irods_connector.reset()
-        if self.ibridges_configuration:
-            self.ibridges_configuration.reset()
-            filepath = path.LocalPath(self.ibridges_conf_file).expanduser()
-            self.ibridges_configuration.filepath = filepath
-        if self.irods_environment:
-            self.irods_environment.reset()
-            filepath = path.LocalPath(self.irods_env_file).expanduser()
-            self.irods_environment.filepath = filepath
-
-
-def is_complete(conf_dict: dict, mandatory: list, conf_type: str) -> bool:
-    """Check if given iRODS environment has all mandatory keys.
-
-    Parameters
-    ----------
-    conf_dict : dict
-        Configuration to check.
-    mandatory : list
-        Values to check for.
-    conf_type : str
-        Type of configuration.
-
-    Returns
-    -------
-    bool
-        If the given configuration is complete.
-
-    """
-    missing = []
-    for key in mandatory:
-        if key not in conf_dict:
-            missing.append(key)
-    if len(missing) > 0:
-        logging.warning('Missing key(s) in %s: %s', conf_type, missing)
-        logging.warning('Please fix and try again!')
-        return False
-    return True
-
-
-class ContextContainer:
-    """Abstract base class for classes needing to use context.
-       DEPRECATED, class will be removed in the next release.
-
-    """
-    context = Context()
-
-    def __init__(self):
-        logging.debug('ContextContainer inherited by: %s', type(self).__name__)
-        logging.debug('ContextContainer is deprecated!')
-
-    @property
-    def conf(self) -> dict:
-        """iBridges configuration dictionary.
-
-        Returns
-        -------
-        dict
-            Configuration from JSON serialized string.
-
-        """
-        if self.context.ibridges_configuration:
-            return self.context.ibridges_configuration.config
-        return {}
-
-    @property
-    def conn(self):
-        """IrodsConnector instance.
-
-        Returns
-        -------
-        irodsConnector.manager.IrodsConnector
-            iRODS connection instance set into the context.
-        """
-        return self.context.irods_connector
-
-    @property
-    def ienv(self) -> dict:
-        """iRODS environment dictionary.
-
-        Returns
-        -------
-        dict
-            Environment from JSON serialized string.
-        """
-        if self.context.irods_environment:
-            return self.context.irods_environment.config
-        return {}
diff --git a/old/utils/elab_connector.py b/old/utils/elab_connector.py
deleted file mode 100644
index 2161e521..00000000
--- a/old/utils/elab_connector.py
+++ /dev/null
@@ -1,178 +0,0 @@
-"""IrodsConnector for eLabJournal
-
-"""
-from urllib.parse import urlparse
-
-from elabjournal import elabjournal
-
-URL_PATH = '/members/experiments/browser/#view=experiment&nodeID='
-DEFAULT = '\x1b[0m'
-RED = '\x1b[1;31m'
-YEL = '\x1b[1;33m'
-BLUE = '\x1b[1;34m'
-
-
-class ElabConnector():
-
-    def __init__(self, token):
-        try:
-            self.elab = elabjournal.api(key=token)
-            self.experiment = self.elab.experiments().first()
-        except TypeError:
-            raise PermissionError('Invalid token for ELN.')
-
-        self.userId = self.elab.user().id()
-        self.baseUrl = f'https://{token.split(";")[0]}'
-        exp_id = self.experiment.id()
-        self.metadataUrl = f'{self.baseUrl}{URL_PATH}{exp_id}'
-        self.__name__ = 'ELN'
-        self.group = None
-
-    def show_groups(self, get=False):
-        groups_frame = self.elab.groups().all(['name', 'description'])
-        print(groups_frame)
-        if get:
-            lines = groups_frame.to_string().split('\n')[2:]
-            groups = [(group[0], ' '.join(group[1:]))
-                      for group in [line.strip().split() for line in lines]]
-            return groups
-        return True
-
-    def _choose_group(self):
-        success = False
-        while not success:
-            in_var = input('Choose Elab groupId: ')
-            try:
-                group_id = int(in_var)
-                if group_id in self.elab.groups().all().index:
-                    print(f'{BLUE}Group chosen: {self.elab.group().name()}{DEFAULT}')
-                    success = True
-                    return group_id
-
-                print(f'{YEL}\nNot a valid group_id"{DEFAULT}')
-            except ValueError:
-                print(r'{RED}Not a number{DEFAULT}')
-
-    def _resolve_group_name(self, group_name):
-        all_groups = self.elab.groups().all()
-        if len(all_groups.loc[all_groups["name"]==group_name])==1:
-            return all_groups.loc[all_groups["name"]==group_name].index[0]
-        return group_name
-
-    def _switch_group(self, group_id):
-        if isinstance(group_id, str) and group_id.isnumeric():
-            group_id = int(group_id)
-        elif isinstance(group_id, str):
-            group_id = self._resolve_group_name(group_name=group_id)
-
-        if int(group_id) in self.elab.groups().all().index.to_list():
-            self.elab.set_group(group_id)
-            self.group = self.elab.groups().all().loc[[group_id]]
-            return True
-
-        raise ValueError('ERROR ELAB: groupId not found.')
-
-    def show_experiments(self, group_id=None, get=False):
-        current_group = self.elab.group().id()
-        if group_id is None:
-            group_id = self.elab.group().id()
-        self._switch_group(group_id)
-        exp_frames = self.elab.experiments().all()
-        print('Your experiments:')
-        my_exp_frame = exp_frames.loc[exp_frames['userID'] == self.userId, ['name', 'projectID']]
-        print(my_exp_frame)
-        print('Other experiments:')
-        other_exp_frame = exp_frames.loc[exp_frames['userID'] != self.userId, ['name', 'projectID']]
-        print(other_exp_frame)
-        self._switch_group(current_group)
-
-        if get:
-            lines = my_exp_frame.to_string().split('\n')[2:]
-            my_experiments = [(line[0], ' '.join(line[1:len(line) - 1]), line[len(line) - 1])
-                             for line in [l.split() for l in lines]]
-            lines = other_exp_frame.to_string().split('\n')[2:]
-            other_experiments = [(line[0], ' '.join(line[1:len(line) - 1]), line[len(line) - 1])
-                                for line in [l.split() for l in lines]]
-
-            return (my_experiments, other_experiments)
-        return True
-
-    def _choose_experiment(self, group_id=None):
-        current_group = self.elab.group().id()
-        if group_id is None:
-            group_id = self.elab.group().id()
-        else:
-            self._switch_group(group_id)
-        self.showExperiments()
-        success = False
-        while not success:
-            try:
-                exp_id = int(input('Choose an experimentId: '))
-                assert exp_id in self.elab.experiments().all().index
-                success = True
-                self._switch_group(current_group)
-                return exp_id
-            except Exception:
-                print(f'{RED}Not a valid Experiment ID.{DEFAULT}')
-
-    def __resolve_experiment_name(self, exp_name):
-        all_exp = self.elab.experiments().all()
-        if len(all_exp.loc[all_exp["name"]==exp_name])==1:
-            return all_exp.loc[all_exp["name"]==exp_name].index[0]
-        return exp_name
-
-    def _switch_experiment(self, exp_id):
-
-        if isinstance(exp_id, str) and exp_id.isnumeric():
-            exp_id = int(exp_id)
-        elif isinstance(exp_id, str):
-            exp_id = self.__resolve_experiment_name(exp_name=exp_id)
-
-        exp_frames = self.elab.experiments().all()
-        if exp_id in exp_frames.index:
-            self.experiment = self.elab.experiments().get(exp_id)
-            return True
-
-        raise ValueError("ERROR ELAB: expId not found.")
-
-    def update_metadata_url_interactive(self, **params):
-        current_group = self.elab.group().id()
-        if 'group' in params and params['group'] is True:
-            group_id = self._choose_group()
-        else:
-            group_id = current_group
-
-        exp_id = self._choose_experiment(group_id)
-        self._switch_group(group_id)
-        self._switch_experiment(exp_id)
-        self.metadataUrl = f'{self.baseUrl}{URL_PATH}{exp_id}'
-        return self.elab.group().name(), self.experiment.name()
-
-    def update_metadata_url(self, **params):
-        group_id = params['group']
-        exp_id = params['experiment']
-        self._switch_group(group_id)
-        self._switch_experiment(exp_id)
-        self.metadataUrl = f'{self.baseUrl}{URL_PATH}{exp_id}'
-        return self.metadataUrl
-
-    def add_metadata(self, url, meta=None, title='Title'):
-        infos = []
-
-        url_parts = urlparse(url)
-        if all([url_parts.scheme, url_parts.netloc]):
-            infos.append(f'<a href="{url}">Experiment data in iRODS</a>')
-        else:
-            infos.append(url)
-
-        if meta is not None:
-            infos.append('<br>')
-            infos.append('<table style="width: 500px;" border="1" cellspacing="1" cellpadding="1">')
-            infos.append('<tbody>')
-            for key, value in meta.items():
-                infos.append(f'<tr><td>{key}</td><td>{value}</td></tr>')
-            infos.append('</tbody>')
-            infos.append('</table>')
-
-        self.experiment.add(''.join(infos), title)
-        return True
diff --git a/old/utils/elab_plugin.py b/old/utils/elab_plugin.py
deleted file mode 100644
index 2d7bc676..00000000
--- a/old/utils/elab_plugin.py
+++ /dev/null
@@ -1,122 +0,0 @@
-"""
-Plugin for IBridgesCli for annotating eLab Journal.
-"""
-
-import logging
-import os
-
-from irods.exception import CAT_NO_ACCESS_PERMISSION, CollectionDoesNotExist
-
-from ibridges.utils.elab_connector import ElabConnector
-
-
-class ElabPlugin():
-    """
-    Clas containing functions called before and after upload to iRods.
-    """
-
-    def __init__(self) -> None:
-        self.elab = None
-        self.title = None
-
-    def setup(self, calling_class):
-        """
-        Setup, called before upload to iRods.
-        """
-        token = calling_class.context.ibridges_configuration.config.get('eln_token', '')
-        #self.group = calling_class.context.ibridges_configuration.config.get('eln_group', '')
-        #self.experiment = calling_class.context.ibridges_configuration.config.get(
-            # 'eln_experiment', '')
-        #self.title = calling_class.context.ibridges_configuration.config.get('eln_title', '')
-
-        in_var = input('Link data to ElabJournal experiment (Y/N, default N): ').strip().lower()
-        if in_var in ['', 'n', 'no']:
-            logging.info('Skipping ELN')
-            return
-
-        if not token:
-            logging.info('Skipping ELN (no API token found)')
-            return
-
-        self.elab = ElabConnector(token)
-        print(f'INFO: Default experiment is: {self.elab.experiment.name()}')
-        print(f'INFO: Data will be linked to: {self.elab.metadataUrl}')
-        in_var = input('Choose another group or experiment? (Y/N): ').strip().lower()
-
-        if in_var in ['y', 'yes']:
-            self.elab.showGroups()
-            self.elab.updateMetadataUrlInteractive(group=True)
-
-        if not self.title:
-            title = input('ELN paragraph title (default "iRODS data"): ')
-            if title:
-                self.title = title
-            else:
-                self.title = "iRODS data"
-
-        logging.info('Link Data to experiment: %s', self.elab.metadataUrl)
-        logging.info('with title: %s', self.title)
-
-        calling_class.target_path = f"{calling_class.irods_path}/{self.elab.__name__}/" + \
-                                    f"{str(self.elab.group.index[0])}/{str(self.elab.experiment.id())}"
-
-    def annotate(self, calling_class):
-        """
-        Annotation, called after upload to iRods.
-        """
-
-        if not self.elab:
-            return
-
-        if not calling_class.upload_finished:
-            logging.warning('Upload unsuccesful, aborting eLab annotation')
-            return
-
-        irods_conn = calling_class.irods_conn
-
-        try:
-            coll = irods_conn.get_collection(calling_class.target_path)
-        except CollectionDoesNotExist as error:
-            logging.error(
-                'Could not get collection %s: %r',
-                calling_class.target_path, error)
-            return
-
-        annotation = {
-            "iRODS path": coll.path,
-            "iRODS server": irods_conn.host,
-            "iRODS user": irods_conn.username,
-        }
-
-        # YODA: webdav URL does not contain "home", but iRODS path does!
-        if irods_conn.davrods and ("yoda" in irods_conn.host or "uu.nl" in irods_conn.host):
-            url = f"{irods_conn.davrods}/{coll.path.split('home/')[1].strip()}"
-        elif irods_conn.davrods and "surfsara.nl" in irods_conn.host:
-            url = f"{irods_conn.davrods}/{coll.path.split(irods_conn.zone)[1].strip('/')}"
-        elif irods_conn.davrods:
-            url = f"{irods_conn.davrods}/{coll.path.strip('/')}"
-        else:
-            url = '{' + "\n".join([irods_conn.host, irods_conn.zone,
-                                   irods_conn.username, str(irods_conn.port), coll.path]) + '}'
-
-        try:
-            self.elab.addMetadata(url=url, meta=annotation, title=self.title)
-        except ValueError as error:
-            logging.error('Could not add eLab-metadata: %r', error)
-
-        try:
-            if os.path.isfile(calling_class.local_path):
-                item = irods_conn.get_dataobject(
-                    f"{coll.path}/{os.path.basename(calling_class.local_path)}")
-                irods_conn.add_metadata([item], 'ELN', self.elab.metadataUrl)
-            elif os.path.isdir(calling_class.local_path):
-                uploaded_coll = irods_conn.get_collection(
-                    f"{coll.path}/{os.path.basename(calling_class.local_path)}")
-                items = [uploaded_coll]
-                for this_coll, _, objs in uploaded_coll.walk():
-                    items.append(this_coll)
-                    items.extend(objs)
-
-                irods_conn.add_metadata(items, 'ELN', self.elab.metadataUrl)
-        except CAT_NO_ACCESS_PERMISSION as error:
-            logging.error('Could not add iRODS-metadata: %r', error)
diff --git a/old/utils/json_config.py b/old/utils/json_config.py
deleted file mode 100644
index 53586889..00000000
--- a/old/utils/json_config.py
+++ /dev/null
@@ -1,126 +0,0 @@
-"""Manipulate configurations stored as JSON files.
-
-"""
-import json
-from typing import Union
-
-from ibridges.utils import path
-from ibridges.utils.path import LocalPath
-
-
-class JsonConfig:
-    """A configuration stored in a JSON file.
-
-    """
-
-    def __init__(self, filepath: Union[str, LocalPath]):
-        """Create the configuration.
-
-        Parameters
-        ----------
-        filepath : str
-
-        """
-        self._config: dict = {}
-        self.filepath = LocalPath(filepath)
-
-    def __bool__(self) -> bool:
-        """If 'config' dictionary is truthy.
-
-        Returns
-        -------
-        bool
-            If self.config is truthy.
-
-        """
-        return self._config != {}
-
-    def __repr__(self) -> str:
-        """Representation of this configuration.
-
-        Returns
-        -------
-        str
-            Representation.
-
-        """
-        return f'{self.__class__.__name__}("{self._config.__repr__()}")'
-
-    @property
-    def config(self) -> dict:
-        """A persistent configuration dictionary.
-
-        Attempt to load a configuration from the JSON file.
-
-        Returns
-        -------
-        dict
-            A dictionary loaded from the configuration file, if it
-            exists.  The empty persistent dictionary otherwise.
-
-        """
-        if self._config == {}:
-            if self.filepath.is_file():
-                with open(self.filepath, 'r', encoding='utf-8') as confd:
-                    self._config.update(json.load(confd))
-        return self._config
-
-    @config.setter
-    def config(self, conf_dict: dict):
-        """Configuration setter.
-
-        Set the configuration to `conf_dict`.
-        file.
-
-        """
-        self._config.clear()
-        self._config.update(conf_dict)
-
-    @config.deleter
-    def config(self):
-        """Configuration deleter.
-
-        Delete the contents of the configuration dictionary.
-
-        """
-        self._config.clear()
-
-    @property
-    def filepath(self) -> path.LocalPath:
-        """
-
-        """
-        if not isinstance(self._filepath, path.LocalPath):
-            return path.LocalPath()
-        return self._filepath
-
-    @filepath.setter
-    def filepath(self, filepath: str):
-        """
-
-        """
-        self._filepath = path.LocalPath(filepath)
-
-    def clear(self):
-        """Clear the configuration .
-
-        """
-        self._config.clear()
-
-    def reset(self):
-        """Reset current instance
-
-        """
-        self._config.clear()
-        self._filepath = ''
-
-    def save(self):
-        """Write the serialized configuration dictionary to the JSON
-        file.
-
-        """
-        config = {}
-        if self.config != {}:
-            config = self.config
-        with open(self.filepath, 'w', encoding='utf-8') as confd:
-            json.dump(config, confd, indent=4, sort_keys=True)
diff --git a/old/utils/path.py b/old/utils/path.py
deleted file mode 100644
index f43602f8..00000000
--- a/old/utils/path.py
+++ /dev/null
@@ -1,57 +0,0 @@
-"""
-A classes to handle iRODS and local (Win, linux) paths.
-"""
-import pathlib
-import sys
-
-
-def is_posix() -> bool:
-    """Determine POSIXicity.
-
-    Returns
-    -------
-    bool
-        Whether or not this is a POSIX operating system.
-    """
-    return sys.platform not in ['win32', 'cygwin']
-
-
-class IrodsPath(pathlib.PurePosixPath):
-    pass
-    # """
-    # A POSIX path without file system functionality.
-    # """
-
-    # def __new__(cls, *args):
-    #     """
-    #     Instantiate an IrodsPath
-
-    #     Returns
-    #     -------
-    #     IrodsPath
-    #         Instance of PurePosixPath
-    #     """
-    #     path = pathlib.PurePosixPath(*args)
-    #     return path
-
-
-class LocalPath(pathlib.Path):
-    pass
-    # """
-    # A platform-dependent local path with file system functionality.
-    # """
-
-    # def __new__(cls, *args, **kwargs):
-    #     """Instantiate a LocalPath.
-
-    #     Returns
-    #     -------
-    #     LocalPath
-    #         Instance of a PosixPath or WindowsPath.
-
-    #     """
-    #     if is_posix():
-    #         path = pathlib.PosixPath(*args)
-    #     else:
-    #         path = pathlib.WindowsPath(*args)
-    #     return path
diff --git a/old/utils/sync_result.py b/old/utils/sync_result.py
deleted file mode 100644
index 0c7f0f6a..00000000
--- a/old/utils/sync_result.py
+++ /dev/null
@@ -1,21 +0,0 @@
-""" Utilities for synchronising files
-"""
-
-from typing import Optional
-
-# def get_diff_download(source, target)
-# def get_diff_upload(source, target)
-# def get_diff_both(source, target)
-
-class SyncResult:
-    """
-     Return value object for determining diffs
-     """
-    source_path: Optional[str] = None  # can be both a irods or (local) filesystem
-    target_path: Optional[str] = None  # can be both a irods or (local) filesystem
-    source_file_size: Optional[int] = None  # bytes
-
-    def __init__(self, source: str, target: str, filesize: int) -> None:
-        self.source_path = source
-        self.target_path = target
-        self.source_file_size = filesize
diff --git a/old/utils/utils.py b/old/utils/utils.py
deleted file mode 100644
index 4fbcf091..00000000
--- a/old/utils/utils.py
+++ /dev/null
@@ -1,212 +0,0 @@
-### Filesystem utils
-import logging
-import os
-import socket
-import sys
-
-from ibridges.utils import path
-
-
-def ensure_dir(pathname: path.LocalPath) -> bool:
-    """Ensure `pathname` exists as a directory.
-
-    Parameters
-    ----------
-    pathname : pathLocalPath
-        The path to be ensured.
-
-    Returns
-    -------
-    bool
-        If `pathname` exists/was created.
-
-    """
-    try:
-        pathname.mkdir(parents=True, exist_ok=True)
-    except (PermissionError, OSError) as error:
-        logging.info('Error ensuring directory: %r', error)
-    return pathname.is_dir()
-
-
-def get_downloads_dir() -> path.LocalPath:
-    """Find the platform-dependent 'Downloads' directory.
-
-    Returns
-    -------
-    LocalPath
-        Absolute path to 'Downloads' directory.
-
-    """
-    if path.is_posix():
-        return path.LocalPath('~', 'Downloads').expanduser()
-    else:
-        import winreg
-        sub_key = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
-        downloads_guid = '{374DE290-123F-4565-9164-39C4925E467B}'
-        with winreg.OpenKey(winreg.HKEY_CURRENT_USER, sub_key) as key:
-            return path.LocalPath(winreg.QueryValueEx(key, downloads_guid)[0])
-
-
-def get_working_dir() -> path.LocalPath:
-    """Determine working directory where iBridges started.
-
-    Returns
-    -------
-    LocalPath
-        Directory path of the executable.
-
-    """
-    if getattr(sys, 'frozen', False):
-        return path.LocalPath(sys.executable).parent
-    elif __file__:
-        return path.LocalPath(__file__).parent
-    else:
-        return path.LocalPath('.')
-
-
-def get_local_size(pathnames: list) -> int:
-    """Collect the sizes of a set of local files and/or directories and
-    determine the total size recursively.
-
-    Parameters
-    ----------
-    pathnames : list
-        Names of input paths.
-
-    Returns
-    -------
-    int
-        Total size [bytes] of all local files found from the paths in
-        `pathnames`.
-
-    """
-    sizes = []
-    for pathname in pathnames:
-        pathobj = path.LocalPath(pathname)
-        if pathobj.is_dir():
-            for dirname, _, filenames in os.walk(pathobj):
-                for filename in filenames:
-                    filepath = path.LocalPath(dirname, filename)
-                    sizes.append(filepath.stat().st_size)
-        elif pathobj.is_file():
-            sizes.append(pathobj.stat().st_size)
-    return sum(sizes)
-
-
-### Network utils
-def can_connect(hostname: str, port: int) -> bool:
-    """Check connectivity to an iRODS server.
-
-    Parameters
-    ----------
-    hostname : str
-        FQDN/IP of an iRODS server.
-
-    Returns
-    -------
-    bool
-        Connection to `hostname` possible.
-
-    """
-    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
-        try:
-            sock.settimeout(10.0)
-            sock.connect((hostname, port))
-            return True
-        except socket.error:
-            return False
-
-### Output conversion
-
-def bytes_to_str(value: int) -> str:
-    """Render incoming number of bytes to a string with units.
-
-    Parameters
-    ----------
-    value : int
-        Number of bytes.
-
-    Returns
-    -------
-    str
-        Rendered string with units.
-
-    """
-    if value < 1e12:
-        return f'{value / 1e9:.3f} GB'
-    else:
-        return f'{value / 1e12:.3f} TB'
-
-
-### Logger utils
-def set_log_level(log_level: Optional[int] = None):
-    """Set the log level excluding DEBUG-level entries from other
-    modules.  If log_level not specified, attempt to access the verbose
-    setting from the configuration.
-
-    Parameters
-    ----------
-    log_level : int
-        Level to set the current logger.
-
-    """
-    if log_level is None:
-        cntxt = context.Context()
-        verbose = cntxt.ibridges_configuration.config.get('verbose', 'info')
-        log_level = LOG_LEVEL.get(verbose, logging.INFO)
-    logging.getLogger().setLevel(log_level)
-    if log_level == logging.DEBUG:
-        for logger in logging.Logger.manager.loggerDict.values():
-            if hasattr(logger, 'name'):
-                if logger.name != 'root' and not logger.name.startswith('irods'):
-                    logger.debug('Disabling logger: %s', logger.name)
-                    logger.disabled = True
-
-
-def init_logger(app_name: str):
-    """Initialize the application logging service.
-
-    Parameters
-    ----------
-    app_name : str
-        Application name as base name of the log file.
-
-    """
-    old_factory = logging.getLogRecordFactory()
-
-    def new_factory(*args, **kwargs) -> logging.LogRecord:
-        """Custom record factory"""
-        record = old_factory(*args, **kwargs)
-        # Limit the size of the log message to something sane.
-        record.msg = record.msg[:MAX_MSG_LEN]
-        record.prefix = ''
-        record.postfix = ''
-        if record.levelname == 'WARNING':
-            record.prefix = YELLOW
-            record.postfix = DEFAULT
-        if record.levelname == 'ERROR':
-            record.prefix = RED
-            record.postfix = DEFAULT
-        return record
-
-    logging.setLogRecordFactory(new_factory)
-    logger = logging.getLogger()
-    logdir = path.LocalPath(context.IBRIDGES_DIR).expanduser()
-    logfile = logdir.joinpath(f'{app_name}.log')
-    log_formatter = logging.Formatter(
-        '[%(asctime)s] %(name)s {%(filename)s:%(lineno)d} %(levelname)s - %(message)s')
-    file_handler = logging.handlers.RotatingFileHandler(logfile, 'a', 100000, 1)
-    file_handler.setFormatter(log_formatter)
-    logger.addHandler(file_handler)
-    log_formatter = logging.Formatter(
-        '[%(asctime)s] %(levelname)s - %(prefix)s%(message)s%(postfix)s')
-    stream_handler = logging.StreamHandler(sys.stdout)
-    stream_handler.setFormatter(log_formatter)
-    logger.addHandler(stream_handler)
-    # Indicate start of a new session
-    with open(logfile, 'a', encoding='utf-8') as logfd:
-        logfd.write('\n\n')
-        underscores = f'{"_" * 50}\n'
-        logfd.write(underscores * 2)
-        logfd.write(f'\t\t{datetime.datetime.now().isoformat()}\n')
-        logfd.write(underscores * 2)
diff --git a/pyproject.toml b/pyproject.toml
index f663684e..6a0a6579 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -36,11 +36,12 @@ documentation = "https://github.com/UtrechtUniversity/iBridges"
 
 [project.optional-dependencies]
 test = [
-    "flake8==6.0.0",
-    "pylint==2.16.2",
-    "pytest==7.2.1",
-    "pytest-cov==4.0.0",
-    "mypy"
+    "pylint",
+    "pytest",
+    "pytest-cov",
+    "ruff",
+    "mypy",
+    "types-tqdm",
 ]
 
 
@@ -71,4 +72,5 @@ max-args=10
 [tool.ruff]
 exclude = ["_version.py"]
 line-length = 100
-select = ["E", "F", "W", "I", "N"]
+select = ["D", "E", "F", "W", "I", "N"]
+ignore = ["D203", "D213"]
diff --git a/Tutorial_sync.ipynb b/tutorials/Data_sync.ipynb
similarity index 90%
rename from Tutorial_sync.ipynb
rename to tutorials/Data_sync.ipynb
index cae32428..69aba7e9 100644
--- a/Tutorial_sync.ipynb
+++ b/tutorials/Data_sync.ipynb
@@ -18,6 +18,19 @@
     "The command can be in one of the two modes: synchronization of data from the client's local file system to iRODS, or from iRODS to the local file system."
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "76bb5c37",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import os\n",
+    "print(os.getcwd())\n",
+    "os.chdir('..')\n",
+    "print(os.getcwd()) # needs to return ../iBridges"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -28,8 +41,8 @@
     "import os\n",
     "from pathlib import Path\n",
     "from ibridges import Session\n",
-    "from ibridges.utils.path import IrodsPath\n",
-    "from ibridges.irodsconnector.sync import sync"
+    "from ibridges.path import IrodsPath\n",
+    "from ibridges.sync import sync"
    ]
   },
   {
@@ -49,7 +62,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "session = Session(irods_env_path=os.path.expanduser(\"~/.irods/irods_environment.json\"))"
+    "session = Session(irods_env_path=Path(os.path.expanduser(\"~\")).joinpath(\".irods\", \"irods_environment.json\"))"
    ]
   },
   {
@@ -70,11 +83,8 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "local_path = '/data/my_research'\n",
-    "irods_path = 'data/my_research'\n",
-    "\n",
-    "target = IrodsPath(session, \"~\", irods_path)\n",
-    "source = Path(local_path)"
+    "target = IrodsPath(session, \"~\", <irods coll path>)\n",
+    "source = Path(os.path.expanduser(\"~\"), <your path>)"
    ]
   },
   {
@@ -187,7 +197,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.12.0"
+   "version": "3.9.18"
   }
  },
  "nbformat": 4,
diff --git a/Tutorial_irodsConnector.ipynb b/tutorials/QuickStart.ipynb
similarity index 93%
rename from Tutorial_irodsConnector.ipynb
rename to tutorials/QuickStart.ipynb
index c7013cc8..28a07dfb 100644
--- a/Tutorial_irodsConnector.ipynb
+++ b/tutorials/QuickStart.ipynb
@@ -16,6 +16,19 @@
     "## Create an iRODS session (connection to iRODS server)"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "5026bbb9",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import os\n",
+    "print(os.getcwd())\n",
+    "os.chdir('..')\n",
+    "print(os.getcwd()) # needs to return ../iBridges"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -24,8 +37,9 @@
    "outputs": [],
    "source": [
     "from ibridges import Session\n",
-    "from ibridges.utils.path import IrodsPath\n",
+    "from ibridges.path import IrodsPath\n",
     "import os, json\n",
+    "from pathlib import Path\n",
     "from getpass import getpass"
    ]
   },
@@ -66,7 +80,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "with open(os.path.expanduser(\"~/.irods/irods_environment.json\"), \"r\") as f:\n",
+    "with open(env_file, \"r\") as f:\n",
     "    ienv = json.load(f)\n",
     "password = getpass(\"Your iRODS password\")\n",
     "session = Session(irods_env=ienv, password=password)"
@@ -87,7 +101,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "session._write_pam_password()"
+    "session.write_pam_password()"
    ]
   },
   {
@@ -125,7 +139,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "session = Session(irods_env_path=os.path.expanduser(\"~/.irods/irods_environment.json\"))"
+    "session = Session(irods_env_path=env_file)"
    ]
   },
   {
@@ -164,8 +178,8 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "from ibridges.irodsconnector.data_operations import create_collection, get_size, obj_replicas\n",
-    "from ibridges.irodsconnector.data_operations import upload, download, get_dataobject, get_collection"
+    "from ibridges.data_operations import create_collection, get_size, obj_replicas\n",
+    "from ibridges.data_operations import upload, download, get_dataobject, get_collection"
    ]
   },
   {
@@ -451,7 +465,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "from ibridges.irodsconnector.meta import MetaData\n",
+    "from ibridges.meta import MetaData\n",
     "obj_meta = MetaData(obj)\n",
     "print(obj_meta)"
    ]
@@ -483,7 +497,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "obj_meta.delete('NEWKEY', 'NewValue')\n",
+    "obj_meta.delete('NewKey', 'NewValue')\n",
     "print(obj_meta)"
    ]
   },
@@ -502,7 +516,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "obj_meta.set(\"NEWKEY\", \"YetAnotherValue\")\n",
+    "obj_meta.set(\"NewKey\", \"YetAnotherValue\")\n",
     "print(obj_meta)"
    ]
   },
@@ -550,11 +564,11 @@
    "outputs": [],
    "source": [
     "import ibridges.search\n",
-    "from ibridges.search import search\n",
+    "from ibridges.search import search_data\n",
     "\n",
     "#all collections and objects with \n",
     "key_vals = {\"NEWKEY\": \"YetAnotherValue\"}\n",
-    "result = search(session, key_vals=key_vals)\n",
+    "result = search_data(session, key_vals=key_vals)\n",
     "print(result)"
    ]
   },
@@ -575,22 +589,10 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "result = search(session, path=session.home+\"/new_coll/%\")\n",
+    "result = search_data(session, path=session.home+\"/new_coll/%\")\n",
     "print(result)"
    ]
   },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "de3fcea9",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "irods_path = IrodsPath(session, \"~\", \"new_coll\", \"%\")\n",
-    "result1 = search(session, irods_path)\n",
-    "print(result == result1)"
-   ]
-  },
   {
    "cell_type": "markdown",
    "id": "82fb48cc",
@@ -610,7 +612,7 @@
    },
    "outputs": [],
    "source": [
-    "from ibridges.irodsconnector.resources import Resources\n",
+    "from ibridges.resources import Resources\n",
     "resources = Resources(session)"
    ]
   },
@@ -709,7 +711,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "from ibridges.irodsconnector.tickets import Tickets\n",
+    "from ibridges.tickets import Tickets\n",
     "tickets = Tickets(session)\n",
     "print(tickets.all_ticket_strings)\n",
     "print(list(tickets)) # (ticket string, access mode, object or collection id, expiry date in epoche)"
@@ -730,10 +732,11 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "from datetime import datetime\n",
+    "from datetime import datetime, timedelta\n",
     "\n",
     "objPath = \"/nluu12p/home/research-test-christine/books/BenHur.txt\"\n",
-    "ticket = tickets.create_ticket(obj_path=objPath, ticket_type=\"write\", expiry_date=datetime.date.today() + datetime.timedelta(days=1))  # allow write access"
+    "ticket = tickets.create_ticket(obj_path=objPath, ticket_type=\"write\", \n",
+    "                               expiry_date=datetime.today() + timedelta(days=1))  # allow write access"
    ]
   },
   {
@@ -743,7 +746,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "tickets.all_ticket_strings"
+    "print(\"Ticket strings:\\t\", tickets.all_ticket_strings)\n",
+    "for ticket in tickets:\n",
+    "    print(ticket)"
    ]
   },
   {
@@ -781,8 +786,8 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "from ibridges.irodsconnector.rules import execute_rule\n",
-    "rule_file = \"example_rules/example.r\"\n",
+    "from ibridges.rules import execute_rule\n",
+    "rule_file = \"tutorials/example_rules/example.r\"\n",
     "stdout, stderr = execute_rule(session, rule_file, {})"
    ]
   },
@@ -863,7 +868,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "from ibridges.irodsconnector.permissions import Permissions\n",
+    "from ibridges.permissions import Permissions\n",
     "\n",
     "# select a file to inspect and set permissions on\n",
     "item_path = session.home # Path to collection or data object\n",
@@ -890,7 +895,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "perm.available_permissions.codes"
+    "perm.available_permissions"
    ]
   },
   {
@@ -1012,7 +1017,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.10.13"
+   "version": "3.9.18"
   }
  },
  "nbformat": 4,
diff --git a/example_rules/example.r b/tutorials/example_rules/example.r
similarity index 100%
rename from example_rules/example.r
rename to tutorials/example_rules/example.r
diff --git a/Tutorial_iRODS_paths.ipynb b/tutorials/iRODS_paths.ipynb
similarity index 92%
rename from Tutorial_iRODS_paths.ipynb
rename to tutorials/iRODS_paths.ipynb
index 8e8966fe..2e3622fd 100644
--- a/Tutorial_iRODS_paths.ipynb
+++ b/tutorials/iRODS_paths.ipynb
@@ -49,6 +49,19 @@
     "### Create a session"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "8f45d63d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import os\n",
+    "print(os.getcwd())\n",
+    "os.chdir('..')\n",
+    "print(os.getcwd()) # needs to return ../iBridges"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -58,6 +71,7 @@
    "source": [
     "from ibridges import Session\n",
     "import os, json\n",
+    "from pathlib import Path\n",
     "from getpass import getpass"
    ]
   },
@@ -68,7 +82,8 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "with open(os.path.expanduser(\"~/.irods/irods_environment.json\"), \"r\") as f:\n",
+    "env_file = Path(os.path.expanduser(\"~\")).joinpath(\".irods\", \"irods_environment.json\")\n",
+    "with open(env_file, \"r\") as f:\n",
     "    ienv = json.load(f)\n",
     "password = getpass(\"Your iRODS password\")\n",
     "session = Session(irods_env=ienv, password=password, irods_home=None)"
@@ -262,6 +277,14 @@
     "new_loc = win_path.joinpath(ipath.parts[0], ipath.parts[1])\n",
     "print(new_loc)"
    ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b6be82b4",
+   "metadata": {},
+   "outputs": [],
+   "source": []
   }
  ],
  "metadata": {
@@ -280,7 +303,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.10.13"
+   "version": "3.9.18"
   }
  },
  "nbformat": 4,


Test patch:
diff --git a/docker/irods_client/tests/conftest.py b/docker/irods_client/tests/conftest.py
index e194cfe9..2bdca6a4 100644
--- a/docker/irods_client/tests/conftest.py
+++ b/docker/irods_client/tests/conftest.py
@@ -6,13 +6,13 @@
 import tomli
 
 from ibridges import Session
-from ibridges.irodsconnector.data_operations import (
+from ibridges.data_operations import (
     create_collection,
     get_collection,
     get_dataobject,
     upload,
 )
-from ibridges.utils.path import IrodsPath
+from ibridges.path import IrodsPath
 
 
 @pytest.fixture(scope="session")
diff --git a/docker/irods_client/tests/test_data_ops.py b/docker/irods_client/tests/test_data_ops.py
index 975f78c4..82e68d47 100644
--- a/docker/irods_client/tests/test_data_ops.py
+++ b/docker/irods_client/tests/test_data_ops.py
@@ -3,7 +3,7 @@
 
 import pytest
 
-from ibridges.irodsconnector.data_operations import (
+from ibridges.data_operations import (
     download,
     get_collection,
     get_dataobject,
@@ -11,7 +11,7 @@
     is_dataobject,
     upload,
 )
-from ibridges.utils.path import IrodsPath
+from ibridges.path import IrodsPath
 
 
 def _get_digest(obj_or_file):
diff --git a/docker/irods_client/tests/test_meta.py b/docker/irods_client/tests/test_meta.py
index 5508898d..9bf6c09c 100644
--- a/docker/irods_client/tests/test_meta.py
+++ b/docker/irods_client/tests/test_meta.py
@@ -1,7 +1,7 @@
 import pytest
 from pytest import mark
 
-from ibridges.irodsconnector.meta import MetaData
+from ibridges.meta import MetaData
 
 
 @mark.parametrize("item_name", ["collection", "dataobject"])
diff --git a/docker/irods_client/tests/test_permissions.py b/docker/irods_client/tests/test_permissions.py
index 6839c8f4..ee59ccdf 100644
--- a/docker/irods_client/tests/test_permissions.py
+++ b/docker/irods_client/tests/test_permissions.py
@@ -2,9 +2,9 @@
 from irods.exception import CAT_NO_ACCESS_PERMISSION
 from pytest import mark
 
-from ibridges.irodsconnector.data_operations import download, upload
-from ibridges.irodsconnector.permissions import Permissions
-from ibridges.utils.path import IrodsPath
+from ibridges.data_operations import download, upload
+from ibridges.permissions import Permissions
+from ibridges.path import IrodsPath
 
 
 @mark.parametrize("item_name", ["collection", "dataobject"])
diff --git a/docker/irods_client/tests/test_resources.py b/docker/irods_client/tests/test_resources.py
index 641ce727..5332c21a 100644
--- a/docker/irods_client/tests/test_resources.py
+++ b/docker/irods_client/tests/test_resources.py
@@ -1,6 +1,6 @@
 import irods
 
-from ibridges.irodsconnector.resources import Resources
+from ibridges.resources import Resources
 
 
 def test_resources(session, config):
diff --git a/docker/irods_client/tests/test_rules.py b/docker/irods_client/tests/test_rules.py
index ec23bee1..41294a36 100644
--- a/docker/irods_client/tests/test_rules.py
+++ b/docker/irods_client/tests/test_rules.py
@@ -1,4 +1,4 @@
-from ibridges.irodsconnector.rules import execute_rule
+from ibridges.rules import execute_rule
 
 
 def test_rules(session, testdata):
diff --git a/docker/irods_client/tests/test_sync.py b/docker/irods_client/tests/test_sync.py
index e386b3bc..ed6fed99 100644
--- a/docker/irods_client/tests/test_sync.py
+++ b/docker/irods_client/tests/test_sync.py
@@ -1,6 +1,6 @@
-from ibridges.irodsconnector.sync import sync, _calc_checksum
-from ibridges.utils.path import IrodsPath
-from ibridges.irodsconnector.data_operations import create_collection, get_dataobject
+from ibridges.sync import sync_data, _calc_checksum
+from ibridges.path import IrodsPath
+from ibridges.data_operations import create_collection, get_dataobject
 
 def test_sync_dry_run(session, testdata, capsys):
 
@@ -8,7 +8,7 @@ def test_sync_dry_run(session, testdata, capsys):
     coll = create_collection(session=session, coll_path=ipath)
 
     # upload
-    sync(session=session,
+    sync_data(session=session,
          source=testdata,
          target=ipath,
          max_level=None,
@@ -38,7 +38,7 @@ def test_sync_upload_download(session, testdata, tmpdir):
     assert len(coll.data_objects)+len(coll.subcollections)==0, "iRODS folder not empty"
 
     # upload
-    sync(session=session,
+    sync_data(session=session,
          source=testdata,
          target=ipath,
          max_level=None,
@@ -66,7 +66,7 @@ def test_sync_upload_download(session, testdata, tmpdir):
             "Checksums not identical after upload"
 
     # download
-    sync(session=session,
+    sync_data(session=session,
         source=ipath,
         target=tmpdir,
         max_level=None,
diff --git a/docker/irods_client/tests/test_ticket.py b/docker/irods_client/tests/test_ticket.py
index 246dca56..81f620c6 100644
--- a/docker/irods_client/tests/test_ticket.py
+++ b/docker/irods_client/tests/test_ticket.py
@@ -4,7 +4,7 @@
 import pytest
 from pytest import mark
 
-from ibridges.irodsconnector.tickets import Tickets
+from ibridges.tickets import Tickets
 
 
 @mark.parametrize("item_name", ["collection", "dataobject"])
diff --git a/ibridges/path_test.ipynb b/ibridges/path_test.ipynb
new file mode 100644
index 00000000..28e5eadf
--- /dev/null
+++ b/ibridges/path_test.ipynb
@@ -0,0 +1,93 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import json\n",
+    "import os\n",
+    "from ibridges import Session, IrodsPath\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pw = \"y0bDcjJGrbrnnUKP7gLsEeId8MEnRfa2\"\n",
+    "with open(os.path.expanduser(\"~/.irods/irods_environment.json\"), \"r\") as f:\n",
+    "    ienv = json.load(f)\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Auth with password\n"
+     ]
+    }
+   ],
+   "source": [
+    "session = Session(irods_env=ienv, password=pw)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "path = IrodsPath(session)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "IrodsPath(~, bunny, x)"
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "IrodsPath(session, \"~/bunny\", \"x\")"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.6"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/tests/test_irodspath.py b/tests/test_irodspath.py
index db4e0058..d7b4a731 100644
--- a/tests/test_irodspath.py
+++ b/tests/test_irodspath.py
@@ -4,7 +4,7 @@
 from pathlib import Path
 
 from ibridges import IrodsPath
-from ibridges.irodsconnector.data_operations import _create_irods_dest
+from ibridges.data_operations import _create_irods_dest
 
 class MockIrodsSession:
     zone = "testzone"