Skip to content

Commit

Permalink
Path parameter: raise if path not found on parameter instantiation …
Browse files Browse the repository at this point in the history
…and add `check_exists` attribute (#800)
  • Loading branch information
maximlt authored Jul 31, 2023
1 parent 978af55 commit f5208aa
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 32 deletions.
32 changes: 29 additions & 3 deletions examples/user_guide/Parameter_Types.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,9 @@
"\n",
"A Path can be set to a string specifying the path of a file or folder. In code, the string should be specified in POSIX (UNIX) style, using forward slashes / and starting from / if absolute or some other character if relative. When retrieved, the string will be in the format of the user's operating system. \n",
"\n",
"Relative paths are converted to absolute paths by searching for a matching filename on the filesystem. If `search_paths` is provided and not empty, the folders in that list are searched for the given filename, in order, returning the absolute path for the first match found by appending the provided path to the search path. An IOError is raised if the file or folder is not found. If `search_paths` is empty (the default), the file or folder is expected to be in the current working directory.\n",
"Relative paths are converted to absolute paths by searching for a matching filename on the filesystem in the current working directory (obtained with `os.getcwd()`). If `search_paths` is provided and not empty, the folders in that list are searched for the given filename, in order, returning the absolute path for the first match found by appending the provided path to the search path. An IOError is raised if the file or folder is not found. If `search_paths` is empty (the default), the file or folder is expected to be in the current working directory.\n",
"\n",
"When `check_exists` is set to `False` (default is `True`) the provided path can optionally exist. This is for instance useful to declare an output file path that is meant to be created at a later stage in a process. In the default case the path must exist, on Parameter instantiation and setting.\n",
"\n",
"Either a file or a folder name is accepted by `param.Path`, while `param.Filename` accepts only file names and `param.Foldername` accepts only folder names."
]
Expand All @@ -716,6 +718,7 @@
" p = param.Path('Parameter_Types.ipynb')\n",
" f = param.Filename('Parameter_Types.ipynb')\n",
" d = param.Foldername('lib', search_paths=['/','/usr','/share'])\n",
" o = param.Filename('output.csv', check_exists=False)\n",
" \n",
"p = P()\n",
"p.p"
Expand All @@ -728,7 +731,7 @@
"metadata": {},
"outputs": [],
"source": [
"p.p='/usr/lib'\n",
"p.p = '/usr/lib'\n",
"p.p"
]
},
Expand All @@ -750,7 +753,7 @@
"outputs": [],
"source": [
"with param.exceptions_summarized():\n",
" p.f='/usr/lib'"
" p.f = '/usr/lib'"
]
},
{
Expand All @@ -774,6 +777,29 @@
" p.d = 'Parameter_Types.ipynb'"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "53225144",
"metadata": {},
"outputs": [],
"source": [
"p.o # the output file doesn't exist yet"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "863b9817",
"metadata": {},
"outputs": [],
"source": [
"with open(p.o, 'w') as f:\n",
" f.write('Param is awesome!')\n",
"\n",
"p.o # it now exists, getting its value resolve the full file path"
]
},
{
"cell_type": "markdown",
"id": "573079ef",
Expand Down
44 changes: 36 additions & 8 deletions param/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2315,12 +2315,11 @@ def __call__(self,path="",**params):


class Path(Parameter):
"""
Parameter that can be set to a string specifying the path of a file or folder.
"""Parameter that can be set to a string specifying the path of a file or folder.
The string should be specified in UNIX style, but it will be
returned in the format of the user's operating system. Please use
the Filename or Foldername classes if you require discrimination
the Filename or Foldername Parameters if you require discrimination
between the two possibilities.
The specified path can be absolute, or relative to either:
Expand All @@ -2332,26 +2331,42 @@ class Path(Parameter):
* any of the paths searched by resolve_path() (if search_paths
is None).
Parameters
----------
search_paths : list, default=[os.getcwd()]
List of paths to search the path from
check_exists: boolean, default=True
If True (default) the path must exist on instantiation and set,
otherwise the path can optionally exist.
"""

__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
if check_exists is not Undefined and not isinstance(check_exists, bool):
raise ValueError("'check_exists' attribute value must be a boolean")
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 @@ -2361,17 +2376,30 @@ def _validate(self, val):
if not self.allow_None:
raise ValueError(f'{_validate_error_prefix(self)} does not accept None')
else:
if not isinstance(val, (str, pathlib.Path)):
raise ValueError(f'{_validate_error_prefix(self)} only take str or pathlib.Path types')
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:
raise
else:
path = raw_path
return path

def __getstate__(self):
# don't want to pickle the search_paths
Expand Down
Loading

0 comments on commit f5208aa

Please sign in to comment.