scikit-base 0.8.1__tar.gz → 0.8.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. {scikit_base-0.8.1/scikit_base.egg-info → scikit_base-0.8.2}/PKG-INFO +4 -4
  2. {scikit_base-0.8.1 → scikit_base-0.8.2}/README.md +1 -1
  3. {scikit_base-0.8.1 → scikit_base-0.8.2}/pyproject.toml +3 -3
  4. {scikit_base-0.8.1 → scikit_base-0.8.2/scikit_base.egg-info}/PKG-INFO +4 -4
  5. {scikit_base-0.8.1 → scikit_base-0.8.2}/scikit_base.egg-info/requires.txt +2 -2
  6. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/__init__.py +1 -1
  7. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/tests/conftest.py +5 -0
  8. scikit_base-0.8.2/skbase/utils/dependencies/_dependencies.py +542 -0
  9. scikit_base-0.8.1/skbase/utils/dependencies/_dependencies.py +0 -341
  10. {scikit_base-0.8.1 → scikit_base-0.8.2}/LICENSE +0 -0
  11. {scikit_base-0.8.1 → scikit_base-0.8.2}/docs/source/conf.py +0 -0
  12. {scikit_base-0.8.1 → scikit_base-0.8.2}/scikit_base.egg-info/SOURCES.txt +0 -0
  13. {scikit_base-0.8.1 → scikit_base-0.8.2}/scikit_base.egg-info/dependency_links.txt +0 -0
  14. {scikit_base-0.8.1 → scikit_base-0.8.2}/scikit_base.egg-info/top_level.txt +0 -0
  15. {scikit_base-0.8.1 → scikit_base-0.8.2}/scikit_base.egg-info/zip-safe +0 -0
  16. {scikit_base-0.8.1 → scikit_base-0.8.2}/setup.cfg +0 -0
  17. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/_exceptions.py +0 -0
  18. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/_nopytest_tests.py +0 -0
  19. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/base/__init__.py +0 -0
  20. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/base/_base.py +0 -0
  21. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/base/_meta.py +0 -0
  22. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/base/_pretty_printing/__init__.py +0 -0
  23. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/base/_pretty_printing/_object_html_repr.py +0 -0
  24. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/base/_pretty_printing/_pprint.py +0 -0
  25. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/base/_pretty_printing/tests/__init__.py +0 -0
  26. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/base/_pretty_printing/tests/test_pprint.py +0 -0
  27. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/base/_tagmanager.py +0 -0
  28. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/lookup/__init__.py +0 -0
  29. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/lookup/_lookup.py +0 -0
  30. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/lookup/tests/__init__.py +0 -0
  31. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/lookup/tests/test_lookup.py +0 -0
  32. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/testing/__init__.py +0 -0
  33. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/testing/test_all_objects.py +0 -0
  34. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/testing/utils/__init__.py +0 -0
  35. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/testing/utils/_conditional_fixtures.py +0 -0
  36. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/testing/utils/inspect.py +0 -0
  37. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/tests/__init__.py +0 -0
  38. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/tests/mock_package/__init__.py +0 -0
  39. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/tests/mock_package/test_mock_package.py +0 -0
  40. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/tests/test_base.py +0 -0
  41. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/tests/test_baseestimator.py +0 -0
  42. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/tests/test_exceptions.py +0 -0
  43. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/tests/test_meta.py +0 -0
  44. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/__init__.py +0 -0
  45. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/_check.py +0 -0
  46. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/_iter.py +0 -0
  47. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/_nested_iter.py +0 -0
  48. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/_utils.py +0 -0
  49. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/deep_equals/__init__.py +0 -0
  50. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/deep_equals/_common.py +0 -0
  51. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/deep_equals/_deep_equals.py +0 -0
  52. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/dependencies/__init__.py +0 -0
  53. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/dependencies/tests/__init__.py +0 -0
  54. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/dependencies/tests/test_check_dependencies.py +0 -0
  55. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/random_state.py +0 -0
  56. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/stdout_mute.py +0 -0
  57. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/tests/__init__.py +0 -0
  58. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/tests/test_check.py +0 -0
  59. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/tests/test_deep_equals.py +0 -0
  60. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/tests/test_iter.py +0 -0
  61. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/tests/test_nested_iter.py +0 -0
  62. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/tests/test_random_state.py +0 -0
  63. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/utils/tests/test_utils.py +0 -0
  64. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/validate/__init__.py +0 -0
  65. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/validate/_named_objects.py +0 -0
  66. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/validate/_types.py +0 -0
  67. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/validate/tests/__init__.py +0 -0
  68. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/validate/tests/test_iterable_named_objects.py +0 -0
  69. {scikit_base-0.8.1 → scikit_base-0.8.2}/skbase/validate/tests/test_type_validations.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scikit-base
3
- Version: 0.8.1
3
+ Version: 0.8.2
4
4
  Summary: Base classes for sklearn-like parametric objects
5
5
  Author-email: sktime developers <sktime.toolbox@gmail.com>
6
6
  Maintainer: Franz Király
@@ -90,10 +90,10 @@ Requires-Dist: nbsphinx>=0.8.6; extra == "docs"
90
90
  Requires-Dist: numpydoc; extra == "docs"
91
91
  Requires-Dist: pydata-sphinx-theme; extra == "docs"
92
92
  Requires-Dist: sphinx-issues<5.0.0; extra == "docs"
93
- Requires-Dist: sphinx-gallery<0.17.0; extra == "docs"
93
+ Requires-Dist: sphinx-gallery<0.18.0; extra == "docs"
94
94
  Requires-Dist: sphinx-panels; extra == "docs"
95
95
  Requires-Dist: sphinx-design<0.7.0; extra == "docs"
96
- Requires-Dist: Sphinx!=7.2.0,<8.0.0; extra == "docs"
96
+ Requires-Dist: Sphinx!=7.2.0,<9.0.0; extra == "docs"
97
97
  Requires-Dist: tabulate; extra == "docs"
98
98
  Provides-Extra: test
99
99
  Requires-Dist: pytest; extra == "test"
@@ -114,7 +114,7 @@ Requires-Dist: scikit-learn>=0.24.0; extra == "test"
114
114
  `skbase` provides base classes for creating scikit-learn-like parametric objects,
115
115
  along with tools to make it easier to build your own packages that follow these design patterns.
116
116
 
117
- :rocket: Version 0.8.1 is now available. Check out our
117
+ :rocket: Version 0.8.2 is now available. Check out our
118
118
  [release notes](https://skbase.readthedocs.io/en/latest/changelog.html).
119
119
 
120
120
  | Overview | |
@@ -7,7 +7,7 @@
7
7
  `skbase` provides base classes for creating scikit-learn-like parametric objects,
8
8
  along with tools to make it easier to build your own packages that follow these design patterns.
9
9
 
10
- :rocket: Version 0.8.1 is now available. Check out our
10
+ :rocket: Version 0.8.2 is now available. Check out our
11
11
  [release notes](https://skbase.readthedocs.io/en/latest/changelog.html).
12
12
 
13
13
  | Overview | |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "scikit-base"
3
- version = "0.8.1"
3
+ version = "0.8.2"
4
4
  description = "Base classes for sklearn-like parametric objects"
5
5
  authors = [
6
6
  {name = "sktime developers", email = "sktime.toolbox@gmail.com"},
@@ -71,10 +71,10 @@ docs = [
71
71
  "numpydoc",
72
72
  "pydata-sphinx-theme",
73
73
  "sphinx-issues<5.0.0",
74
- "sphinx-gallery<0.17.0",
74
+ "sphinx-gallery<0.18.0",
75
75
  "sphinx-panels",
76
76
  "sphinx-design<0.7.0",
77
- "Sphinx<8.0.0,!=7.2.0",
77
+ "Sphinx!=7.2.0,<9.0.0",
78
78
  "tabulate",
79
79
  ]
80
80
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scikit-base
3
- Version: 0.8.1
3
+ Version: 0.8.2
4
4
  Summary: Base classes for sklearn-like parametric objects
5
5
  Author-email: sktime developers <sktime.toolbox@gmail.com>
6
6
  Maintainer: Franz Király
@@ -90,10 +90,10 @@ Requires-Dist: nbsphinx>=0.8.6; extra == "docs"
90
90
  Requires-Dist: numpydoc; extra == "docs"
91
91
  Requires-Dist: pydata-sphinx-theme; extra == "docs"
92
92
  Requires-Dist: sphinx-issues<5.0.0; extra == "docs"
93
- Requires-Dist: sphinx-gallery<0.17.0; extra == "docs"
93
+ Requires-Dist: sphinx-gallery<0.18.0; extra == "docs"
94
94
  Requires-Dist: sphinx-panels; extra == "docs"
95
95
  Requires-Dist: sphinx-design<0.7.0; extra == "docs"
96
- Requires-Dist: Sphinx!=7.2.0,<8.0.0; extra == "docs"
96
+ Requires-Dist: Sphinx!=7.2.0,<9.0.0; extra == "docs"
97
97
  Requires-Dist: tabulate; extra == "docs"
98
98
  Provides-Extra: test
99
99
  Requires-Dist: pytest; extra == "test"
@@ -114,7 +114,7 @@ Requires-Dist: scikit-learn>=0.24.0; extra == "test"
114
114
  `skbase` provides base classes for creating scikit-learn-like parametric objects,
115
115
  along with tools to make it easier to build your own packages that follow these design patterns.
116
116
 
117
- :rocket: Version 0.8.1 is now available. Check out our
117
+ :rocket: Version 0.8.2 is now available. Check out our
118
118
  [release notes](https://skbase.readthedocs.io/en/latest/changelog.html).
119
119
 
120
120
  | Overview | |
@@ -19,10 +19,10 @@ nbsphinx>=0.8.6
19
19
  numpydoc
20
20
  pydata-sphinx-theme
21
21
  sphinx-issues<5.0.0
22
- sphinx-gallery<0.17.0
22
+ sphinx-gallery<0.18.0
23
23
  sphinx-panels
24
24
  sphinx-design<0.7.0
25
- Sphinx!=7.2.0,<8.0.0
25
+ Sphinx!=7.2.0,<9.0.0
26
26
  tabulate
27
27
 
28
28
  [linters]
@@ -6,4 +6,4 @@
6
6
  The included functionality makes it easy to reuse scikit-learn and
7
7
  sktime design principles in your project.
8
8
  """
9
- __version__: str = "0.8.1"
9
+ __version__: str = "0.8.2"
@@ -251,7 +251,12 @@ SKBASE_FUNCTIONS_BY_MODULE.update(
251
251
  "skbase.utils.dependencies._dependencies": (
252
252
  "_check_soft_dependencies",
253
253
  "_check_python_version",
254
+ "_check_env_marker",
254
255
  "_check_estimator_deps",
256
+ "_get_pkg_version",
257
+ "_get_installed_packages",
258
+ "_normalize_requirement",
259
+ "_raise_at_severity",
255
260
  ),
256
261
  "skbase.utils.random_state": (
257
262
  "check_random_state",
@@ -0,0 +1,542 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Utility to check soft dependency imports, and raise warnings or errors."""
3
+ import sys
4
+ import warnings
5
+ from functools import lru_cache
6
+ from importlib.metadata import distributions
7
+ from inspect import isclass
8
+
9
+ from packaging.markers import InvalidMarker, Marker
10
+ from packaging.requirements import InvalidRequirement, Requirement
11
+ from packaging.specifiers import InvalidSpecifier, Specifier, SpecifierSet
12
+ from packaging.version import InvalidVersion, Version
13
+
14
+
15
+ # todo 0.10.0: remove suppress_import_stdout argument
16
+ def _check_soft_dependencies(
17
+ *packages,
18
+ package_import_alias="deprecated",
19
+ severity="error",
20
+ obj=None,
21
+ msg=None,
22
+ suppress_import_stdout="deprecated",
23
+ ):
24
+ """Check if required soft dependencies are installed and raise error or warning.
25
+
26
+ Parameters
27
+ ----------
28
+ packages : str or list/tuple of str, or length-1-tuple containing list/tuple of str
29
+ str should be package names and/or package version specifications to check.
30
+ Each str must be a PEP 440 compatible specifier string, for a single package.
31
+ For instance, the PEP 440 compatible package name such as ``"pandas"``;
32
+ or a package requirement specifier string such as ``"pandas>1.2.3"``.
33
+ arg can be str, kwargs tuple, or tuple/list of str, following calls are valid:
34
+ ``_check_soft_dependencies("package1")``
35
+ ``_check_soft_dependencies("package1", "package2")``
36
+ ``_check_soft_dependencies(("package1", "package2"))``
37
+ ``_check_soft_dependencies(["package1", "package2"])``
38
+
39
+ package_import_alias : ignored, present only for backwards compatibility
40
+
41
+ severity : str, "error" (default), "warning", "none"
42
+ whether the check should raise an error, a warning, or nothing
43
+
44
+ * "error" - raises a ``ModuleNotFoundError`` if one of packages is not installed
45
+ * "warning" - raises a warning if one of packages is not installed
46
+ function returns False if one of packages is not installed, otherwise True
47
+ * "none" - does not raise exception or warning
48
+ function returns False if one of packages is not installed, otherwise True
49
+
50
+ obj : python class, object, str, or None, default=None
51
+ if self is passed here when _check_soft_dependencies is called within __init__,
52
+ or a class is passed when it is called at the start of a single-class module,
53
+ the error message is more informative and will refer to the class/object;
54
+ if str is passed, will be used as name of the class/object or module
55
+
56
+ msg : str, or None, default=None
57
+ if str, will override the error message or warning shown with msg
58
+
59
+ Raises
60
+ ------
61
+ InvalidRequirement
62
+ if package requirement strings are not PEP 440 compatible
63
+ ModuleNotFoundError
64
+ error with informative message, asking to install required soft dependencies
65
+ TypeError, ValueError
66
+ on invalid arguments
67
+
68
+ Returns
69
+ -------
70
+ boolean - whether all packages are installed, only if no exception is raised
71
+ """
72
+ # todo 0.10.0: remove this warning
73
+ if suppress_import_stdout != "deprecated":
74
+ warnings.warn(
75
+ "In skbase _check_soft_dependencies, the suppress_import_stdout argument "
76
+ "is deprecated and no longer has any effect. "
77
+ "The argument will be removed in version 0.10.0, so users of the "
78
+ "_check_soft_dependencies utility should not pass this argument anymore. "
79
+ "The _check_soft_dependencies utility also no longer causes imports, "
80
+ "hence no stdout "
81
+ "output is created from imports, for any setting of the "
82
+ "suppress_import_stdout argument. If you wish to import packages "
83
+ "and make use of stdout prints, import the package directly instead.",
84
+ DeprecationWarning,
85
+ stacklevel=2,
86
+ )
87
+
88
+ if len(packages) == 1 and isinstance(packages[0], (tuple, list)):
89
+ packages = packages[0]
90
+ if not all(isinstance(x, str) for x in packages):
91
+ raise TypeError(
92
+ "packages argument of _check_soft_dependencies must be str or tuple of "
93
+ f"str, but found packages argument of type {type(packages)}"
94
+ )
95
+
96
+ if obj is None:
97
+ class_name = "This functionality"
98
+ elif not isclass(obj):
99
+ class_name = type(obj).__name__
100
+ elif isclass(obj):
101
+ class_name = obj.__name__
102
+ elif isinstance(obj, str):
103
+ class_name = obj
104
+ else:
105
+ raise TypeError(
106
+ "obj argument of _check_soft_dependencies must be a class, an object,"
107
+ " a str, or None, but found obj of type"
108
+ f" {type(obj)}"
109
+ )
110
+
111
+ if msg is not None and not isinstance(msg, str):
112
+ raise TypeError(
113
+ "msg argument of _check_soft_dependencies must be a str, "
114
+ f"or None, but found msg of type {type(msg)}"
115
+ )
116
+
117
+ for package in packages:
118
+ try:
119
+ req = Requirement(package)
120
+ req = _normalize_requirement(req)
121
+ except InvalidRequirement:
122
+ msg_version = (
123
+ f"wrong format for package requirement string, "
124
+ f"passed via packages argument of _check_soft_dependencies, "
125
+ f'must be PEP 440 compatible requirement string, e.g., "pandas"'
126
+ f' or "pandas>1.1", but found {package!r}'
127
+ )
128
+ raise InvalidRequirement(msg_version) from None
129
+
130
+ package_name = req.name
131
+ package_version_req = req.specifier
132
+
133
+ pkg_env_version = _get_pkg_version(package_name)
134
+
135
+ # if package not present, make the user aware of installation reqs
136
+ if pkg_env_version is None:
137
+ if obj is None and msg is None:
138
+ msg = (
139
+ f"{class_name} requires package {package!r} to be present "
140
+ f"in the python environment, but {package!r} was not found. "
141
+ )
142
+ elif msg is None: # obj is not None, msg is None
143
+ msg = (
144
+ f"{class_name} requires package {package!r} to be present "
145
+ f"in the python environment, but {package!r} was not found. "
146
+ f"{package!r} is a dependency of {class_name} and required "
147
+ f"to construct it. "
148
+ )
149
+ msg = msg + (
150
+ f"Please run: `pip install {package}` to "
151
+ f"install the {package} package. "
152
+ )
153
+ # if msg is not None, none of the above is executed,
154
+ # so if msg is passed it overrides the default messages
155
+
156
+ _raise_at_severity(msg, severity, caller="_check_soft_dependencies")
157
+ return False
158
+
159
+ # now we check compatibility with the version specifier if non-empty
160
+ if package_version_req != SpecifierSet(""):
161
+ msg = (
162
+ f"{class_name} requires package {package!r} to be present "
163
+ f"in the python environment, with version {package_version_req}, "
164
+ f"but incompatible version {pkg_env_version} was found. "
165
+ )
166
+ if obj is not None:
167
+ msg = msg + (
168
+ f"{package!r}, with version {package_version_req},"
169
+ f"is a dependency of {class_name} and required to construct it. "
170
+ )
171
+
172
+ # raise error/warning or return False if version is incompatible
173
+ if pkg_env_version not in package_version_req:
174
+ _raise_at_severity(msg, severity, caller="_check_soft_dependencies")
175
+ return False
176
+
177
+ # if package can be imported and no version issue was caught for any string,
178
+ # then obj is compatible with the requirements and we should return True
179
+ return True
180
+
181
+
182
+ @lru_cache
183
+ def _get_installed_packages_private():
184
+ """Get a dictionary of installed packages and their versions.
185
+
186
+ Same as _get_installed_packages, but internal to avoid mutating the lru_cache
187
+ by accident.
188
+ """
189
+ dists = distributions()
190
+ packages = {dist.metadata["Name"]: dist.version for dist in dists}
191
+ return packages
192
+
193
+
194
+ def _get_installed_packages():
195
+ """Get a dictionary of installed packages and their versions.
196
+
197
+ Returns
198
+ -------
199
+ dict : dictionary of installed packages and their versions
200
+ keys are PEP 440 compatible package names, values are package versions
201
+ MAJOR.MINOR.PATCH version format is used for versions, e.g., "1.2.3"
202
+ """
203
+ return _get_installed_packages_private().copy()
204
+
205
+
206
+ def _get_pkg_version(package_name):
207
+ """Check whether package is available in environment, and return its version if yes.
208
+
209
+ Returns ``Version`` object from ``lru_cache``, this should not be mutated.
210
+
211
+ Parameters
212
+ ----------
213
+ package_name : str, optional, default=None
214
+ name of package to check,
215
+ PEP 440 compatibe specifier string, e.g., "pandas" or "sklearn".
216
+ This is the pypi package name, not the import name, e.g.,
217
+ ``scikit-learn``, not ``sklearn``.
218
+
219
+ Returns
220
+ -------
221
+ None, if package is not found in python environment.
222
+ ``importlib`` ``Version`` of package, if present in environment.
223
+ """
224
+ pkgs = _get_installed_packages()
225
+ pkg_vers_str = pkgs.get(package_name, None)
226
+ if pkg_vers_str is None:
227
+ return None
228
+ try:
229
+ pkg_env_version = Version(pkg_vers_str)
230
+ except InvalidVersion:
231
+ pkg_env_version = None
232
+ return pkg_env_version
233
+
234
+
235
+ def _check_python_version(obj, package=None, msg=None, severity="error"):
236
+ """Check if system python version is compatible with requirements of obj.
237
+
238
+ Parameters
239
+ ----------
240
+ obj : BaseObject descendant
241
+ used to check python version
242
+
243
+ package : str, default = None
244
+ if given, will be used in error message as package name
245
+
246
+ msg : str, optional, default = default message (msg below)
247
+ error message to be returned in the ``ModuleNotFoundError``, overrides default
248
+
249
+ severity : str, "error" (default), "warning", "none"
250
+ whether the check should raise an error, a warning, or nothing
251
+
252
+ * "error" - raises a ``ModuleNotFoundError`` if one of packages is not installed
253
+ * "warning" - raises a warning if one of packages is not installed
254
+ function returns False if one of packages is not installed, otherwise True
255
+ * "none" - does not raise exception or warning
256
+ function returns False if one of packages is not installed, otherwise True
257
+
258
+ Returns
259
+ -------
260
+ compatible : bool, whether obj is compatible with system python version
261
+ check is using the python_version tag of obj
262
+
263
+ Raises
264
+ ------
265
+ ModuleNotFoundError
266
+ User friendly error if obj has python_version tag that is
267
+ incompatible with the system python version. If package is given,
268
+ error message gives package as the reason for incompatibility.
269
+ """
270
+ est_specifier_tag = obj.get_class_tag("python_version", tag_value_default="None")
271
+ if est_specifier_tag in ["None", None]:
272
+ return True
273
+
274
+ try:
275
+ est_specifier = SpecifierSet(est_specifier_tag)
276
+ except InvalidSpecifier:
277
+ msg_version = (
278
+ f"wrong format for python_version tag, "
279
+ f'must be PEP 440 compatible specifier string, e.g., "<3.9, >= 3.6.3",'
280
+ f" but found {est_specifier_tag!r}"
281
+ )
282
+ raise InvalidSpecifier(msg_version) from None
283
+
284
+ # python sys version, e.g., "3.8.12"
285
+ sys_version = sys.version.split(" ")[0]
286
+
287
+ if sys_version in est_specifier:
288
+ return True
289
+ # now we know that est_version is not compatible with sys_version
290
+
291
+ if isclass(obj):
292
+ class_name = obj.__name__
293
+ else:
294
+ class_name = type(obj).__name__
295
+
296
+ if not isinstance(msg, str):
297
+ msg = (
298
+ f"{class_name} requires python version to be {est_specifier},"
299
+ f" but system python version is {sys.version}."
300
+ )
301
+
302
+ if package is not None:
303
+ msg += (
304
+ f" This is due to python version requirements of the {package} package."
305
+ )
306
+
307
+ _raise_at_severity(msg, severity, caller="_check_python_version")
308
+ return False
309
+
310
+
311
+ def _check_env_marker(obj, package=None, msg=None, severity="error"):
312
+ """Check if packaging marker tag is with requirements of obj.
313
+
314
+ Parameters
315
+ ----------
316
+ obj : BaseObject descendant
317
+ used to check python version
318
+ package : str, default = None
319
+ if given, will be used in error message as package name
320
+ msg : str, optional, default = default message (msg below)
321
+ error message to be returned in the `ModuleNotFoundError`, overrides default
322
+
323
+ severity : str, "error" (default), "warning", "none"
324
+ whether the check should raise an error, a warning, or nothing
325
+
326
+ * "error" - raises a ``ModuleNotFoundError`` if one of packages is not installed
327
+ * "warning" - raises a warning if one of packages is not installed
328
+ function returns False if one of packages is not installed, otherwise True
329
+ * "none" - does not raise exception or warning
330
+ function returns False if one of packages is not installed, otherwise True
331
+
332
+ Returns
333
+ -------
334
+ compatible : bool, whether obj is compatible with system python version
335
+ check is using the python_version tag of obj
336
+
337
+ Raises
338
+ ------
339
+ InvalidMarker
340
+ User friendly error if obj has env_marker tag that is not a
341
+ packaging compatible marker string
342
+ ModuleNotFoundError
343
+ User friendly error if obj has an env_marker tag that is
344
+ incompatible with the python environment. If package is given,
345
+ error message gives package as the reason for incompatibility.
346
+ """
347
+ est_marker_tag = obj.get_class_tag("env_marker", tag_value_default="None")
348
+ if est_marker_tag in ["None", None]:
349
+ return True
350
+
351
+ try:
352
+ est_marker = Marker(est_marker_tag)
353
+ except InvalidMarker:
354
+ msg_version = (
355
+ f"wrong format for env_marker tag, "
356
+ f"must be PEP 508 compatible specifier string, e.g., "
357
+ f'platform_system!="windows", but found {est_marker_tag!r}'
358
+ )
359
+ raise InvalidMarker(msg_version) from None
360
+
361
+ if est_marker.evaluate():
362
+ return True
363
+ # now we know that est_marker is not compatible with the environment
364
+
365
+ if isclass(obj):
366
+ class_name = obj.__name__
367
+ else:
368
+ class_name = type(obj).__name__
369
+
370
+ if not isinstance(msg, str):
371
+ msg = (
372
+ f"{class_name} requires an environment to satisfy "
373
+ f"packaging marker spec {est_marker}, but environment does not satisfy it."
374
+ )
375
+
376
+ if package is not None:
377
+ msg += f" This is due to requirements of the {package} package."
378
+
379
+ _raise_at_severity(msg, severity, caller="_check_env_marker")
380
+ return False
381
+
382
+
383
+ def _check_estimator_deps(obj, msg=None, severity="error"):
384
+ """Check if object/estimator's package & python requirements are met by python env.
385
+
386
+ Convenience wrapper around `_check_python_version` and `_check_soft_dependencies`,
387
+ checking against estimator tags `"python_version"`, `"python_dependencies"`.
388
+
389
+ Checks whether dependency requirements of `BaseObject`-s in `obj`
390
+ are satisfied by the current python environment.
391
+
392
+ Parameters
393
+ ----------
394
+ obj : BaseObject descendant, instance or class, or list/tuple thereof
395
+ object(s) that this function checks compatibility of, with the python env
396
+
397
+ msg : str, optional, default = default message (msg below)
398
+ error message to be returned in the ``ModuleNotFoundError``, overrides default
399
+
400
+ severity : str, "error" (default), "warning", "none"
401
+ whether the check should raise an error, a warning, or nothing
402
+
403
+ * "error" - raises a ``ModuleNotFoundError`` if one of packages is not installed
404
+ * "warning" - raises a warning if one of packages is not installed
405
+ function returns False if one of packages is not installed, otherwise True
406
+ * "none" - does not raise exception or warning
407
+ function returns False if one of packages is not installed, otherwise True
408
+
409
+ Returns
410
+ -------
411
+ compatible : bool, whether `obj` is compatible with python environment
412
+ False is returned only if no exception is raised by the function
413
+ checks for python version using the python_version tag of obj
414
+ checks for soft dependencies present using the python_dependencies tag of obj
415
+ if `obj` contains multiple `BaseObject`-s, checks whether all are compatible
416
+
417
+ Raises
418
+ ------
419
+ ModuleNotFoundError
420
+ User friendly error if obj has python_version tag that is
421
+ incompatible with the system python version.
422
+ Compatible python versions are determined by the "python_version" tag of obj.
423
+ User friendly error if obj has package dependencies that are not satisfied.
424
+ Packages are determined based on the "python_dependencies" tag of obj.
425
+ """
426
+ compatible = True
427
+
428
+ # if list or tuple, recurse & iterate over element, and return conjunction
429
+ if isinstance(obj, (list, tuple)):
430
+ for x in obj:
431
+ x_chk = _check_estimator_deps(x, msg=msg, severity=severity)
432
+ compatible = compatible and x_chk
433
+ return compatible
434
+
435
+ compatible = compatible and _check_python_version(obj, severity=severity)
436
+ compatible = compatible and _check_env_marker(obj, severity=severity)
437
+
438
+ pkg_deps = obj.get_class_tag("python_dependencies", None)
439
+ pck_alias = obj.get_class_tag("python_dependencies_alias", None)
440
+ if pkg_deps is not None and not isinstance(pkg_deps, list):
441
+ pkg_deps = [pkg_deps]
442
+ if pkg_deps is not None:
443
+ pkg_deps_ok = _check_soft_dependencies(
444
+ *pkg_deps, severity=severity, obj=obj, package_import_alias=pck_alias
445
+ )
446
+ compatible = compatible and pkg_deps_ok
447
+
448
+ return compatible
449
+
450
+
451
+ def _normalize_requirement(req):
452
+ """Normalize packaging Requirement by removing build metadata from versions.
453
+
454
+ Parameters
455
+ ----------
456
+ req : packaging.requirements.Requirement
457
+ requirement string to normalize, e.g., Requirement("pandas>1.2.3+foobar")
458
+
459
+ Returns
460
+ -------
461
+ normalized_req : packaging.requirements.Requirement
462
+ normalized requirement object with build metadata removed from versions,
463
+ e.g., Requirement("pandas>1.2.3")
464
+ """
465
+ # Process each specifier in the requirement
466
+ normalized_specs = []
467
+ for spec in req.specifier:
468
+ # Parse the version and remove the build metadata
469
+ spec_v = Version(spec.version)
470
+ version_wo_build_metadata = f"{spec_v.major}.{spec_v.minor}.{spec_v.micro}"
471
+
472
+ # Create a new specifier without the build metadata
473
+ normalized_spec = Specifier(f"{spec.operator}{version_wo_build_metadata}")
474
+ normalized_specs.append(normalized_spec)
475
+
476
+ # Reconstruct the specifier set
477
+ normalized_specifier_set = SpecifierSet(",".join(str(s) for s in normalized_specs))
478
+
479
+ # Create a new Requirement object with the normalized specifiers
480
+ normalized_req = Requirement(f"{req.name}{normalized_specifier_set}")
481
+
482
+ return normalized_req
483
+
484
+
485
+ def _raise_at_severity(
486
+ msg,
487
+ severity,
488
+ exception_type=None,
489
+ warning_type=None,
490
+ stacklevel=2,
491
+ caller="_raise_at_severity",
492
+ ):
493
+ """Raise exception or warning or take no action, based on severity.
494
+
495
+ Parameters
496
+ ----------
497
+ msg : str
498
+ message to raise or warn
499
+
500
+ severity : str, "error" (default), "warning", "none"
501
+ whether the check should raise an error, a warning, or nothing
502
+
503
+ * "error" - raises a ``ModuleNotFoundError`` if one of packages is not installed
504
+ * "warning" - raises a warning if one of packages is not installed
505
+ function returns False if one of packages is not installed, otherwise True
506
+ * "none" - does not raise exception or warning
507
+ function returns False if one of packages is not installed, otherwise True
508
+
509
+ exception_type : Exception, default=ModuleNotFoundError
510
+ exception type to raise if severity="severity"
511
+ warning_type : warning, default=Warning
512
+ warning type to raise if severity="warning"
513
+ stacklevel : int, default=2
514
+ stacklevel for warnings, if severity="warning"
515
+ caller : str, default="_raise_at_severity"
516
+ caller name, used in exception if severity not in ["error", "warning", "none"]
517
+
518
+ Returns
519
+ -------
520
+ None
521
+
522
+ Raises
523
+ ------
524
+ exception : exception_type, if severity="error"
525
+ warning : warning+type, if severity="warning"
526
+ ValueError : if severity not in ["error", "warning", "none"]
527
+ """
528
+ if exception_type is None:
529
+ exception_type = ModuleNotFoundError
530
+
531
+ if severity == "error":
532
+ raise exception_type(msg)
533
+ elif severity == "warning":
534
+ warnings.warn(msg, category=warning_type, stacklevel=stacklevel)
535
+ elif severity == "none":
536
+ return None
537
+ else:
538
+ raise ValueError(
539
+ f"Error in calling {caller}, severity "
540
+ f'argument must be "error", "warning", or "none", found {severity!r}.'
541
+ )
542
+ return None
@@ -1,341 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """Utility to check soft dependency imports, and raise warnings or errors."""
3
- import sys
4
- import warnings
5
- from importlib import import_module
6
- from inspect import isclass
7
- from typing import List
8
-
9
- from packaging.requirements import InvalidRequirement, Requirement
10
- from packaging.specifiers import InvalidSpecifier, SpecifierSet
11
-
12
- from skbase.utils.stdout_mute import StdoutMute
13
-
14
- __author__: List[str] = ["fkiraly", "mloning"]
15
-
16
-
17
- def _check_soft_dependencies(
18
- *packages,
19
- package_import_alias=None,
20
- severity="error",
21
- obj=None,
22
- msg=None,
23
- suppress_import_stdout=False,
24
- ):
25
- """Check if required soft dependencies are installed and raise error or warning.
26
-
27
- Parameters
28
- ----------
29
- packages : str or list/tuple of str, or length-1-tuple containing list/tuple of str
30
- str should be package names and/or package version specifications to check.
31
- Each str must be a PEP 440 compatible specifier string, for a single package.
32
- For instance, the PEP 440 compatible package name such as "pandas";
33
- or a package requirement specifier string such as "pandas>1.2.3".
34
- arg can be str, kwargs tuple, or tuple/list of str, following calls are valid:
35
- `_check_soft_dependencies("package1")`
36
- `_check_soft_dependencies("package1", "package2")`
37
- `_check_soft_dependencies(("package1", "package2"))`
38
- `_check_soft_dependencies(["package1", "package2"])`
39
- package_import_alias : dict with str keys and values, optional, default=empty
40
- key-value pairs are package name, import name
41
- import name is str used in python import, i.e., from import_name import ...
42
- should be provided if import name differs from package name
43
- severity : str, "error" (default), "warning", "none"
44
- behaviour for raising errors or warnings
45
- "error" - raises a `ModuleNotFoundError` if one of packages is not installed
46
- "warning" - raises a warning if one of packages is not installed
47
- function returns False if one of packages is not installed, otherwise True
48
- "none" - does not raise exception or warning
49
- function returns False if one of packages is not installed, otherwise True
50
- obj : python class, object, str, or None, default=None
51
- if self is passed here when _check_soft_dependencies is called within __init__,
52
- or a class is passed when it is called at the start of a single-class module,
53
- the error message is more informative and will refer to the class/object;
54
- if str is passed, will be used as name of the class/object or module
55
- msg : str, or None, default=None
56
- if str, will override the error message or warning shown with msg
57
- suppress_import_stdout : bool, optional. Default=False
58
- whether to suppress stdout printout upon import.
59
-
60
- Raises
61
- ------
62
- ModuleNotFoundError
63
- error with informative message, asking to install required soft dependencies
64
-
65
- Returns
66
- -------
67
- boolean - whether all packages are installed, only if no exception is raised
68
- """
69
- if len(packages) == 1 and isinstance(packages[0], (tuple, list)):
70
- packages = packages[0]
71
- if not all(isinstance(x, str) for x in packages):
72
- raise TypeError(
73
- "packages argument of _check_soft_dependencies must be str or tuple of "
74
- f"str, but found packages argument of type {type(packages)}"
75
- )
76
-
77
- if package_import_alias is None:
78
- package_import_alias = {}
79
- msg_pkg_import_alias = (
80
- "package_import_alias argument of _check_soft_dependencies must "
81
- "be a dict with str keys and values, but found "
82
- f"package_import_alias of type {type(package_import_alias)}"
83
- )
84
- if not isinstance(package_import_alias, dict):
85
- raise TypeError(msg_pkg_import_alias)
86
- if not all(isinstance(x, str) for x in package_import_alias.keys()):
87
- raise TypeError(msg_pkg_import_alias)
88
- if not all(isinstance(x, str) for x in package_import_alias.values()):
89
- raise TypeError(msg_pkg_import_alias)
90
-
91
- if obj is None:
92
- class_name = "This functionality"
93
- elif not isclass(obj):
94
- class_name = type(obj).__name__
95
- elif isclass(obj):
96
- class_name = obj.__name__
97
- elif isinstance(obj, str):
98
- class_name = obj
99
- else:
100
- raise TypeError(
101
- "obj argument of _check_soft_dependencies must be a class, an object,"
102
- " a str, or None, but found obj of type"
103
- f" {type(obj)}"
104
- )
105
-
106
- if msg is not None and not isinstance(msg, str):
107
- raise TypeError(
108
- "msg argument of _check_soft_dependencies must be a str, "
109
- f"or None, but found msg of type {type(msg)}"
110
- )
111
-
112
- for package in packages:
113
- try:
114
- req = Requirement(package)
115
- except InvalidRequirement:
116
- msg_version = (
117
- f"wrong format for package requirement string, "
118
- f"passed via packages argument of _check_soft_dependencies, "
119
- f'must be PEP 440 compatible requirement string, e.g., "pandas"'
120
- f' or "pandas>1.1", but found {package!r}'
121
- )
122
- raise InvalidRequirement(msg_version) from None
123
-
124
- package_name = req.name
125
- package_version_req = req.specifier
126
-
127
- # determine the package import
128
- if package_name in package_import_alias.keys():
129
- package_import_name = package_import_alias[package_name]
130
- else:
131
- package_import_name = package_name
132
- # attempt import - if not possible, we know we need to raise warning/exception
133
- try:
134
- with StdoutMute(active=suppress_import_stdout):
135
- pkg_ref = import_module(package_import_name)
136
- # if package cannot be imported, make the user aware of installation requirement
137
- except ModuleNotFoundError as e:
138
- if msg is None:
139
- msg = (
140
- f"{e}. "
141
- f"{class_name} requires package {package!r} to be present "
142
- f"in the python environment, but {package!r} was not found. "
143
- )
144
- if obj is not None:
145
- msg = msg + (
146
- f"{package!r} is a dependency of {class_name} and required "
147
- f"to construct it. "
148
- )
149
- msg = msg + (
150
- f"Please run: `pip install {package}` to "
151
- f"install the {package} package. "
152
- )
153
- # if msg is not None, none of the above is executed,
154
- # so if msg is passed it overrides the default messages
155
-
156
- if severity == "error":
157
- raise ModuleNotFoundError(msg) from e
158
- elif severity == "warning":
159
- warnings.warn(msg, stacklevel=2)
160
- return False
161
- elif severity == "none":
162
- return False
163
- else:
164
- raise RuntimeError(
165
- "Error in calling _check_soft_dependencies, severity "
166
- 'argument must be "error", "warning", or "none",'
167
- f"found {severity!r}."
168
- ) from e
169
-
170
- # now we check compatibility with the version specifier if non-empty
171
- if package_version_req != SpecifierSet(""):
172
- pkg_env_version = pkg_ref.__version__
173
-
174
- msg = (
175
- f"{class_name} requires package {package!r} to be present "
176
- f"in the python environment, with version {package_version_req}, "
177
- f"but incompatible version {pkg_env_version} was found. "
178
- )
179
- if obj is not None:
180
- msg = msg + (
181
- f"{package!r}, with version {package_version_req},"
182
- f"is a dependency of {class_name} and required to construct it. "
183
- )
184
-
185
- # raise error/warning or return False if version is incompatible
186
- if pkg_env_version not in package_version_req:
187
- if severity == "error":
188
- raise ModuleNotFoundError(msg)
189
- elif severity == "warning":
190
- warnings.warn(msg, stacklevel=2)
191
- elif severity == "none":
192
- return False
193
- else:
194
- raise RuntimeError(
195
- "Error in calling _check_soft_dependencies, severity argument"
196
- f' must be "error", "warning", or "none", found {severity!r}.'
197
- )
198
-
199
- # if package can be imported and no version issue was caught for any string,
200
- # then obj is compatible with the requirements and we should return True
201
- return True
202
-
203
-
204
- def _check_python_version(obj, package=None, msg=None, severity="error"):
205
- """Check if system python version is compatible with requirements of obj.
206
-
207
- Parameters
208
- ----------
209
- obj : BaseObject descendant
210
- used to check python version
211
- package : str, default = None
212
- if given, will be used in error message as package name
213
- msg : str, optional, default = default message (msg below)
214
- error message to be returned in the `ModuleNotFoundError`, overrides default
215
- severity : str, "error" (default), "warning", or "none"
216
- whether the check should raise an error, a warning, or nothing
217
-
218
- Returns
219
- -------
220
- compatible : bool, whether obj is compatible with system python version
221
- check is using the python_version tag of obj
222
-
223
- Raises
224
- ------
225
- ModuleNotFoundError
226
- User friendly error if obj has python_version tag that is
227
- incompatible with the system python version. If package is given,
228
- error message gives package as the reason for incompatibility.
229
- """
230
- est_specifier_tag = obj.get_class_tag("python_version", tag_value_default="None")
231
- if est_specifier_tag in ["None", None]:
232
- return True
233
-
234
- try:
235
- est_specifier = SpecifierSet(est_specifier_tag)
236
- except InvalidSpecifier:
237
- msg_version = (
238
- f"wrong format for python_version tag, "
239
- f'must be PEP 440 compatible specifier string, e.g., "<3.9, >= 3.6.3",'
240
- f" but found {est_specifier_tag!r}"
241
- )
242
- raise InvalidSpecifier(msg_version) from None
243
-
244
- # python sys version, e.g., "3.8.12"
245
- sys_version = sys.version.split(" ")[0]
246
-
247
- if sys_version in est_specifier:
248
- return True
249
- # now we know that est_version is not compatible with sys_version
250
- if isclass(obj):
251
- class_name = obj.__name__
252
- else:
253
- class_name = type(obj).__name__
254
-
255
- if not isinstance(msg, str):
256
- msg = (
257
- f"{class_name} requires python version to be {est_specifier},"
258
- f" but system python version is {sys.version}."
259
- )
260
-
261
- if package is not None:
262
- msg += (
263
- f" This is due to python version requirements of the {package} package."
264
- )
265
-
266
- if severity == "error":
267
- raise ModuleNotFoundError(msg)
268
- elif severity == "warning":
269
- warnings.warn(msg, stacklevel=2)
270
- elif severity == "none":
271
- return False
272
- else:
273
- raise RuntimeError(
274
- "Error in calling _check_python_version, severity "
275
- f'argument must be "error", "warning", or "none", found {severity!r}.'
276
- )
277
- return True
278
-
279
-
280
- def _check_estimator_deps(obj, msg=None, severity="error"):
281
- """Check if object/estimator's package & python requirements are met by python env.
282
-
283
- Convenience wrapper around `_check_python_version` and `_check_soft_dependencies`,
284
- checking against estimator tags `"python_version"`, `"python_dependencies"`.
285
-
286
- Checks whether dependency requirements of `BaseObject`-s in `obj`
287
- are satisfied by the current python environment.
288
-
289
- Parameters
290
- ----------
291
- obj : `BaseObject` descendant, instance or class, or list/tuple thereof
292
- object(s) that this function checks compatibility of, with the python env
293
- msg : str, optional, default = default message (msg below)
294
- error message to be returned in the `ModuleNotFoundError`, overrides default
295
- severity : str, "error" (default), "warning", or "none"
296
- behaviour for raising errors or warnings
297
- "error" - raises a `ModuleNotFoundError` if environment is incompatible
298
- "warning" - raises a warning if environment is incompatible
299
- function returns False if environment is incompatible, otherwise True
300
- "none" - does not raise exception or warning
301
- function returns False if environment is incompatible, otherwise True
302
-
303
- Returns
304
- -------
305
- compatible : bool, whether `obj` is compatible with python environment
306
- False is returned only if no exception is raised by the function
307
- checks for python version using the python_version tag of obj
308
- checks for soft dependencies present using the python_dependencies tag of obj
309
- if `obj` contains multiple `BaseObject`-s, checks whether all are compatible
310
-
311
- Raises
312
- ------
313
- ModuleNotFoundError
314
- User friendly error if obj has python_version tag that is
315
- incompatible with the system python version.
316
- Compatible python versions are determined by the "python_version" tag of obj.
317
- User friendly error if obj has package dependencies that are not satisfied.
318
- Packages are determined based on the "python_dependencies" tag of obj.
319
- """
320
- compatible = True
321
-
322
- # if list or tuple, recurse & iterate over element, and return conjunction
323
- if isinstance(obj, (list, tuple)):
324
- for x in obj:
325
- x_chk = _check_estimator_deps(x, msg=msg, severity=severity)
326
- compatible = compatible and x_chk
327
- return compatible
328
-
329
- compatible = compatible and _check_python_version(obj, severity=severity)
330
-
331
- pkg_deps = obj.get_class_tag("python_dependencies", None)
332
- pck_alias = obj.get_class_tag("python_dependencies_alias", None)
333
- if pkg_deps is not None and not isinstance(pkg_deps, list):
334
- pkg_deps = [pkg_deps]
335
- if pkg_deps is not None:
336
- pkg_deps_ok = _check_soft_dependencies(
337
- *pkg_deps, severity=severity, obj=obj, package_import_alias=pck_alias
338
- )
339
- compatible = compatible and pkg_deps_ok
340
-
341
- return compatible
File without changes
File without changes