diff --git a/examples/user_guide/Parameter_Types.ipynb b/examples/user_guide/Parameter_Types.ipynb index fd2ba8a49..8310edd36 100644 --- a/examples/user_guide/Parameter_Types.ipynb +++ b/examples/user_guide/Parameter_Types.ipynb @@ -31,6 +31,7 @@ "- [Path](#paths): A POSIX-style string specifying the location of a local file or folder\n", " * [Filename](#paths): A POSIX-style string specifying the location of a local file\n", " * [Foldername](#paths): A POSIX-style string specifying the location of a local folder\n", + " - [Foldernames](#paths): A POSIX-style string or a list of such strings specifying the location of one or more folder(s)\n", "- [SelectorBase](#selectors): Abstract superclass covering various selector parameter types\n", " * [Selector](#selectors): One object selected out of a provided ordered list of objects\n", " - [FileSelector](#selectors): One filename selected out of those matching a provided glob\n", @@ -647,12 +648,13 @@ "- `param.Path`: A POSIX-style string specifying the location of a local file or folder\n", "- `param.Filename`: A POSIX-style string specifying the location of a local file\n", "- `param.Foldername`: A POSIX-style string specifying the location of a local folder\n", + "- `param.Foldernames`: A POSIX-style string or a list of such strings specifying the location of one or more folder(s)\n", "\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", "\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." + "Either a file or a folder name is accepted by `param.Path`, while `param.Filename` accepts only file names, and `param.Foldername` and `param.Foldernames` accept only folder names." ] }, { @@ -665,6 +667,7 @@ " p = param.Path('Parameter_Types.ipynb')\n", " f = param.Filename('Parameter_Types.ipynb')\n", " d = param.Foldername('lib', search_paths=['/','/usr','/share'])\n", + " ds = param.Foldernames(['lib', 'opt'], search_paths=['/','/usr','/share'])\n", " \n", "p = P()\n", "p.p" @@ -718,6 +721,35 @@ " p.d='Parameter_Types.ipynb'" ] }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "p.ds" + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "p.ds = '/lib'\n", + "p.ds" + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "with param.exceptions_summarized():\n", + " p.ds='Parameter_Types.ipynb'" + ], + "metadata": {} + }, { "cell_type": "markdown", "metadata": {}, diff --git a/param/__init__.py b/param/__init__.py index 7b212e2f0..88ad3b248 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -17,6 +17,7 @@ Parameters and Parameterized classes. """ +import pathlib import os.path import sys import copy @@ -1817,6 +1818,55 @@ def _resolve(self, path): return resolve_path(path, path_to_file=False, search_paths=self.search_paths) +class Foldernames(Foldername): + r""" + Parameter that can be set to a list of paths, where each path can be a pathlib.Path object + or a string specifying the path of a folder. For convenience, if a single string or + path is provided, it is first inserted into a single-item list. + + The string(s) should be specified in UNIX style, but they will be + returned in the format of the user's operating system. + + The specified path(s) can be absolute, or relative to either: + + * any of the paths specified in the search_paths attribute (if + search_paths is not None); + + or + + * any of the paths searched by resolve_dir_path() (if search_paths + is None). + """ + + @staticmethod + def _cast_to_list(obj): + """Insert a single folder name (str or pathlib.Path) into a one-item list.""" + if isinstance(obj, (str, pathlib.Path)): + return [obj] + else: + return obj # validation is deferred to _resolve() + + def __init__(self, default=None, search_paths=None, **params): + super(Foldernames, self).__init__(self._cast_to_list(default), search_paths, **params) + + def __set__(self, param_owner, obj): + super(Foldernames, self).__set__(param_owner, self._cast_to_list(obj)) + + def _resolve(self, paths): + """Resolve and validate each folder item. + + Parameters + ---------- + paths: list + list of folder paths, either as `str` or `pathlib.Path` objects + + Returns + ------- + list + If the object is valid, return the folder names a list of `str` objects + """ + return [super(Foldernames, self)._resolve(p) for p in paths] + def abbreviate_paths(pathspec,named_paths): """