From 6c86837fb3c6c01da4b1ad6687ac6c1a9d8d60f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20M=C3=BCller?= Date: Thu, 30 Nov 2023 10:11:06 +0100 Subject: [PATCH] enh: display the actual directory tree instead of a table --- CHANGELOG | 2 + mpl_data_cast/gui/main.py | 7 +- mpl_data_cast/gui/widget_input.py | 4 + mpl_data_cast/gui/widget_output.py | 4 +- mpl_data_cast/gui/widget_tree.py | 123 ++++++++------------ mpl_data_cast/gui/widget_tree.ui | 26 +---- mpl_data_cast/mod_recipes/rcp_rtdc.py | 2 +- mpl_data_cast/path_tree.py | 145 ------------------------ tests/test_gui_rtdc.py | 18 +-- tests/test_path_tree.py | 157 -------------------------- 10 files changed, 74 insertions(+), 414 deletions(-) delete mode 100644 mpl_data_cast/path_tree.py delete mode 100644 tests/test_path_tree.py diff --git a/CHANGELOG b/CHANGELOG index 5f4ef8a..a137428 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ 0.6.0 - feat: generalize GUI to use all recipes + - enh: display the actual directory tree instead of a table - enh: compute file hash while copying, avoiding reading data twice - enh: handle PermissionError when building directory tree - enh: identify existing target paths based on size quicker @@ -7,6 +8,7 @@ - enh: display object count and size in tree views - ref: migrate from pkg_resources to importlib.resources - ref: unify input and output tree widget with one base class + - ref: remove the path_tree submodule 0.5.2 - build: add mpldc.exe CLI to Windows binary release - setup: bump dclab to 0.55.6 diff --git a/mpl_data_cast/gui/main.py b/mpl_data_cast/gui/main.py index aa75513..1a244f2 100644 --- a/mpl_data_cast/gui/main.py +++ b/mpl_data_cast/gui/main.py @@ -88,8 +88,8 @@ def on_action_preferences(self): "main/tree_depth_limit", 8)) self.widget_input.tree_depth_limit = int(self.settings.value( "main/tree_depth_limit", 8)) - self.widget_output.update_tree_dir(self.settings.value( - "main/output_path", pathlib.Path.home())) + self.widget_output.path = self.settings.value("main/output_path", + pathlib.Path.home()) @QtCore.pyqtSlot() def on_action_quit(self) -> None: @@ -154,7 +154,6 @@ def on_task_transfer(self) -> None: self.progressBar.setValue(0) QtWidgets.QApplication.processEvents( QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300) - self.widget_output.update_tree() else: msg = "Some problems occured during data transfer:\n" for path, _ in result["errors"]: @@ -171,7 +170,7 @@ def on_task_transfer(self) -> None: def on_recipe_changed(self): # Update the recipe description rec_cls = self.current_recipe - doc = rec_cls.__doc__.split("\n")[0] + doc = rec_cls.__doc__.strip().split("\n")[0] self.label_recipe_descr.setText(f"*{doc}*") self.widget_input.recipe = rec_cls self.widget_output.recipe = rec_cls diff --git a/mpl_data_cast/gui/widget_input.py b/mpl_data_cast/gui/widget_input.py index 7780a2e..bb58bda 100644 --- a/mpl_data_cast/gui/widget_input.py +++ b/mpl_data_cast/gui/widget_input.py @@ -1,3 +1,5 @@ +import pathlib + from .widget_tree import TreeWidget @@ -6,3 +8,5 @@ class InputWidget(TreeWidget): Contains a lineEdit, a button, and a treeview widget.""" def __init__(self, *args, **kwargs): super(InputWidget, self).__init__(which="input", *args, **kwargs) + + self.path = pathlib.Path.cwd() diff --git a/mpl_data_cast/gui/widget_output.py b/mpl_data_cast/gui/widget_output.py index 1c11243..8feb6e0 100644 --- a/mpl_data_cast/gui/widget_output.py +++ b/mpl_data_cast/gui/widget_output.py @@ -9,5 +9,5 @@ class OutputWidget(TreeWidget): def __init__(self, *args, **kwargs): super(OutputWidget, self).__init__(which="output", *args, **kwargs) - self.update_tree_dir( - str(self.settings.value("main/output_path", pathlib.Path.home()))) + self.path = self.settings.value("main/output_path", + pathlib.Path.home()) diff --git a/mpl_data_cast/gui/widget_tree.py b/mpl_data_cast/gui/widget_tree.py index db2d320..3d65a9a 100644 --- a/mpl_data_cast/gui/widget_tree.py +++ b/mpl_data_cast/gui/widget_tree.py @@ -4,9 +4,8 @@ import threading from typing import Literal -from PyQt6 import QtWidgets, QtCore, uic +from PyQt6 import QtWidgets, QtCore, QtGui, uic -from ..path_tree import PathTree, list_items_in_tree from ..recipe import map_recipe_name_to_class from ..util import is_dir_writable @@ -22,6 +21,7 @@ def __init__(self, *args, **kwargs): self.size_objects = 0 self.must_break = False self.is_counting = False + self.has_counted = False def run(self): recipe = self.recipe @@ -41,14 +41,16 @@ def run(self): # reset self.num_objects = 0 self.size_objects = 0 + self.has_counted = False recipe = self.recipe path = self.path - elif self.num_objects: + elif self.num_objects or self.has_counted: # already counted pass else: # start crawling the directory tree self.is_counting = True + self.has_counted = False try: rcp = recipe(path, path) except BaseException: @@ -76,6 +78,7 @@ def run(self): except BaseException: pass self.is_counting = False + self.has_counted = True time.sleep(0.5) @@ -105,6 +108,11 @@ def __init__(self, self.tree_counter = TreeObjectCounter() self.tree_counter.start() + # tree view model + self.model = QtGui.QFileSystemModel() + self.model.setReadOnly(True) + self.treeView.setModel(self.model) + # UI update function self.tree_label_timer = QtCore.QTimer(self) self.tree_label_timer.timeout.connect(self.on_update_object_count) @@ -115,12 +123,11 @@ def __init__(self, self.settings = QtCore.QSettings() self.tree_depth_limit = int(self.settings.value( "main/tree_depth_limit", 3)) - self.update_tree_dir(str(pathlib.Path.home())) self.pushButton_dir.clicked.connect( - self.on_task_select_tree_dir) + self.on_tree_browse_button) self.lineEdit_dir.editingFinished.connect( - self.update_tree_dir_from_lineedit) + self.on_tree_edit_line) @property def path(self): @@ -128,8 +135,21 @@ def path(self): @path.setter def path(self, path): - self._path = path - self.tree_counter.path = path + path = pathlib.Path(path) + if not self.readonly and not is_dir_writable(path): + msg_txt = f"The {self.which} directory '{path}' is not " \ + f"writable. Please select a different directory." + msg = QtWidgets.QMessageBox(self) + msg.setIcon(QtWidgets.QMessageBox.Icon.Warning) + msg.setText(msg_txt) + msg.setWindowTitle(f"{self.which.capitalize()} directory invalid") + msg.exec() + else: + self._path = path + self.tree_counter.path = path + self.model.setRootPath(str(path)) + self.treeView.setRootIndex(self.model.index(str(path))) + self.lineEdit_dir.setText(str(path)) @property def recipe(self): @@ -141,26 +161,6 @@ def recipe(self, recipe): self._recipe = recipe self.tree_counter.recipe = recipe - @QtCore.pyqtSlot() - def on_task_select_tree_dir(self) -> None: - p = QtWidgets.QFileDialog.getExistingDirectory( - parent=self, - caption=f"Select {self.which} directory:", - directory=str(self.path) if self.path else None) - if p: - self.update_tree_dir(p) - - @QtCore.pyqtSlot() - def on_update_object_count(self): - objects = self.tree_counter.num_objects - size = self.tree_counter.size_objects - size_str = human_size(size) - if self.tree_counter.is_counting: - label = f"counting {objects} objects ({size_str})" - else: - label = f"{objects} objects ({size_str})" - self.label_objects.setText(label) - @QtCore.pyqtSlot(object) def dragEnterEvent(self, e) -> None: """Whether files are accepted""" @@ -179,58 +179,35 @@ def dropEvent(self, e) -> None: path_tree = pp else: path_tree = pp.parent - self.update_tree_dir(path_tree) - - @QtCore.pyqtSlot() - def update_tree_dir_from_lineedit(self) -> None: - """Executed when the tree path was manually edited by the user.""" - tree_dir = self.lineEdit_dir.text() - if tree_dir: - self.update_tree_dir(tree_dir) + self.path = path_tree @QtCore.pyqtSlot() - def update_object_count(self) -> None: - """Update `self.label_objects` with the counted events""" + def on_tree_browse_button(self) -> None: + p = QtWidgets.QFileDialog.getExistingDirectory( + parent=self, + caption=f"Select {self.which} directory:", + directory=str(self.path) if self.path else None) + if p: + self.path = p @QtCore.pyqtSlot() - def update_tree_dir(self, tree_dir: str | pathlib.Path) -> None: - """Checks if the tree directory as given by the user exists and - updates the lineEdit widget accordingly. - - Parameter - --------- - tree_dir: str or pathlib.Path - The directory for the tree. - """ - if not self.readonly and not is_dir_writable(tree_dir): - msg_txt = f"The {self.which} directory '{tree_dir}' is not " \ - f"writable. Please select a different directory." - msg = QtWidgets.QMessageBox(self) - msg.setIcon(QtWidgets.QMessageBox.Icon.Warning) - msg.setText(msg_txt) - msg.setWindowTitle(f"{self.which.capitalize()} directory invalid") - msg.exec() - else: - tree_dir = pathlib.Path(tree_dir) + def on_tree_edit_line(self) -> None: + """User edited the lineEdit manually""" + tree_dir = pathlib.Path(self.lineEdit_dir.text()) + if tree_dir.is_dir(): self.path = tree_dir - self.lineEdit_dir.setText(str(tree_dir)) - self.update_tree() @QtCore.pyqtSlot() - def update_tree(self) -> None: - """Update the `PathTree` object based on the current root path in - `self.path` and update the GUI to show the new tree.""" - self.p_tree = PathTree(self.path, self.tree_depth_limit) - self.treeWidget.clear() - self.treeWidget.setColumnCount(self.p_tree.tree_depth + 1) - - list_items_in_tree(self.p_tree, - self.treeWidget, - h_level=0, - depth_limit=self.tree_depth_limit) - - QtWidgets.QApplication.processEvents( - QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 300) + def on_update_object_count(self): + """`self.tree_label_timer` calls this regularly""" + objects = self.tree_counter.num_objects + size = self.tree_counter.size_objects + size_str = human_size(size) + if self.tree_counter.is_counting: + label = f"counting {objects} objects ({size_str})" + else: + label = f"{objects} objects ({size_str})" + self.label_objects.setText(label) def human_size(bt, units=None): diff --git a/mpl_data_cast/gui/widget_tree.ui b/mpl_data_cast/gui/widget_tree.ui index bd48ab9..319e7c8 100644 --- a/mpl_data_cast/gui/widget_tree.ui +++ b/mpl_data_cast/gui/widget_tree.ui @@ -66,7 +66,7 @@ - + true @@ -94,32 +94,18 @@ Qt::ElideRight - - 5 - true - - 1 - - - 0 + + true - + true - - - false - + - 125 + 200 - - - 1 - - diff --git a/mpl_data_cast/mod_recipes/rcp_rtdc.py b/mpl_data_cast/mod_recipes/rcp_rtdc.py index 1f4b099..a8a336f 100644 --- a/mpl_data_cast/mod_recipes/rcp_rtdc.py +++ b/mpl_data_cast/mod_recipes/rcp_rtdc.py @@ -7,7 +7,7 @@ class RTDCRecipe(Recipe): __doc__ = f""" - Compress DC data and include .ini files (dclab {dclab.__version__}) + Compress raw DC data and include .ini files (dclab {dclab.__version__}) """ def convert_dataset(self, path_list, temp_path, **kwargs): diff --git a/mpl_data_cast/path_tree.py b/mpl_data_cast/path_tree.py deleted file mode 100644 index dbc0bf0..0000000 --- a/mpl_data_cast/path_tree.py +++ /dev/null @@ -1,145 +0,0 @@ -""" -Structuring paths in a tree to efficiently handle files. -""" - -import pathlib -import numpy as np -from PyQt6 import QtWidgets - - -class PathError(BaseException): - pass - - -class PathTree: - def __init__(self, path: pathlib.Path, depth_limit: int = 8): - """ - Class for handling paths in a tree structure. - - Parameters - ---------- - path : str or pathlib.Path - The root of the path tree. Must be absolute path. If the path - points to a file, the parent directory of that file is used. - depth_limit : int - Maximum depth of subdirectories that the PathTree object will load. - Avoid large numbers on directories with many subfolders and files - to reduce speed and performance issues. - """ - if not isinstance(path, pathlib.Path): - raise TypeError(f"Path for PathTree must be `pathlib.Path`. Got " - f"{type(path)} instead!") - if path.is_dir(): - self.tree_root = path - elif path.is_file(): - self.tree_root = path.parent - else: - raise PathError("Root path given as input for PathTree is neither " - "a valid directory nor a valid file. Don't know " - "what to do.") - self.depth_limit = depth_limit - self.children = {} - self.list_child_dirs = [] - - tree_iterator = self.tree_root.iterdir() - while True: - try: - file_obj = next(tree_iterator) - except StopIteration: - break - except PermissionError: - # Windows might encounter this. - pass - else: - if file_obj.is_dir() and self.depth_limit > 1: - self.children[file_obj.name] = \ - PathTree(file_obj, self.depth_limit - 1) - elif file_obj.is_dir() and self.depth_limit == 1: - # in case we are in the "deepest" PathTree, only make - # a list of subdirectories, but not full PathTree objects. - # This will make sure that the function `get_tree_depth` - # will always return correct values. - self.list_child_dirs.append(file_obj.name) - self.tree_depth = self.get_tree_depth() - - def __contains__(self, other_path: pathlib.Path) -> bool: - # this will not work perfectly, since the tree might just not have - # enough depth to contain the path in question (if self.depth_limit is - # rather small). - # To check the whole tree, use: - # path in tree.retrieve_full_path_tree() - if other_path.samefile(self.tree_root): - return True - elif self.children: - for child_tree in self.children.values(): - return other_path in child_tree - elif self.list_child_dirs: - contained = False - for sub_dir in self.list_child_dirs: - if other_path.samefile(self.tree_root / sub_dir): - contained = True - break - return contained - else: - return False - - def get_tree_depth(self) -> int: - """Returns the depth of the path tree. If there are no subfolders, - the depth is 1. Works recursively!""" - if self.children: - child_depths = [child.get_tree_depth() for child in - self.children.values()] - return np.max(child_depths) + 1 - else: - return 1 - - def get_file_list(self) -> list[pathlib.Path]: - """Return a list of all files in the root directory.""" - return [x for x in self.tree_root.glob("*.*") if - x.is_file() and not x.name[0] == "."] - - def retrieve_full_path_tree(self): - """Returns a `PathTree` object containing all files and subdirectories, - without a depth limit (depth_limit=100).""" - return PathTree(self.tree_root, depth_limit=100) - - -def list_items_in_tree(p_tree: PathTree, - tree_widget: QtWidgets.QTreeWidget, - h_level: int = 0, - depth_limit: int = 24) -> None: - """Recursive function used for visualisation of the paths saved in a - `PathTree` object. - To avoid problems when working with many nested subdirectories, a limit - for the maximum depth of subfolders is given by the parameter - `depth_limit`. - - Parameters - ---------- - p_tree : PathTree - An instance of :class:`PathTree`, contains all the information about - files and subfolders. - tree_widget : QtWidgets.QTreeWidget - The QtWidget used to display the files and subfolders in a certain - directory (=the root directory of :PathTree:`p_tree`) - h_level : int - Counter for the hierarchy level, tells how deep into subfolders the - algorithms went. Needed for displaying files in the right column of the - QTreeWidget. Basically counts the recursion depth of the function. - depth_limit : int - Maximum depth of subfolders that will be displayed. - """ - root_item = QtWidgets.QTreeWidgetItem(tree_widget) - root_item.setText(h_level, p_tree.tree_root.name) - if h_level < depth_limit: - for file in p_tree.get_file_list(): - item = QtWidgets.QTreeWidgetItem(tree_widget) - item.setText(h_level + 1, file.name) - if p_tree.children and h_level < depth_limit: - for child_tree in p_tree.children.values(): - list_items_in_tree(child_tree, tree_widget, h_level + 1, - depth_limit) - if p_tree.list_child_dirs and h_level <= depth_limit: - for sub_dir in p_tree.list_child_dirs: - item = QtWidgets.QTreeWidgetItem(tree_widget) - item.setText(h_level + 1, sub_dir) diff --git a/tests/test_gui_rtdc.py b/tests/test_gui_rtdc.py index d131f52..863f878 100644 --- a/tests/test_gui_rtdc.py +++ b/tests/test_gui_rtdc.py @@ -18,11 +18,9 @@ def test_setting_paths(qtbot, tmp_path): path_in = retrieve_data("rcp_rtdc_mask-contour_2018.zip") mw.widget_input.lineEdit_dir.setText(str(path_in)) mw.widget_output.lineEdit_dir.setText(str(tmp_path)) - mw.widget_input.update_tree_dir_from_lineedit() - mw.widget_output.update_tree_dir_from_lineedit() + mw.widget_input.on_tree_edit_line() + mw.widget_output.on_tree_edit_line() assert str(mw.widget_output.path) == str(tmp_path) - assert mw.widget_input.p_tree.tree_depth == 1 - assert mw.widget_output.p_tree.tree_depth == 1 mw.close() QtTest.QTest.qWait(100) QtWidgets.QApplication.processEvents( @@ -45,8 +43,8 @@ def test_transfer_data_simple(qtbot, tmp_path, monkeypatch): assert tmp_path.exists() assert path_out.exists() - mw.widget_input.update_tree_dir(tmp_path) - mw.widget_output.update_tree_dir(path_out) + mw.widget_input.path = tmp_path + mw.widget_output.path = path_out bt = mw.pushButton_transfer @@ -95,9 +93,8 @@ def test_transfer_data_advanced(qtbot, tmp_path, monkeypatch): assert tmp_file2.exists() assert len(list(path_out.rglob("*.rtdc"))) == 0 - mw.widget_input.update_tree_dir(path_in) - mw.widget_output.update_tree_dir(path_out) - assert mw.widget_output.p_tree.tree_depth == 1 + mw.widget_input.path = path_in + mw.widget_output.path = path_out bt = mw.pushButton_transfer @@ -109,9 +106,6 @@ def test_transfer_data_advanced(qtbot, tmp_path, monkeypatch): assert tmp_file1.exists() assert tmp_file2.exists() - assert mw.widget_output.p_tree.tree_depth == 3 - assert mw.widget_input.treeWidget.columnCount() == 4 - assert mw.widget_output.treeWidget.columnCount() == 4 mw.close() QtTest.QTest.qWait(100) QtWidgets.QApplication.processEvents( diff --git a/tests/test_path_tree.py b/tests/test_path_tree.py deleted file mode 100644 index 66a52ab..0000000 --- a/tests/test_path_tree.py +++ /dev/null @@ -1,157 +0,0 @@ -import tempfile as tf -import pathlib -import shutil - -import pytest - -from helper import retrieve_data -from mpl_data_cast.path_tree import PathTree, PathError - - -def test_simple_tree_from_dir(): - """Check basic attributes of a small tree""" - with tf.TemporaryDirectory() as tmpdir: - tmpdir = pathlib.Path(tmpdir) - tree = PathTree(tmpdir, depth_limit=10) - assert isinstance(tree.tree_root, pathlib.Path) - assert isinstance(tree.children, dict) - assert tree.tree_depth == 1 - - -def test_simple_tree_from_file(): - """Check PathTree generation with just one file.""" - with tf.TemporaryDirectory() as tmpdir: - with tf.NamedTemporaryFile(dir=tmpdir) as tmpfile: - tmpfile = pathlib.Path(str(tmpfile.name)) - tree = PathTree(tmpfile) - assert isinstance(tree.tree_root, pathlib.Path) - assert tree.tree_depth == 1 - assert tree.tree_root == tmpfile.parent - - -def test_wrong_input_raises_errors(tmp_path): - """Check that errors are raised in case the input for the PathTree - constructor is wrong.""" - with pytest.raises(TypeError): - _ = PathTree(r"some/path/to/dir") - with pytest.raises(PathError): - _ = PathTree(pathlib.Path(r"some/path/somewhere")) - - -def test_tree_with_subdir(): - """Check PathTree generation with a subfolder""" - with tf.TemporaryDirectory() as tmpdir: - with tf.TemporaryDirectory(dir=tmpdir) as tmpdir2: - tmpdir = pathlib.Path(tmpdir) - tmpdir2 = pathlib.Path(tmpdir2) - - tree = PathTree(tmpdir) - assert tree.tree_depth == 2 - assert len(tree.children) == 1 - assert tmpdir2.name in tree.children.keys() - assert isinstance(tree.children[tmpdir2.name], PathTree) - - -def test_tree_with_subdir_and_files(tmp_path): - path_in = retrieve_data("rcp_rtdc_mask-contour_2018.zip") - test_file = path_in / "M001_data.rtdc" - t_file1 = tmp_path / "M001_data.rtdc" - shutil.copy(test_file, t_file1) - with tf.TemporaryDirectory(dir=tmp_path) as tmpdir2: - tmpdir2 = pathlib.Path(tmpdir2) - t_file2 = tmpdir2 / "M002_data.rtdc" - shutil.copy(t_file1, t_file2) - - assert t_file1.exists() - assert t_file2.exists() - tree = PathTree(tmp_path) - - assert tree.tree_depth == 2 - assert len(tree.get_file_list()) == 1 - child_tree = tree.children[tmpdir2.name] - assert isinstance(child_tree, PathTree) - assert child_tree.tree_depth == 1 - assert child_tree.get_file_list()[0] == t_file2 - - -def test_tree_with_hidden_file(tmp_path): - """Check that PathTree ignores hidden files which start with a '.'""" - path_in = retrieve_data("rcp_rtdc_mask-contour_2018.zip") - test_file = path_in / "M001_data.rtdc" - t_file1 = tmp_path / "M001_data.rtdc" - # and a "hidden" file - t_file2 = tmp_path / ".M001_data.rtdc" - shutil.copy(test_file, t_file1) - shutil.copy(test_file, t_file2) - with tf.TemporaryDirectory(dir=tmp_path) as tmpdir2: - tmpdir2 = pathlib.Path(tmpdir2) - t_file3 = tmpdir2 / "M002_data.rtdc" - shutil.copy(t_file1, t_file3) - - assert t_file1.exists() - assert t_file2.exists() - assert t_file3.exists() - tree = PathTree(tmp_path) - - assert tree.tree_depth == 2 - assert len(tree.get_file_list()) == 1 - child_tree = tree.children[tmpdir2.name] - assert isinstance(child_tree, PathTree) - assert child_tree.tree_depth == 1 - assert child_tree.get_file_list()[0] == t_file3 - - -def test_path_in_tree(tmp_path): - """Check that the __contains__ functionality works""" - path_in = retrieve_data("rcp_rtdc_mask-contour_2018.zip") - test_file = path_in / "M001_data.rtdc" - t_file1 = tmp_path / "M001_data.rtdc" - shutil.copy(test_file, t_file1) - with tf.TemporaryDirectory(dir=tmp_path) as tmpdir2: - tmpdir2 = pathlib.Path(tmpdir2) - with tf.TemporaryDirectory(dir=tmpdir2) as tmpdir3: - tmpdir3 = pathlib.Path(tmpdir3) - - tree = PathTree(tmp_path) - assert tree.tree_depth == 3 - assert tmpdir2 in tree - assert tmpdir3 in tree - - -def test_retrieve_full_tree(tmp_path): - """Check that the function `retrieve_full_path_tree()` acutally returns - a PathTree object with all subdirectories in full depth.""" - with tf.TemporaryDirectory(dir=tmp_path) as tmpdir2: - tmpdir2 = pathlib.Path(tmpdir2) - with tf.TemporaryDirectory(dir=tmpdir2) as tmpdir3: - _ = pathlib.Path(tmpdir3) - - tree = PathTree(tmp_path, depth_limit=2) - assert tree.tree_depth == 2 - - full_tree = tree.retrieve_full_path_tree() - assert full_tree.tree_depth == 3 - assert full_tree.tree_root.samefile(tree.tree_root) - - -def test_small_path_tree(tmp_path): - """Have a shallow PathTree object and check that the children on the - deepest level do not have PathTree objects as children, but only a list - of subdirectories in `PathTree.list_child_dirs`.""" - with tf.TemporaryDirectory(dir=tmp_path) as tmpdir2: - tmpdir2 = pathlib.Path(tmpdir2) - with tf.TemporaryDirectory(dir=tmpdir2) as tmpdir3: - tmpdir3 = pathlib.Path(tmpdir3) - - tree = PathTree(tmp_path, depth_limit=2) - assert tree.tree_depth == 2 - child_tree = tree.children[tmpdir2.name] - assert not child_tree.children - assert child_tree.list_child_dirs - assert child_tree.list_child_dirs[0] == tmpdir3.name - - # check whether the __contains__ magic function was implemented - # correctly - assert tmpdir2 in tree - assert tmpdir3 in tree - assert tmpdir3 in child_tree