pytest-optional-dependencies 0.1.2__py3-none-any.whl
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.
- pytest_optional_dependencies/__init__.py +4 -0
- pytest_optional_dependencies/plugin.py +288 -0
- pytest_optional_dependencies-0.1.2.dist-info/METADATA +113 -0
- pytest_optional_dependencies-0.1.2.dist-info/RECORD +7 -0
- pytest_optional_dependencies-0.1.2.dist-info/WHEEL +4 -0
- pytest_optional_dependencies-0.1.2.dist-info/entry_points.txt +2 -0
- pytest_optional_dependencies-0.1.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pytest-optional-dependencies: Handle missing imports gracefully during test collection.
|
|
3
|
+
|
|
4
|
+
This plugin allows tests to be skipped or deselected if they fail collection due to
|
|
5
|
+
missing optional dependency imports. This is useful when a package has optional extras
|
|
6
|
+
that may not be installed in all test environments.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Use pytest's StashKey for thread-safe storage of plugin state
|
|
13
|
+
FILTER_EVENTS_KEY = pytest.StashKey[list[str]]()
|
|
14
|
+
ACCEPTABLE_MISSING_MODULES_KEY = pytest.StashKey[set[str]]()
|
|
15
|
+
OPTIONAL_DEPENDENCIES_ANY_KEY = pytest.StashKey[bool]()
|
|
16
|
+
OPTIONAL_DEPENDENCIES_ACTION_KEY = pytest.StashKey[str]()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _DeselectedCollectorNode:
|
|
20
|
+
"""Minimal object for pytest_deselected accounting."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, nodeid):
|
|
23
|
+
self.nodeid = nodeid
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _extract_missing_module_name(longrepr_text):
|
|
27
|
+
"""Extract the module name from a ModuleNotFoundError/ImportError message.
|
|
28
|
+
|
|
29
|
+
Why: pytest's longreprtext contains the full error traceback. We need to parse
|
|
30
|
+
the specific error message to extract just the missing module name. The error
|
|
31
|
+
message format is: "No module named 'module.name'" with either single or double
|
|
32
|
+
quotes depending on Python version and context.
|
|
33
|
+
"""
|
|
34
|
+
no_module_prefix = "No module named "
|
|
35
|
+
if no_module_prefix not in longrepr_text:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
missing_part = longrepr_text.split(no_module_prefix, 1)[1].strip()
|
|
39
|
+
if not missing_part:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
# Extract the quoted module name. The quote character (single or double) indicates
|
|
43
|
+
# where the module name begins, and we find the closing quote.
|
|
44
|
+
quote = missing_part[0]
|
|
45
|
+
if quote in {'"', "'"}:
|
|
46
|
+
end_idx = missing_part.find(quote, 1)
|
|
47
|
+
if end_idx > 1:
|
|
48
|
+
return missing_part[1:end_idx]
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _normalize_module_names(raw_values):
|
|
53
|
+
"""Convert raw config values into a normalized set of module names.
|
|
54
|
+
|
|
55
|
+
Why: Config values can come from either CLI (--optional-dependency flag, can be
|
|
56
|
+
repeated) or ini file (comma-separated lists). We need to handle both formats
|
|
57
|
+
uniformly. CLI passes a list, ini passes strings. This function flattens them
|
|
58
|
+
and handles comma-separated values so users can write either:
|
|
59
|
+
optional_dependencies = numpy,scipy
|
|
60
|
+
optional_dependencies = numpy
|
|
61
|
+
scipy
|
|
62
|
+
in their ini file, or use --optional-dependency multiple times on the CLI.
|
|
63
|
+
"""
|
|
64
|
+
modules = set()
|
|
65
|
+
for value in raw_values:
|
|
66
|
+
if not value:
|
|
67
|
+
continue
|
|
68
|
+
for part in str(value).split(","):
|
|
69
|
+
module = part.strip()
|
|
70
|
+
if module:
|
|
71
|
+
modules.add(module)
|
|
72
|
+
return modules
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _get_optional_missing_module(report, config):
|
|
76
|
+
"""Check if a collection failure is due to an optional missing import.
|
|
77
|
+
|
|
78
|
+
Why this function exists: During collection, if a test module imports an optional
|
|
79
|
+
dependency that's not installed, the entire test collection fails. We need to:
|
|
80
|
+
1. Detect if the failure was actually due to a missing import (not another error)
|
|
81
|
+
2. Extract which module was missing
|
|
82
|
+
3. Check if that module is in our list of acceptable-to-skip missing modules
|
|
83
|
+
|
|
84
|
+
Returns: The missing module name if it's an optional dependency we should skip,
|
|
85
|
+
or None if this failure shouldn't be handled by this plugin.
|
|
86
|
+
"""
|
|
87
|
+
if not report.failed:
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
longrepr_text = getattr(report, "longreprtext", "")
|
|
91
|
+
# Check for ModuleNotFoundError or ImportError - only then is a missing module
|
|
92
|
+
# the root cause. Other import errors (syntax errors, etc.) shouldn't be skipped.
|
|
93
|
+
if (
|
|
94
|
+
"ImportError" not in longrepr_text
|
|
95
|
+
and "ModuleNotFoundError" not in longrepr_text
|
|
96
|
+
):
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
missing_module = _extract_missing_module_name(longrepr_text)
|
|
100
|
+
if not missing_module:
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
# If optional_dependencies_any is set, skip ANY missing module import error.
|
|
104
|
+
# This is useful for test environments where many optional deps might be missing.
|
|
105
|
+
if config.stash.get(OPTIONAL_DEPENDENCIES_ANY_KEY, False):
|
|
106
|
+
return missing_module
|
|
107
|
+
|
|
108
|
+
# Treat submodules as acceptable if top-level package is listed.
|
|
109
|
+
# Why: If user specifies "sklearn" as optional, they likely mean sklearn and all
|
|
110
|
+
# its submodules (sklearn.ensemble, sklearn.preprocessing, etc.). Without this,
|
|
111
|
+
# a test importing sklearn.ensemble would fail even if sklearn is listed.
|
|
112
|
+
optional_dependencies = config.stash.get(ACCEPTABLE_MISSING_MODULES_KEY, set())
|
|
113
|
+
top_level = missing_module.split(".", 1)[0]
|
|
114
|
+
if missing_module in optional_dependencies or top_level in optional_dependencies:
|
|
115
|
+
return missing_module
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _record_filter_event(config, message):
|
|
120
|
+
"""Record a filtering decision for the debug report if --report-optional-dependencies is set.
|
|
121
|
+
|
|
122
|
+
Why: Users can pass --report-optional-dependencies to see which tests were skipped and why.
|
|
123
|
+
This helps them verify the plugin is working as intended and debug any issues.
|
|
124
|
+
"""
|
|
125
|
+
if config.getoption("report_optional_dependencies"):
|
|
126
|
+
config.stash[FILTER_EVENTS_KEY].append(message)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
|
130
|
+
def pytest_make_collect_report(collector):
|
|
131
|
+
"""Intercept collection failures and convert optional-dependency failures to skips/passes.
|
|
132
|
+
|
|
133
|
+
Why tryfirst=True: We need to run before other plugins that might fail on import errors.
|
|
134
|
+
Why hookwrapper=True: We need to intercept the report AFTER collection happens but BEFORE
|
|
135
|
+
pytest processes it further. This allows us to change the outcome from "failed" to "skipped".
|
|
136
|
+
"""
|
|
137
|
+
outcome = yield
|
|
138
|
+
report = outcome.get_result()
|
|
139
|
+
|
|
140
|
+
missing_module = _get_optional_missing_module(report, collector.config)
|
|
141
|
+
if missing_module:
|
|
142
|
+
action = collector.config.stash.get(OPTIONAL_DEPENDENCIES_ACTION_KEY, "skip")
|
|
143
|
+
_record_filter_event(
|
|
144
|
+
collector.config,
|
|
145
|
+
f"{report.nodeid}: missing module '{missing_module}' is optional ({action})",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if action == "skip":
|
|
149
|
+
# Mark as skipped so the test still appears in output (good for visibility)
|
|
150
|
+
report.outcome = "skipped"
|
|
151
|
+
report.longrepr = (
|
|
152
|
+
str(collector.path),
|
|
153
|
+
0,
|
|
154
|
+
f"missing module '{missing_module}' is configured as optional",
|
|
155
|
+
)
|
|
156
|
+
else:
|
|
157
|
+
# Deselect collection node so it is reflected in pytest deselected counts.
|
|
158
|
+
collector.config.hook.pytest_deselected(
|
|
159
|
+
items=[_DeselectedCollectorNode(report.nodeid)]
|
|
160
|
+
)
|
|
161
|
+
report.outcome = "passed"
|
|
162
|
+
report.longrepr = None
|
|
163
|
+
outcome.force_result(report)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def pytest_addoption(parser):
|
|
167
|
+
"""Register command-line and ini-file options for this plugin."""
|
|
168
|
+
group = parser.getgroup("Optional dependencies")
|
|
169
|
+
|
|
170
|
+
# CLI options for specifying optional dependencies (can be used multiple times)
|
|
171
|
+
group.addoption(
|
|
172
|
+
"--optional-dependency",
|
|
173
|
+
action="append",
|
|
174
|
+
default=[],
|
|
175
|
+
metavar="MODULE",
|
|
176
|
+
help="Treat a missing module as an optional dependency during collection",
|
|
177
|
+
)
|
|
178
|
+
group.addoption(
|
|
179
|
+
"--optional-dependencies-any",
|
|
180
|
+
action="store_true",
|
|
181
|
+
default=False,
|
|
182
|
+
help="Treat any missing-module import as optional during collection",
|
|
183
|
+
)
|
|
184
|
+
group.addoption(
|
|
185
|
+
"--optional-dependencies-action",
|
|
186
|
+
action="store",
|
|
187
|
+
default=None,
|
|
188
|
+
choices=("deselect", "skip"),
|
|
189
|
+
help="How to report optional missing imports: skip (default) or deselect",
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Ini file options (alternative to CLI for permanent project configuration)
|
|
193
|
+
parser.addini(
|
|
194
|
+
"optional_dependencies",
|
|
195
|
+
"Optional dependencies that may be missing during collection import",
|
|
196
|
+
type="linelist",
|
|
197
|
+
default=[],
|
|
198
|
+
)
|
|
199
|
+
parser.addini(
|
|
200
|
+
"optional_dependencies_any",
|
|
201
|
+
"If true, treat any missing-module import as optional during collection",
|
|
202
|
+
type="bool",
|
|
203
|
+
default=False,
|
|
204
|
+
)
|
|
205
|
+
parser.addini(
|
|
206
|
+
"optional_dependencies_action",
|
|
207
|
+
"How optional missing imports are reported: skip (default) or deselect",
|
|
208
|
+
default="skip",
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Reporting option
|
|
212
|
+
if not getattr(parser, "_pytest_optional_dependencies_report_option_added", False):
|
|
213
|
+
group.addoption(
|
|
214
|
+
"--report-optional-dependencies",
|
|
215
|
+
action="store_true",
|
|
216
|
+
default=False,
|
|
217
|
+
help="Report optional-dependency collection decisions and their reasons",
|
|
218
|
+
)
|
|
219
|
+
parser._pytest_optional_dependencies_report_option_added = True
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def pytest_configure(config):
|
|
223
|
+
"""Initialize plugin state at the start of the test session.
|
|
224
|
+
|
|
225
|
+
Why: We need to prepare the config.stash with initial values before collection starts,
|
|
226
|
+
and also parse/merge CLI options with ini file settings. CLI options have priority.
|
|
227
|
+
"""
|
|
228
|
+
config.stash[FILTER_EVENTS_KEY] = []
|
|
229
|
+
|
|
230
|
+
# Merge optional dependencies from both ini file and CLI (CLI takes precedence)
|
|
231
|
+
configured_missing_imports = _normalize_module_names(
|
|
232
|
+
config.getini("optional_dependencies")
|
|
233
|
+
)
|
|
234
|
+
cli_missing_imports = _normalize_module_names(
|
|
235
|
+
config.getoption("optional_dependency")
|
|
236
|
+
)
|
|
237
|
+
config.stash[ACCEPTABLE_MISSING_MODULES_KEY] = (
|
|
238
|
+
configured_missing_imports | cli_missing_imports
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Set the "treat any missing import" flag if either CLI or ini is enabled
|
|
242
|
+
config.stash[OPTIONAL_DEPENDENCIES_ANY_KEY] = bool(
|
|
243
|
+
config.getini("optional_dependencies_any")
|
|
244
|
+
) or bool(config.getoption("optional_dependencies_any"))
|
|
245
|
+
|
|
246
|
+
# Determine action (skip or deselect) - CLI takes precedence over ini file
|
|
247
|
+
action = config.getoption("optional_dependencies_action") or config.getini(
|
|
248
|
+
"optional_dependencies_action"
|
|
249
|
+
)
|
|
250
|
+
if action not in {"deselect", "skip"}:
|
|
251
|
+
raise pytest.UsageError(
|
|
252
|
+
"optional_dependencies_action must be either 'deselect' or 'skip'"
|
|
253
|
+
)
|
|
254
|
+
config.stash[OPTIONAL_DEPENDENCIES_ACTION_KEY] = action
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def pytest_collection_finish(session):
|
|
258
|
+
"""Print debug report about optional dependencies if --report-optional-dependencies was set.
|
|
259
|
+
|
|
260
|
+
Why: Users need visibility into what the plugin did. This report shows the configured
|
|
261
|
+
policy and a log of every collection decision made, helping them debug issues.
|
|
262
|
+
"""
|
|
263
|
+
config = session.config
|
|
264
|
+
if not config.getoption("report_optional_dependencies"):
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
optional_dependencies_any = config.stash.get(OPTIONAL_DEPENDENCIES_ANY_KEY, False)
|
|
268
|
+
optional_dependencies = sorted(
|
|
269
|
+
config.stash.get(ACCEPTABLE_MISSING_MODULES_KEY, set())
|
|
270
|
+
)
|
|
271
|
+
action = config.stash.get(OPTIONAL_DEPENDENCIES_ACTION_KEY, "skip")
|
|
272
|
+
|
|
273
|
+
print("optional dependency policy:")
|
|
274
|
+
print(f" optional dependencies any: {optional_dependencies_any}")
|
|
275
|
+
print(f" optional dependencies action: {action}")
|
|
276
|
+
if optional_dependencies:
|
|
277
|
+
print(" optional dependencies: " + ", ".join(optional_dependencies))
|
|
278
|
+
else:
|
|
279
|
+
print(" optional dependencies: (none)")
|
|
280
|
+
|
|
281
|
+
events = config.stash.get(FILTER_EVENTS_KEY, [])
|
|
282
|
+
print("optional dependency report:")
|
|
283
|
+
if not events:
|
|
284
|
+
print(" no optional imports were skipped")
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
for event in events:
|
|
288
|
+
print(f" - {event}")
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pytest-optional-dependencies
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Don't test code that won't load due to missing imports. A pytest plugin to skip tests that require optional dependencies that are not installed.
|
|
5
|
+
Project-URL: Homepage, https://github.com/okken/pytest-optional-dependencies
|
|
6
|
+
Project-URL: Repository, https://github.com/okken/pytest-optional-dependencies
|
|
7
|
+
Project-URL: Issues, https://github.com/okken/pytest-optional-dependencies/issues
|
|
8
|
+
Author: Brian Okken
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: collection,imports,plugin,pytest,testing
|
|
12
|
+
Classifier: Framework :: Pytest
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Topic :: Software Development :: Testing
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: pytest>=8.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: hatchling>=1.25; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: tox>=4.0; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# pytest-optional-dependencies
|
|
26
|
+
|
|
27
|
+
Don't test code that won't load due to missing imports.
|
|
28
|
+
A pytest plugin to skip tests that require optional dependencies that are not installed.
|
|
29
|
+
|
|
30
|
+
Collection-time optional dependency handling for pytest.
|
|
31
|
+
|
|
32
|
+
This plugin allows specific missing imports to be treated as optional so collection can continue without errors.
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
* --optional-dependency MODULE (repeatable, also accepts comma-separated values).
|
|
37
|
+
* specify which dependencies to skip/deselect based on their absence
|
|
38
|
+
* --optional-dependencies-any
|
|
39
|
+
* to treat any missing module import as optional.
|
|
40
|
+
* --optional-dependencies-action
|
|
41
|
+
* to control optional import handling: skip (default) or deselect.
|
|
42
|
+
* Configuration options
|
|
43
|
+
* optional_dependencies
|
|
44
|
+
* optional_dependencies_any
|
|
45
|
+
* optional_dependencies_action
|
|
46
|
+
* --report-optional-dependencies
|
|
47
|
+
* Report what was filtered and why.
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv pip install pytest-optional-dependencies
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or with pip:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
python -m pip install pytest-optional-dependencies
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Compatibility
|
|
62
|
+
|
|
63
|
+
- Python: 3.10+
|
|
64
|
+
- pytest: 8.0+
|
|
65
|
+
|
|
66
|
+
## CLI options
|
|
67
|
+
|
|
68
|
+
- --optional-dependency MODULE
|
|
69
|
+
- --optional-dependencies-any
|
|
70
|
+
- --optional-dependencies-action {deselect,skip}
|
|
71
|
+
- --report-optional-dependencies
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
pytest.ini:
|
|
76
|
+
|
|
77
|
+
```ini
|
|
78
|
+
[pytest]
|
|
79
|
+
optional_dependencies =
|
|
80
|
+
optional_dependency
|
|
81
|
+
some_namespace.submodule
|
|
82
|
+
optional_dependencies_any = false
|
|
83
|
+
optional_dependencies_action = skip
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
pyproject.toml:
|
|
87
|
+
|
|
88
|
+
```toml
|
|
89
|
+
[tool.pytest.ini_options]
|
|
90
|
+
optional_dependencies = [
|
|
91
|
+
"optional_dependency",
|
|
92
|
+
"some_namespace.submodule",
|
|
93
|
+
]
|
|
94
|
+
optional_dependencies_any = false
|
|
95
|
+
optional_dependencies_action = "skip"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Example
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pytest -q --optional-dependency optional_dependency --report-optional-dependencies
|
|
102
|
+
pytest -q --optional-dependency optional_dependency --optional-dependencies-action skip
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Development
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
python -m pytest -q
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT. See LICENSE.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
pytest_optional_dependencies/__init__.py,sha256=X5R-DJs-iCNJxlpaPEDAFf2IcvJvMgpO6yJE4NQp-t0,100
|
|
2
|
+
pytest_optional_dependencies/plugin.py,sha256=Th4UbGFjb6GHF8R18Bno9oSbMFbS5kpWKQleURoY7aw,11294
|
|
3
|
+
pytest_optional_dependencies-0.1.2.dist-info/METADATA,sha256=7IVqPQGaQ6cR4d-0aX5bjG2DYRX55ylR2Qs8_1Fs4gE,2993
|
|
4
|
+
pytest_optional_dependencies-0.1.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
5
|
+
pytest_optional_dependencies-0.1.2.dist-info/entry_points.txt,sha256=zcUj5PVlA6DWSC7Rn58QQtIMCx_oxOOB0EPnx7gyiVQ,71
|
|
6
|
+
pytest_optional_dependencies-0.1.2.dist-info/licenses/LICENSE,sha256=0J6JKEVXQiGte_mgexMhnvLhrPiYlfNZGOi3ASq_2l0,1079
|
|
7
|
+
pytest_optional_dependencies-0.1.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) collect-filter contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|