Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Path parameter: raise if path not found on parameter instantiation and add check_exists attribute #800

Merged
merged 11 commits into from
Jul 31, 2023
27 changes: 22 additions & 5 deletions param/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2324,24 +2324,30 @@ class Path(Parameter):
is None).
"""

__slots__ = ['search_paths']
__slots__ = ['search_paths', 'check_exists']

_slot_defaults = _dict_update(
Parameter._slot_defaults, check_exists=True,
)

@typing.overload
def __init__(
self,
default=None, *, search_paths=None,
default=None, *, search_paths=None, check_exists=True,
allow_None=False, doc=None, label=None, precedence=None, instantiate=False,
constant=False, readonly=False, pickle_default_value=True, per_instance=True
):
...

@_deprecate_positional_args
def __init__(self, default=Undefined, *, search_paths=Undefined, **params):
def __init__(self, default=Undefined, *, search_paths=Undefined, check_exists=Undefined, **params):
if search_paths is Undefined:
search_paths = []

self.search_paths = search_paths
self.check_exists = check_exists
super().__init__(default,**params)
self._validate(self.default)

def _resolve(self, path):
return resolve_path(path, path_to_file=None, search_paths=self.search_paths)
Expand All @@ -2354,14 +2360,25 @@ def _validate(self, val):
try:
self._resolve(val)
except OSError as e:
Parameterized(name=f"{self.owner.name}.{self.name}").param.warning('%s',e.args[0])
if self.check_exists:
raise OSError(e.args[0]) from None

def __get__(self, obj, objtype):
"""
Return an absolute, normalized path (see resolve_path).
"""
raw_path = super().__get__(obj,objtype)
return None if raw_path is None else self._resolve(raw_path)
if raw_path is None:
path = None
else:
try:
path = self._resolve(raw_path)
except OSError:
if self.check_exists:
philippjfr marked this conversation as resolved.
Show resolved Hide resolved
raise
else:
path = raw_path
return path

def __getstate__(self):
# don't want to pickle the search_paths
Expand Down
125 changes: 124 additions & 1 deletion tests/testpathparam.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class P(param.Parameterized):
a = param.Path()
b = param.Path(self.fb)
c = param.Path('a.txt', search_paths=[tmpdir1])
d = param.Path(check_exists=False)

self.P = P

Expand All @@ -39,6 +40,7 @@ def _check_defaults(self, p):
assert p.default is None
assert p.allow_None is True
assert p.search_paths == []
assert p.check_exists is True

def test_defaults_class(self):
class P(param.Parameterized):
Expand Down Expand Up @@ -120,6 +122,45 @@ class B(self.P):
with pytest.raises(OSError, match='Path a.txt was not found'):
assert b.c is None

def test_notfound_instantiation_raises_error(self):
with pytest.raises(
OSError,
match=r"Path file was not found in the following place\(s\): \['\S+(\\\\|/)non(\\\\|/)existing(\\\\|/)file'\]"
):
param.Path('non/existing/file')

def test_set_notfound_raises_error(self):
p = self.P()
with pytest.raises(
OSError,
match=r"Path file was not found in the following place\(s\): \['\S+(\\\\|/)non(\\\\|/)existing(\\\\|/)file'\]"
):
p.a = 'non/existing/file'

def test_set_notfound_class_raises_error(self):
with pytest.raises(
OSError,
match=r"Path file was not found in the following place\(s\): \['\S+(\\\\|/)non(\\\\|/)existing(\\\\|/)file'\]"
):
self.P.a = 'non/existing/file'

def test_notfoundok_unbound_no_error(self):
p = param.Path('non/existing/file', check_exists=False)
assert p.default == 'non/existing/file'

def test_notfoundok_class_no_error(self):
self.P.d = 'non/existing/file'
assert self.P.d == 'non/existing/file'

def test_notfoundok_instantiation_no_error(self):
p = self.P(d='non/existing/file')
assert p.d == 'non/existing/file'

def test_notfoundok_set_no_error(self):
p = self.P()
p.d = 'non/existing/file'
assert p.d == 'non/existing/file'


class TestFilenameParameters(unittest.TestCase):

Expand All @@ -140,6 +181,7 @@ class P(param.Parameterized):
a = param.Filename()
b = param.Filename(self.fb)
c = param.Filename('a.txt', search_paths=[tmpdir1])
d = param.Filename(check_exists=False)

self.P = P

Expand All @@ -150,6 +192,7 @@ def _check_defaults(self, p):
assert p.default is None
assert p.allow_None is True
assert p.search_paths == []
assert p.check_exists is True

def test_defaults_class(self):
class P(param.Parameterized):
Expand Down Expand Up @@ -202,6 +245,45 @@ def test_search_paths(self):
assert os.path.isabs(p.c)
assert p.c == self.fa

def test_notfound_instantiation_raises_error(self):
with pytest.raises(
OSError,
match=r"File file was not found in the following place\(s\): \['\S+(\\\\|/)non(\\\\|/)existing(\\\\|/)file'\]"
):
param.Filename('non/existing/file')

def test_set_notfound_class_raises_error(self):
with pytest.raises(
OSError,
match=r"File file was not found in the following place\(s\): \['\S+(\\\\|/)non(\\\\|/)existing(\\\\|/)file'\]"
):
self.P.a = 'non/existing/file'

def test_set_notfound_raises_error(self):
p = self.P()
with pytest.raises(
OSError,
match=r"File file was not found in the following place\(s\): \['\S+(\\\\|/)non(\\\\|/)existing(\\\\|/)file'\]"
):
p.a = 'non/existing/file'

def test_notfoundok_unbound_no_error(self):
p = param.Filename('non/existing/file', check_exists=False)
assert p.default == 'non/existing/file'

def test_notfoundok_class_no_error(self):
self.P.d = 'non/existing/file'
assert self.P.d == 'non/existing/file'

def test_notfoundok_instantiation_no_error(self):
p = self.P(d='non/existing/file')
assert p.d == 'non/existing/file'

def test_notfoundok_set_no_error(self):
p = self.P()
p.d = 'non/existing/file'
assert p.d == 'non/existing/file'


class TestFoldernameParameters(unittest.TestCase):

Expand All @@ -218,7 +300,8 @@ def setUp(self):
class P(param.Parameterized):
a = param.Foldername()
b = param.Foldername(tmpdir1)
c = param.Path('da', search_paths=[tmpdir1])
c = param.Foldername('da', search_paths=[tmpdir1])
d = param.Foldername(check_exists=False)

self.P = P

Expand All @@ -229,6 +312,7 @@ def _check_defaults(self, p):
assert p.default is None
assert p.allow_None is True
assert p.search_paths == []
assert p.check_exists is True

def test_defaults_class(self):
class P(param.Parameterized):
Expand Down Expand Up @@ -281,3 +365,42 @@ def test_search_paths(self):
assert os.path.isdir(p.c)
assert os.path.isabs(p.c)
assert p.c == self.da

def test_notfound_instantiation_raises_error(self):
with pytest.raises(
OSError,
match=r"Folder folder was not found in the following place\(s\): \['\S+(\\\\|/)non(\\\\|/)existing(\\\\|/)folder'\]"
):
param.Foldername('non/existing/folder')

def test_set_notfound_raises_error(self):
p = self.P()
with pytest.raises(
OSError,
match=r"Folder folder was not found in the following place\(s\): \['\S+(\\\\|/)non(\\\\|/)existing(\\\\|/)folder'\]"
):
p.a = 'non/existing/folder'

def test_set_notfound_class_raises_error(self):
with pytest.raises(
OSError,
match=r"Folder folder was not found in the following place\(s\): \['\S+(\\\\|/)non(\\\\|/)existing(\\\\|/)folder'\]"
):
self.P.a = 'non/existing/folder'

def test_notfoundok_unbound_no_error(self):
p = param.Foldername('non/existing/folder', check_exists=False)
assert p.default == 'non/existing/folder'

def test_notfoundok_class_no_error(self):
self.P.d = 'non/existing/folder'
assert self.P.d == 'non/existing/folder'

def test_notfoundok_instantiation_no_error(self):
p = self.P(d='non/existing/folder')
assert p.d == 'non/existing/folder'

def test_notfoundok_set_no_error(self):
p = self.P()
p.d = 'non/existing/folder'
assert p.d == 'non/existing/folder'