httpx-qs 0.2.0__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.
httpx_qs/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ """httpx-qs: A library for smart query string handling with httpx."""
2
+
3
+ __version__ = "0.2.0"
4
+
5
+ from .enums.merge_policy import MergePolicy
6
+ from .transporters import smart_query_strings
7
+ from .utils.merge_query import merge_query
8
+
9
+
10
+ __all__ = [
11
+ "smart_query_strings",
12
+ "MergePolicy",
13
+ "merge_query",
14
+ ]
@@ -0,0 +1,19 @@
1
+ """Policy that determines how to handle keys that already exist in the query string."""
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class MergePolicy(str, Enum):
7
+ """Policy that determines how to handle keys that already exist in the query string.
8
+
9
+ Values:
10
+ COMBINE: (default) Combine existing and new values into a list (preserving order: existing then new).
11
+ REPLACE: Replace existing value with the new one (last-wins).
12
+ KEEP: Keep the existing value, ignore the new one (first-wins).
13
+ ERROR: Raise a ValueError if a key collision occurs.
14
+ """
15
+
16
+ COMBINE = "combine"
17
+ REPLACE = "replace"
18
+ KEEP = "keep"
19
+ ERROR = "error"
httpx_qs/py.typed ADDED
File without changes
@@ -0,0 +1,36 @@
1
+ """A transport that merges extra query params supplied via request.extensions."""
2
+
3
+ import typing as t
4
+
5
+ import httpx
6
+ from qs_codec import EncodeOptions, ListFormat
7
+
8
+ from httpx_qs.utils.merge_query import MergePolicy, merge_query
9
+
10
+
11
+ class SmartQueryStrings(httpx.BaseTransport):
12
+ """A transport that merges extra query params supplied via request.extensions."""
13
+
14
+ def __init__(self, next_transport: httpx.BaseTransport) -> None:
15
+ """Initialize with the next transport in the chain."""
16
+ self.next_transport = next_transport
17
+
18
+ def handle_request(self, request: httpx.Request) -> httpx.Response:
19
+ """Handle the request, merging extra query params if provided."""
20
+ extra_params: t.Dict[t.Any, t.Any] = request.extensions.get("extra_query_params", {})
21
+ extra_params_options: t.Optional[EncodeOptions] = request.extensions.get("extra_query_params_options", None)
22
+ merge_policy: t.Optional[t.Union[MergePolicy, str]] = request.extensions.get("extra_query_params_policy")
23
+ if extra_params:
24
+ request.url = httpx.URL(
25
+ merge_query(
26
+ str(request.url),
27
+ extra_params,
28
+ (
29
+ extra_params_options
30
+ if extra_params_options is not None
31
+ else EncodeOptions(list_format=ListFormat.REPEAT)
32
+ ),
33
+ policy=merge_policy if merge_policy is not None else MergePolicy.COMBINE,
34
+ )
35
+ )
36
+ return self.next_transport.handle_request(request)
@@ -0,0 +1,84 @@
1
+ """Utility to merge query parameters into a URL's query string.
2
+
3
+ Provides different merge policies controlling how conflicting keys are handled.
4
+ """
5
+
6
+ import typing as t
7
+ from urllib.parse import SplitResult, urlsplit, urlunsplit
8
+
9
+ from qs_codec import EncodeOptions, ListFormat, decode, encode
10
+
11
+ from httpx_qs.enums.merge_policy import MergePolicy
12
+
13
+
14
+ def _combine(existing_value: t.Any, new_value: t.Any) -> t.List[t.Any]:
15
+ """Return a combined list ensuring list semantics for multiple values.
16
+
17
+ Always returns a list (copy) even if both inputs are scalar values.
18
+ """
19
+ left_list: t.List[t.Any]
20
+ if isinstance(existing_value, list):
21
+ # slice copy then cast to satisfy type checker
22
+ left_list = t.cast(t.List[t.Any], existing_value[:])
23
+ else:
24
+ left_list = [existing_value]
25
+
26
+ right_list: t.List[t.Any]
27
+ if isinstance(new_value, list):
28
+ right_list = t.cast(t.List[t.Any], new_value[:])
29
+ else:
30
+ right_list = [new_value]
31
+ # Return a new list (avoid mutating originals if lists)
32
+ return list(left_list) + list(right_list)
33
+
34
+
35
+ def merge_query(
36
+ url: str,
37
+ extra: t.Mapping[str, t.Any],
38
+ options: EncodeOptions = EncodeOptions(list_format=ListFormat.REPEAT),
39
+ policy: t.Union[MergePolicy, str] = MergePolicy.COMBINE,
40
+ ) -> str:
41
+ """Merge extra query parameters into a URL's existing query string.
42
+
43
+ Args:
44
+ url: The original URL which may contain an existing query string.
45
+ extra: Mapping of additional query parameters to merge into the URL.
46
+ options: Optional :class:`qs_codec.EncodeOptions` to customize encoding behavior.
47
+ policy: Merge policy to apply when a key already exists (``combine`` | ``replace`` | ``keep`` | ``error``).
48
+ Returns:
49
+ The URL with the merged query string.
50
+ Raises:
51
+ ValueError: If ``policy == 'error'`` and a duplicate key is encountered.
52
+ """
53
+ policy_enum: MergePolicy = MergePolicy(policy) if not isinstance(policy, MergePolicy) else policy
54
+
55
+ parts: SplitResult = urlsplit(url)
56
+ existing: t.Dict[str, t.Any] = decode(parts.query) if parts.query else {}
57
+
58
+ for k, v in extra.items():
59
+ if k not in existing:
60
+ existing[k] = v
61
+ continue
62
+
63
+ # k exists already
64
+ if policy_enum is MergePolicy.COMBINE:
65
+ existing[k] = _combine(existing[k], v)
66
+ elif policy_enum is MergePolicy.REPLACE:
67
+ existing[k] = v
68
+ elif policy_enum is MergePolicy.KEEP:
69
+ # Leave existing value untouched
70
+ continue
71
+ elif policy_enum is MergePolicy.ERROR:
72
+ raise ValueError(f"Duplicate query parameter '{k}' encountered while policy=error")
73
+ else: # pragma: no cover - defensive (should not happen due to Enum validation)
74
+ existing[k] = _combine(existing[k], v)
75
+
76
+ return urlunsplit(
77
+ (
78
+ parts.scheme,
79
+ parts.netloc,
80
+ parts.path,
81
+ encode(existing, options),
82
+ parts.fragment,
83
+ )
84
+ )
@@ -0,0 +1,321 @@
1
+ Metadata-Version: 2.4
2
+ Name: httpx-qs
3
+ Version: 0.2.0
4
+ Summary: HTTPX transport leveraging qs-codec for advanced query string encoding and decoding.
5
+ Project-URL: Homepage, https://techouse.github.io/httpx_qs/
6
+ Project-URL: Documentation, https://techouse.github.io/httpx_qs/
7
+ Project-URL: Repository, https://github.com/techouse/httpx_qs.git
8
+ Project-URL: Issues, https://github.com/techouse/httpx_qs/issues
9
+ Project-URL: Changelog, https://github.com/techouse/httpx_qs/blob/master/CHANGELOG.md
10
+ Project-URL: Sponsor, https://github.com/sponsors/techouse
11
+ Project-URL: PayPal, https://paypal.me/ktusar
12
+ Author-email: Klemen Tusar <techouse@gmail.com>
13
+ License-Expression: BSD-3-Clause
14
+ License-File: LICENSE
15
+ Keywords: arrays,brackets,codec,form-urlencoded,httpx,nested,percent-encoding,qs,query,query-string,querystring,rfc3986,url,urldecode,urlencode
16
+ Classifier: Development Status :: 4 - Beta
17
+ Classifier: Environment :: Web Environment
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: License :: OSI Approved :: BSD License
20
+ Classifier: Operating System :: OS Independent
21
+ Classifier: Programming Language :: Python
22
+ Classifier: Programming Language :: Python :: 3
23
+ Classifier: Programming Language :: Python :: 3 :: Only
24
+ Classifier: Programming Language :: Python :: 3.8
25
+ Classifier: Programming Language :: Python :: 3.9
26
+ Classifier: Programming Language :: Python :: 3.10
27
+ Classifier: Programming Language :: Python :: 3.11
28
+ Classifier: Programming Language :: Python :: 3.12
29
+ Classifier: Programming Language :: Python :: 3.13
30
+ Classifier: Programming Language :: Python :: 3.14
31
+ Classifier: Programming Language :: Python :: Implementation :: CPython
32
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
33
+ Classifier: Topic :: Internet :: WWW/HTTP
34
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries
35
+ Classifier: Topic :: Software Development :: Libraries
36
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
37
+ Classifier: Topic :: Text Processing :: General
38
+ Classifier: Topic :: Utilities
39
+ Classifier: Typing :: Typed
40
+ Requires-Python: >=3.8
41
+ Requires-Dist: httpx<1.0.0,>=0.28.1
42
+ Requires-Dist: qs-codec>=1.3.1
43
+ Provides-Extra: dev
44
+ Requires-Dist: black; extra == 'dev'
45
+ Requires-Dist: isort; extra == 'dev'
46
+ Requires-Dist: mypy>=1.10.0; extra == 'dev'
47
+ Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
48
+ Requires-Dist: pytest>=8.1.2; extra == 'dev'
49
+ Requires-Dist: toml>=0.10.2; extra == 'dev'
50
+ Requires-Dist: tox; extra == 'dev'
51
+ Description-Content-Type: text/x-rst
52
+
53
+ httpx-qs
54
+ ========
55
+
56
+ Smart, policy-driven query string merging & encoding for `httpx <https://www.python-httpx.org>`_ powered by
57
+ `qs-codec <https://techouse.github.io/qs_codec/>`_.
58
+
59
+ .. image:: https://img.shields.io/pypi/v/httpx-qs
60
+ :target: https://pypi.org/project/httpx-qs/
61
+ :alt: PyPI version
62
+
63
+ .. image:: https://img.shields.io/pypi/status/httpx-qs
64
+ :target: https://pypi.org/project/httpx-qs/
65
+ :alt: PyPI - Status
66
+
67
+ .. image:: https://img.shields.io/pypi/pyversions/httpx-qs
68
+ :target: https://pypi.org/project/httpx-qs/
69
+ :alt: Supported Python versions
70
+
71
+ .. image:: https://img.shields.io/badge/PyPy-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-6f42c1?logo=pypy
72
+ :target: https://www.pypy.org/
73
+ :alt: PyPy support status
74
+
75
+ .. image:: https://img.shields.io/pypi/format/httpx-qs
76
+ :target: https://pypi.org/project/httpx-qs/
77
+ :alt: PyPI - Format
78
+
79
+ .. image:: https://github.com/techouse/httpx_qs/actions/workflows/test.yml/badge.svg
80
+ :target: https://github.com/techouse/httpx_qs/actions/workflows/test.yml
81
+ :alt: Tests
82
+
83
+ .. image:: https://github.com/techouse/httpx_qs/actions/workflows/github-code-scanning/codeql/badge.svg
84
+ :target: https://github.com/techouse/httpx_qs/actions/workflows/github-code-scanning/codeql
85
+ :alt: CodeQL
86
+
87
+ .. image:: https://img.shields.io/github/license/techouse/httpx_qs
88
+ :target: https://github.com/techouse/httpx_qs/blob/master/LICENSE
89
+ :alt: License
90
+
91
+ .. image:: https://codecov.io/gh/techouse/httpx_qs/graph/badge.svg?token=JMt8akIZFh
92
+ :target: https://codecov.io/gh/techouse/httpx_qs
93
+ :alt: Codecov
94
+
95
+ .. image:: https://app.codacy.com/project/badge/Grade/420bf66ab90d4b3798573b6ff86d02af
96
+ :target: https://app.codacy.com/gh/techouse/httpx_qs/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade
97
+ :alt: Codacy Quality
98
+
99
+ .. image:: https://img.shields.io/github/sponsors/techouse
100
+ :target: https://github.com/sponsors/techouse
101
+ :alt: GitHub Sponsors
102
+
103
+ .. image:: https://img.shields.io/github/stars/techouse/httpx_qs
104
+ :target: https://github.com/techouse/httpx_qs/stargazers
105
+ :alt: GitHub Repo stars
106
+
107
+ .. image:: https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg
108
+ :target: CODE-OF-CONDUCT.md
109
+ :alt: Contributor Covenant
110
+
111
+ .. |flake8| image:: https://img.shields.io/badge/flake8-checked-blueviolet.svg
112
+ :target: https://flake8.pycqa.org/en/latest/
113
+
114
+ .. image:: https://img.shields.io/badge/mypy-checked-blue.svg
115
+ :target: https://mypy.readthedocs.io/en/stable/
116
+ :alt: mypy
117
+
118
+ .. image:: https://img.shields.io/badge/linting-pylint-yellowgreen.svg
119
+ :target: https://github.com/pylint-dev/pylint
120
+ :alt: pylint
121
+
122
+ .. image:: https://img.shields.io/badge/imports-isort-blue.svg
123
+ :target: https://pycqa.github.io/isort/
124
+ :alt: isort
125
+
126
+ .. image:: https://img.shields.io/badge/security-bandit-blue.svg
127
+ :target: https://github.com/PyCQA/bandit
128
+ :alt: Security Status
129
+
130
+ Overview
131
+ --------
132
+
133
+ ``httpx-qs`` provides:
134
+
135
+ * A transport wrapper ``SmartQueryStrings`` that merges *existing* URL query parameters with *additional* ones supplied via ``request.extensions``.
136
+ * A flexible ``merge_query`` utility with selectable conflict resolution policies.
137
+ * Consistent, standards-aware encoding via ``qs-codec`` (RFC3986 percent-encoding, structured arrays, nested objects, etc.).
138
+
139
+ Why?
140
+ ----
141
+
142
+ HTTPX already lets you pass ``params=`` when making requests, but sometimes you need to:
143
+
144
+ * Inject **additional** query parameters from middleware/transport layers (e.g., auth tags, tracing IDs, feature flags) *without losing* the caller's original intent.
145
+ * Combine repeated keys or treat them deterministically (replace / keep / error) rather than always flattening.
146
+ * Support nested data or list semantics consistent across clients and services.
147
+
148
+ ``qs-codec`` supplies the primitives (decoding & encoding with configurable ``ListFormat``). ``httpx-qs`` stitches that into HTTPX's transport pipeline so you can declaratively extend queries at request dispatch time.
149
+
150
+ Requirements
151
+ ------------
152
+
153
+ * CPython 3.8-3.14 or PyPy 3.8-3.11
154
+ * ``httpx>=0.28.1,<1.0.0``
155
+ * ``qs-codec>=1.3.1``
156
+
157
+ Installation
158
+ ------------
159
+
160
+ .. code-block:: bash
161
+
162
+ pip install httpx-qs
163
+
164
+ Minimal Example
165
+ ---------------
166
+
167
+ .. code-block:: python
168
+
169
+ import httpx
170
+ from httpx_qs.transporters.smart_query_strings import SmartQueryStrings
171
+
172
+ client = httpx.Client(transport=SmartQueryStrings(httpx.HTTPTransport()))
173
+
174
+ response = client.get(
175
+ "https://www.google.com",
176
+ params={"a": "b", "c": "d"},
177
+ extensions={"extra_query_params": {"c": "D", "tags": ["x", "y"]}},
178
+ )
179
+
180
+ print(str(response.request.url))
181
+ # Example (order may vary): https://www.google.com/?a=b&c=d&c=D&tags=x&tags=y
182
+
183
+ Using Merge Policies
184
+ --------------------
185
+
186
+ Conflict resolution when a key already exists is controlled by ``MergePolicy``.
187
+
188
+ Available policies:
189
+
190
+ * ``combine`` (default): concatenate values → existing first, new afterward (``a=1&a=2``)
191
+ * ``replace``: last-wins, existing value is overwritten (``a=2``)
192
+ * ``keep``: first-wins, ignore the new value (``a=1``)
193
+ * ``error``: raise ``ValueError`` on duplicate key
194
+
195
+ Specify per request:
196
+
197
+ .. code-block:: python
198
+
199
+ from httpx_qs import MergePolicy
200
+
201
+ r = client.get(
202
+ "https://api.example.com/resources",
203
+ params={"dup": "original"},
204
+ extensions={
205
+ "extra_query_params": {"dup": "override"},
206
+ "extra_query_params_policy": MergePolicy.REPLACE,
207
+ },
208
+ )
209
+ # Query contains only dup=override
210
+
211
+ Async Usage
212
+ -----------
213
+
214
+ ``SmartQueryStrings`` works equally for ``AsyncClient``:
215
+
216
+ .. code-block:: python
217
+
218
+ import httpx
219
+ from httpx_qs.transporters.smart_query_strings import SmartQueryStrings
220
+
221
+ async def main() -> None:
222
+ async with httpx.AsyncClient(transport=SmartQueryStrings(httpx.AsyncHTTPTransport())) as client:
223
+ r = await client.get(
224
+ "https://example.com/items",
225
+ params={"filters": "active"},
226
+ extensions={"extra_query_params": {"page": 2}},
227
+ )
228
+ print(r.request.url)
229
+
230
+ # Run with: asyncio.run(main())
231
+
232
+ ``merge_query`` Utility
233
+ -----------------------
234
+
235
+ You can use the underlying function directly:
236
+
237
+ .. code-block:: python
238
+
239
+ from httpx_qs import merge_query, MergePolicy
240
+ from qs_codec import EncodeOptions, ListFormat
241
+
242
+ new_url = merge_query(
243
+ "https://example.com?a=1",
244
+ {"a": 2, "tags": ["x", "y"]},
245
+ options=EncodeOptions(list_format=ListFormat.REPEAT),
246
+ policy=MergePolicy.COMBINE,
247
+ )
248
+ # → https://example.com/?a=1&a=2&tags=x&tags=y
249
+
250
+ Why ``ListFormat.REPEAT`` by Default?
251
+ -------------------------------------
252
+
253
+ ``qs-codec`` exposes several list formatting strategies (e.g. repeat, brackets, indices). ``httpx-qs`` defaults to
254
+ ``ListFormat.REPEAT`` because:
255
+
256
+ * It matches common server expectations (``key=value&key=value``) without requiring bracket parsing logic.
257
+ * It preserves original ordering while remaining unambiguous and simple for log inspection.
258
+ * Many API gateways / proxies / caches reliably forward repeated keys whereas bracket syntaxes can be normalized away.
259
+
260
+ If your API prefers another convention (e.g. ``tags[]=x&tags[]=y`` or ``tags[0]=x``) just pass a custom ``EncodeOptions`` via
261
+ ``extensions['extra_query_params_options']`` or parameter ``options`` when calling ``merge_query`` directly.
262
+
263
+ Advanced Per-Request Customization
264
+ ----------------------------------
265
+
266
+ .. code-block:: python
267
+
268
+ from qs_codec import EncodeOptions, ListFormat
269
+
270
+ r = client.get(
271
+ "https://service.local/search",
272
+ params={"q": "test"},
273
+ extensions={
274
+ "extra_query_params": {"debug": True, "tags": ["alpha", "beta"]},
275
+ "extra_query_params_policy": "combine", # also accepts string values
276
+ "extra_query_params_options": EncodeOptions(list_format=ListFormat.BRACKETS),
277
+ },
278
+ )
279
+ # Example: ?q=test&debug=true&tags[]=alpha&tags[]=beta
280
+
281
+ Error Policy Example
282
+ --------------------
283
+
284
+ .. code-block:: python
285
+
286
+ try:
287
+ client.get(
288
+ "https://example.com",
289
+ params={"token": "abc"},
290
+ extensions={
291
+ "extra_query_params": {"token": "xyz"},
292
+ "extra_query_params_policy": "error",
293
+ },
294
+ )
295
+ except ValueError as exc:
296
+ print("Duplicate detected:", exc)
297
+
298
+ Testing Strategy
299
+ ----------------
300
+
301
+ The project includes unit tests covering policy behaviors, error handling, and transport-level integration. Run them with:
302
+
303
+ .. code-block:: bash
304
+
305
+ pytest
306
+
307
+ Further Reading
308
+ ---------------
309
+
310
+ * HTTPX documentation: https://www.python-httpx.org
311
+ * qs-codec documentation: https://techouse.github.io/qs_codec/
312
+
313
+ License
314
+ -------
315
+
316
+ BSD-3-Clause. See ``LICENSE`` for details.
317
+
318
+ Contributing
319
+ ------------
320
+
321
+ Issues & PRs welcome. Please add tests for new behavior and keep doc examples in sync.
@@ -0,0 +1,9 @@
1
+ httpx_qs/__init__.py,sha256=GZuYqCxIPQyPD1PLo_ofJsJdA6aVJoy6MBp7XQSp4QM,308
2
+ httpx_qs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ httpx_qs/enums/merge_policy.py,sha256=CULesKR01GjBqIuPN7WD_EasvogpIpQy4oTNlbEG9Mg,653
4
+ httpx_qs/transporters/smart_query_strings.py,sha256=d3XKTb2X1Rey3vk1uiWjk4JTKZ5bstbd4NHUw-lXuig,1584
5
+ httpx_qs/utils/merge_query.py,sha256=usOLroZzR0tTFixV2Bh2TzxUz8poqJC_4qBaS9yPhOs,3016
6
+ httpx_qs-0.2.0.dist-info/METADATA,sha256=ZEN_bPqF5BaxyO87SmepXXYCI1dmY3QuDDD1RRVfIHU,10995
7
+ httpx_qs-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
+ httpx_qs-0.2.0.dist-info/licenses/LICENSE,sha256=BGym7TZirVdWajaAWJVsuIt57rdDM8_mmVAzXDerIlY,1498
9
+ httpx_qs-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Klemen Tusar
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.