diff --git a/src/dbetto/textdb.py b/src/dbetto/textdb.py index 6482e84..2b72566 100644 --- a/src/dbetto/textdb.py +++ b/src/dbetto/textdb.py @@ -17,6 +17,7 @@ import json import logging +import os import re import sys from collections.abc import Iterator @@ -68,7 +69,11 @@ class TextDB: """ def __init__( - self, path: str | Path, lazy: str | bool = False, hidden: bool = False + self, + path: str | Path, + lazy: str | bool = False, + hidden: bool = False, + allow_up_tree=False, ) -> None: """Construct a :class:`.TextDB` object. @@ -82,6 +87,8 @@ def __init__( session. hidden ignore hidden (i.e. starting with ".") files of directories. + allow_up_tree + allow paths that go up the tree (i.e. contain ".."). """ if isinstance(lazy, bool): self.__lazy__ = lazy @@ -92,6 +99,7 @@ def __init__( raise ValueError(msg) self.__hidden__ = hidden + self.__allow_up_tree__ = allow_up_tree self.__path__ = Path(path).expanduser().resolve() if not self.__path__.is_dir(): @@ -270,16 +278,39 @@ def group(self, label: str) -> AttrsDict: def __getitem__(self, item: str | Path) -> TextDB | AttrsDict | list | None: """Access files or directories in the database.""" # resolve relative paths / links, but keep it relative to self.__path__ + # up the tree ("..") outside self.__path__ is only allowed if + # self.__allow_up_tree__ is True item = Path(item) if item.is_absolute() and item.is_relative_to(self.__path__): - item = item.expanduser().resolve().relative_to(self.__path__) + try: + item = item.expanduser().resolve().relative_to(self.__path__) + except ValueError: + if self.__allow_up_tree__: + # Path.relative_to(walkup=True) only in python 3.12+ + item = Path(os.path.relpath(item, self.__path__)) + else: + raise elif not item.is_absolute(): - item = ( - (self.__path__ / item).expanduser().resolve().relative_to(self.__path__) - ) + try: + item = ( + (self.__path__ / item) + .expanduser() + .resolve() + .relative_to(self.__path__) + ) + except ValueError: + if self.__allow_up_tree__: + # Path.relative_to(walkup=True) only in python 3.12+ + item = Path( + os.path.relpath( + (self.__path__ / item).expanduser().resolve(), self.__path__ + ) + ) + else: + raise else: - msg = f"{item} lies outside the database root path {self.__path__!s}" + msg = f"{item} is not relative to the database root path {self.__path__!s}" raise ValueError(msg) ext_list = "[" + "|".join(self.__extensions__) + "]" @@ -302,12 +333,21 @@ def __getitem__(self, item: str | Path) -> TextDB | AttrsDict | list | None: obj = db_ptr.__path__ / item.name # do not consider hidden files - if not self.__hidden__ and obj.name.startswith("."): + if not self.__hidden__ and ( + obj.name.startswith(".") and not obj.name.startswith("..") + ): + msg = f"skipping hidden file: {obj}" + log.debug(msg) return None # if directory, construct another TextDB object if obj.is_dir(): - db_ptr.__store__[item_id] = TextDB(obj, lazy=self.__lazy__) + db_ptr.__store__[item_id] = TextDB( + obj, + lazy=self.__lazy__, + hidden=self.__hidden__, + allow_up_tree=self.__allow_up_tree__, + ) else: # try to attach an extension if file cannot be found diff --git a/tests/test_textdb.py b/tests/test_textdb.py index 1d88112..a3671b5 100644 --- a/tests/test_textdb.py +++ b/tests/test_textdb.py @@ -132,6 +132,7 @@ def test_scan(): jdb.scan(recursive=True) assert sorted(jdb.__dict__.keys()) == [ + "__allow_up_tree__", "__ftypes__", "__hidden__", "__lazy__", @@ -149,6 +150,7 @@ def test_scan(): jdb.scan(recursive=False) assert sorted(jdb.__dict__.keys()) == [ + "__allow_up_tree__", "__ftypes__", "__hidden__", "__lazy__", @@ -164,6 +166,7 @@ def test_scan(): jdb.scan(recursive=False, subdir="dir1") assert sorted(jdb.__dict__.keys()) == [ + "__allow_up_tree__", "__ftypes__", "__hidden__", "__lazy__", @@ -292,6 +295,7 @@ def test_lazyness(): jdb = TextDB(testdb, lazy="auto") assert jdb.__lazy__ is True assert sorted(jdb.__dict__.keys()) == [ + "__allow_up_tree__", "__ftypes__", "__hidden__", "__lazy__", @@ -302,6 +306,7 @@ def test_lazyness(): jdb = TextDB(testdb, lazy=True) assert jdb.__lazy__ is True assert sorted(jdb.__dict__.keys()) == [ + "__allow_up_tree__", "__ftypes__", "__hidden__", "__lazy__", @@ -312,6 +317,7 @@ def test_lazyness(): jdb = TextDB(testdb, lazy=False) assert jdb.__lazy__ is False assert sorted(jdb.__dict__.keys()) == [ + "__allow_up_tree__", "__ftypes__", "__hidden__", "__lazy__",