From 27c413ad13bc093283bba903378179e6af9d84e0 Mon Sep 17 00:00:00 2001 From: Jose Borreguero Date: Fri, 16 Dec 2022 18:27:10 -0500 Subject: [PATCH 1/6] class Foldernames and user guide snippet Signed-off-by: Jose Borreguero --- examples/user_guide/Parameter_Types.ipynb | 34 ++++++++++++++++++++++- param/__init__.py | 32 +++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/examples/user_guide/Parameter_Types.ipynb b/examples/user_guide/Parameter_Types.ipynb index fd2ba8a49..1f982f126 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": [ + "with param.exceptions_summarized():\n", + " p.ds='Parameter_Types.ipynb'" + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "with param.exceptions_summarized():\n", + " p.ds = open(\"/tmp/not_a_folder.dat\", \"w\")" + ], + "metadata": {} + }, { "cell_type": "markdown", "metadata": {}, diff --git a/param/__init__.py b/param/__init__.py index 7b212e2f0..e825d9ee1 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -17,7 +17,9 @@ Parameters and Parameterized classes. """ +import typing import os.path +import pathlib import sys import copy import glob @@ -38,6 +40,8 @@ from collections import OrderedDict from numbers import Real +FlexPath = typing.Union[str, pathlib.Path, typing.List[str, pathlib.Path]] + # Determine up-to-date version information, if possible, but with a # safe fallback to ensure that this file and parameterized.py are the # only two required files. @@ -1817,6 +1821,34 @@ 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 string specifying the path of a folder, + a pathlib.Path object, or a list of such strings and/or pathlib.Path objects. + + 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). + """ + + def _resolve(self, paths: FlexPath): + if isinstance(paths, (str, pathlib.Path)): + return super()._resolve(paths) + elif isinstance(paths, (list, tuple)): + return [self._resolve(path) for path in paths] + else: + name = next(x for x in [self.name, self.label, "Foldernames parameter"] if x) + raise ValueError(f"{name} must be a string or a list of strings to one or more local folders") + def abbreviate_paths(pathspec,named_paths): """ From 266d484978f86ca1cf8829c2e29e63260d5beaed Mon Sep 17 00:00:00 2001 From: Jose Borreguero Date: Thu, 22 Dec 2022 21:31:45 -0500 Subject: [PATCH 2/6] insert single folders into a list Signed-off-by: Jose Borreguero --- examples/user_guide/Parameter_Types.ipynb | 6 ++-- param/__init__.py | 34 +++++++++++++++++------ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/examples/user_guide/Parameter_Types.ipynb b/examples/user_guide/Parameter_Types.ipynb index 1f982f126..8310edd36 100644 --- a/examples/user_guide/Parameter_Types.ipynb +++ b/examples/user_guide/Parameter_Types.ipynb @@ -735,8 +735,8 @@ "execution_count": null, "outputs": [], "source": [ - "with param.exceptions_summarized():\n", - " p.ds='Parameter_Types.ipynb'" + "p.ds = '/lib'\n", + "p.ds" ], "metadata": {} }, @@ -746,7 +746,7 @@ "outputs": [], "source": [ "with param.exceptions_summarized():\n", - " p.ds = open(\"/tmp/not_a_folder.dat\", \"w\")" + " p.ds='Parameter_Types.ipynb'" ], "metadata": {} }, diff --git a/param/__init__.py b/param/__init__.py index e825d9ee1..a67628cf2 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -40,7 +40,8 @@ from collections import OrderedDict from numbers import Real -FlexPath = typing.Union[str, pathlib.Path, typing.List[str, pathlib.Path]] +FlexPath = typing.Union[str, pathlib.Path] # a string or a Path object +FlexPaths = typing.Union[FlexPath, typing.List[FlexPath]] # one or a list of FLexPath # Determine up-to-date version information, if possible, but with a # safe fallback to ensure that this file and parameterized.py are the @@ -1826,6 +1827,9 @@ class Foldernames(Foldername): Parameter that can be set to a string specifying the path of a folder, a pathlib.Path object, or a list of such strings and/or pathlib.Path objects. + On accessing, the parameter always returns a list of paths, even for a single + folder. + The string(s) should be specified in UNIX style, but they will be returned in the format of the user's operating system. @@ -1840,14 +1844,28 @@ class Foldernames(Foldername): is None). """ - def _resolve(self, paths: FlexPath): - if isinstance(paths, (str, pathlib.Path)): - return super()._resolve(paths) - elif isinstance(paths, (list, tuple)): - return [self._resolve(path) for path in paths] + @staticmethod + def _cast_to_list(obj): + r"""Cast an iterable onto a list, or insert the non-iterable into a one-item list. + + Validation is deferred to _resolve(). + """ + if obj is None: + return obj + elif hasattr(obj, "__iter__") and not isinstance(obj, str): # store items in list + return list(obj) else: - name = next(x for x in [self.name, self.label, "Foldernames parameter"] if x) - raise ValueError(f"{name} must be a string or a list of strings to one or more local folders") + return [obj] # "my_input_dir" becomes ["my_input_dir"] + + 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: FlexPaths): + r"""Resolve and validate each folder item""" + return [super(Foldernames, self)._resolve(p) for p in paths] def abbreviate_paths(pathspec,named_paths): From b219a8b71c1ba8f3c2a670f88f84e3f76d441fcb Mon Sep 17 00:00:00 2001 From: Jose Borreguero Date: Sun, 8 Jan 2023 09:51:43 -0500 Subject: [PATCH 3/6] make clear that the object is a list of paths Signed-off-by: Jose Borreguero --- param/__init__.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/param/__init__.py b/param/__init__.py index a67628cf2..8d4917674 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -1824,11 +1824,9 @@ def _resolve(self, path): class Foldernames(Foldername): r""" - Parameter that can be set to a string specifying the path of a folder, - a pathlib.Path object, or a list of such strings and/or pathlib.Path objects. - - On accessing, the parameter always returns a list of paths, even for a single - folder. + 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. From 3408ef56e23b485cd1c650507b0493b86de0b2f9 Mon Sep 17 00:00:00 2001 From: Jose Borreguero Date: Sun, 8 Jan 2023 10:02:43 -0500 Subject: [PATCH 4/6] remove Python type hints Signed-off-by: Jose Borreguero --- param/__init__.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/param/__init__.py b/param/__init__.py index 8d4917674..b9e5505a8 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -17,9 +17,7 @@ Parameters and Parameterized classes. """ -import typing import os.path -import pathlib import sys import copy import glob @@ -40,9 +38,6 @@ from collections import OrderedDict from numbers import Real -FlexPath = typing.Union[str, pathlib.Path] # a string or a Path object -FlexPaths = typing.Union[FlexPath, typing.List[FlexPath]] # one or a list of FLexPath - # Determine up-to-date version information, if possible, but with a # safe fallback to ensure that this file and parameterized.py are the # only two required files. @@ -1861,7 +1856,7 @@ def __init__(self, default=None, search_paths=None, **params): def __set__(self, param_owner, obj): super(Foldernames, self).__set__(param_owner, self._cast_to_list(obj)) - def _resolve(self, paths: FlexPaths): + def _resolve(self): r"""Resolve and validate each folder item""" return [super(Foldernames, self)._resolve(p) for p in paths] From b5180ac24f55217eeb8c3cac4d1939585c6ecf24 Mon Sep 17 00:00:00 2001 From: Jose Borreguero Date: Sun, 8 Jan 2023 10:15:50 -0500 Subject: [PATCH 5/6] do not initialize on copy but pass the reference Signed-off-by: Jose Borreguero --- param/__init__.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/param/__init__.py b/param/__init__.py index b9e5505a8..cf660faf5 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 @@ -1839,16 +1840,13 @@ class Foldernames(Foldername): @staticmethod def _cast_to_list(obj): - r"""Cast an iterable onto a list, or insert the non-iterable into a one-item list. - - Validation is deferred to _resolve(). - """ + r"""Insert a single folder name (str or pathlib.Path) into a one-item list.""" if obj is None: return obj - elif hasattr(obj, "__iter__") and not isinstance(obj, str): # store items in list - return list(obj) + elif isinstance(obj, (str, pathlib.Path)): + return [obj] else: - return [obj] # "my_input_dir" becomes ["my_input_dir"] + 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) @@ -1856,8 +1854,19 @@ def __init__(self, default=None, search_paths=None, **params): def __set__(self, param_owner, obj): super(Foldernames, self).__set__(param_owner, self._cast_to_list(obj)) - def _resolve(self): - r"""Resolve and validate each folder item""" + def _resolve(self, paths): + r"""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] From e892426db707e495a32e1448b0f0c4f3b9ec0b5c Mon Sep 17 00:00:00 2001 From: Jose Borreguero Date: Mon, 9 Jan 2023 12:12:42 -0500 Subject: [PATCH 6/6] remove redundant conditional Signed-off-by: Jose Borreguero --- param/__init__.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/param/__init__.py b/param/__init__.py index cf660faf5..88ad3b248 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -1840,10 +1840,8 @@ class Foldernames(Foldername): @staticmethod def _cast_to_list(obj): - r"""Insert a single folder name (str or pathlib.Path) into a one-item list.""" - if obj is None: - return obj - elif isinstance(obj, (str, pathlib.Path)): + """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() @@ -1855,7 +1853,7 @@ def __set__(self, param_owner, obj): super(Foldernames, self).__set__(param_owner, self._cast_to_list(obj)) def _resolve(self, paths): - r"""Resolve and validate each folder item. + """Resolve and validate each folder item. Parameters ----------