optionz 0.1.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.
opt.py ADDED
@@ -0,0 +1,582 @@
1
+ import builtins
2
+ from typing import Any, Callable, Iterable, Optional, overload
3
+
4
+ # Raised when trying to unwrap a None value. It is an alias for ValueError to allow
5
+ # mocking it in tests (or global mokey-patching if you are feelling adventurous).
6
+ UnwrapError = ValueError
7
+
8
+ __all__ = (
9
+ "UnwrapError",
10
+ "unwrap",
11
+ "unwrap_or",
12
+ "unwrap_or_else",
13
+ "expect",
14
+ "map",
15
+ "coalesce",
16
+ "values",
17
+ "together",
18
+ "zip",
19
+ "elements",
20
+ "getattr",
21
+ "call",
22
+ "iter",
23
+ )
24
+
25
+
26
+ def _any(x: object) -> Any:
27
+ return x
28
+
29
+
30
+ #
31
+ # Unwrappers
32
+ #
33
+ def unwrap[T](value: Optional[T], /) -> T:
34
+ """
35
+ Unwrap a value that may be None. If the value is None, raise a ValueError.
36
+
37
+ Args:
38
+ value: The optional value to unwrap.
39
+
40
+ Examples:
41
+ >>> opt.unwrap(42)
42
+ 42
43
+ >>> opt.unwrap(None)
44
+ Traceback (most recent call last):
45
+ ...
46
+ ValueError: Value is None
47
+
48
+ See also:
49
+ - :func:`opt.expect`
50
+ - :func:`opt.unwrap_or`
51
+ - :func:`opt.unwrap_or_else`
52
+ """
53
+ if value is None:
54
+ raise UnwrapError("Value is None")
55
+ return value
56
+
57
+
58
+ def unwrap_or[T](value: Optional[T], /, default: T) -> T:
59
+ """
60
+ Unwrap a value that may be None. If the value is None, return the default
61
+ value.
62
+
63
+ Args:
64
+ value: The optional value to unwrap.
65
+ default: The value to return if the optional value is None.
66
+
67
+ Examples:
68
+ >>> opt.unwrap_or(0, 42)
69
+ 0
70
+ >>> opt.unwrap_or(None, 42)
71
+ 42
72
+
73
+ See also:
74
+ - :func:`opt.unwrap`
75
+ - :func:`opt.unwrap_or_else`
76
+ """
77
+ return value if value is not None else default
78
+
79
+
80
+ def unwrap_or_else[T](value: Optional[T], /, default_fn: Callable[[], T]) -> T:
81
+ """
82
+ Unwrap a value that may be None. If the value is None, return the result of
83
+ calling the default function.
84
+
85
+ This may be used instead of :func:`opt.unwrap_or` when the default value is
86
+ expensive to compute or produces side effects.
87
+
88
+ Args:
89
+ value: The optional value to unwrap.
90
+ default_fn: The function to call if the optional value is None.
91
+
92
+ Examples:
93
+ >>> opt.unwrap_or_else(0, lambda: 42)
94
+ 0
95
+ >>> opt.unwrap_or_else(None, lambda: 42)
96
+ 42
97
+
98
+ See also:
99
+ - :func:`opt.unwrap`
100
+ - :func:`opt.unwrap_or`
101
+ """
102
+ return value if value is not None else default_fn()
103
+
104
+
105
+ def expect[T](value: Optional[T], /, message: str) -> T:
106
+ """
107
+ Unwrap a value that may be None. If the value is None, raise a ValueError
108
+ with the given message.
109
+
110
+ Args:
111
+ value: The optional value to unwrap.
112
+ message: The message to include in the ValueError if the value is None.
113
+
114
+ Examples:
115
+ >>> opt.expect(42, "Value is None")
116
+ 42
117
+ >>> opt.expect(None, "Value is None")
118
+ Traceback (most recent call last):
119
+ ...
120
+ ValueError: Value is None
121
+
122
+ See also:
123
+ - :func:`opt.unwrap`
124
+ """
125
+ if value is None:
126
+ raise UnwrapError(message)
127
+ return value
128
+
129
+
130
+ @overload
131
+ def map[T, R](fn: Callable[[T], R], value: Optional[T], /) -> Optional[R]: ...
132
+
133
+
134
+ @overload
135
+ def map[T1, T2, R](
136
+ fn: Callable[[T1, T2], R], value: Optional[T1], value2: Optional[T2], /
137
+ ) -> Optional[R]: ...
138
+
139
+
140
+ @overload
141
+ def map[T1, T2, T3, R](
142
+ fn: Callable[[T1, T2, T3], R],
143
+ value: Optional[T1],
144
+ value2: Optional[T2],
145
+ value3: Optional[T3],
146
+ /,
147
+ ) -> Optional[R]: ...
148
+
149
+
150
+ @overload
151
+ def map[T1, T2, T3, T4, R](
152
+ fn: Callable[[T1, T2, T3, T4], R],
153
+ x1: Optional[T1],
154
+ x2: Optional[T2],
155
+ x3: Optional[T3],
156
+ x4: Optional[T4],
157
+ /,
158
+ ) -> Optional[R]: ...
159
+
160
+
161
+ @overload
162
+ def map[T1, T2, T3, T4, T5, R](
163
+ fn: Callable[[T1, T2, T3, T4, T5], R],
164
+ x1: Optional[T1],
165
+ x2: Optional[T2],
166
+ x3: Optional[T3],
167
+ x4: Optional[T4],
168
+ x5: Optional[T5],
169
+ /,
170
+ ) -> Optional[R]: ...
171
+
172
+
173
+ @overload
174
+ def map[T1, T2, T3, T4, T5, T6, R](
175
+ fn: Callable[[T1, T2, T3, T4, T5, T6], R],
176
+ x1: Optional[T1],
177
+ x2: Optional[T2],
178
+ x3: Optional[T3],
179
+ x4: Optional[T4],
180
+ x5: Optional[T5],
181
+ x6: Optional[T6],
182
+ /,
183
+ ) -> Optional[R]: ...
184
+
185
+
186
+ @overload
187
+ def map[T1, T2, T3, T4, T5, T6, T7, R](
188
+ fn: Callable[[T1, T2, T3, T4, T5, T6, T7], R],
189
+ x1: Optional[T1],
190
+ x2: Optional[T2],
191
+ x3: Optional[T3],
192
+ x4: Optional[T4],
193
+ x5: Optional[T5],
194
+ x6: Optional[T6],
195
+ x7: Optional[T7],
196
+ /,
197
+ ) -> Optional[R]: ...
198
+
199
+
200
+ @overload
201
+ def map[T1, T2, T3, T4, T5, T6, T7, T8, R](
202
+ fn: Callable[[T1, T2, T3, T4, T5, T6, T7, T8], R],
203
+ x1: Optional[T1],
204
+ x2: Optional[T2],
205
+ x3: Optional[T3],
206
+ x4: Optional[T4],
207
+ x5: Optional[T5],
208
+ x6: Optional[T6],
209
+ x7: Optional[T7],
210
+ x8: Optional[T8],
211
+ /,
212
+ ) -> Optional[R]: ...
213
+
214
+
215
+ def map(fn: Any, *values: Any) -> Any:
216
+ """
217
+ Apply the function to the values if none of them are None, otherwise return None.
218
+
219
+
220
+ Args:
221
+ fn:
222
+ The function to apply to the values if none of them are None.
223
+ x1, x2, ...:
224
+ The optional values to apply the function to.
225
+ It accepts any number of arguments (but only type checks up to 8).
226
+
227
+ Examples:
228
+ >>> opt.map(lambda x: x + 1, 41)
229
+ 42
230
+ >>> opt.map(lambda x, y: x + y, 40, 2)
231
+ 42
232
+ >>> opt.map(lambda x, y: x + y, 42, None)
233
+ None
234
+ """
235
+ if any(value is None for value in values):
236
+ return None
237
+ return fn(*values)
238
+
239
+
240
+ @overload
241
+ def coalesce[T](values: Iterable[Optional[T]], /) -> Optional[T]: ...
242
+
243
+
244
+ @overload
245
+ def coalesce[T](x: Optional[T], /, *rest: Optional[T]) -> Optional[T]: ...
246
+
247
+
248
+ def coalesce(*values: Any) -> Any:
249
+ """
250
+ Return the first value that is not None, or None if all values are None.
251
+
252
+ Examples:
253
+ >>> opt.coalesce(None, None, 42, 43, ...)
254
+ 42
255
+ >>> opt.coalesce(None, None)
256
+ None
257
+ >>> opt.coalesce([None, None, 42, 43, ...])
258
+ 42
259
+ """
260
+ if len(values) == 1:
261
+ values = values[0]
262
+ for value in values:
263
+ if value is not None:
264
+ return value
265
+ return None
266
+
267
+
268
+ def values[T](seq: Iterable[Optional[T]], /) -> Iterable[T]:
269
+ """
270
+ Return an iterable of the non-None values in the given sequence.
271
+
272
+ Args:
273
+ seq: The sequence of options to filter.
274
+
275
+ Examples:
276
+ >>> list(opt.values([1, None, 2, None, 3]))
277
+ [1, 2, 3]
278
+ >>> list(opt.values([None, None]))
279
+ []
280
+ """
281
+ return (value for value in seq if value is not None)
282
+
283
+
284
+ @overload
285
+ def together[T1, T2](
286
+ x1: Optional[T1], x2: Optional[T2], /
287
+ ) -> tuple[Optional[T1], Optional[T2]]: ...
288
+
289
+
290
+ @overload
291
+ def together[T1, T2, T3](
292
+ x1: Optional[T1],
293
+ x2: Optional[T2],
294
+ x3: Optional[T3],
295
+ /,
296
+ ) -> tuple[Optional[T1], Optional[T2], Optional[T3]]: ...
297
+
298
+
299
+ @overload
300
+ def together[T1, T2, T3, T4](
301
+ x1: Optional[T1],
302
+ x2: Optional[T2],
303
+ x3: Optional[T3],
304
+ x4: Optional[T4],
305
+ /,
306
+ ) -> tuple[Optional[T1], Optional[T2], Optional[T3], Optional[T4]]: ...
307
+
308
+
309
+ @overload
310
+ def together[T1, T2, T3, T4, T5](
311
+ x1: Optional[T1],
312
+ x2: Optional[T2],
313
+ x3: Optional[T3],
314
+ x4: Optional[T4],
315
+ x5: Optional[T5],
316
+ /,
317
+ ) -> tuple[Optional[T1], Optional[T2], Optional[T3], Optional[T4], Optional[T5]]: ...
318
+
319
+
320
+ @overload
321
+ def together[T1, T2, T3, T4, T5, T6](
322
+ x1: Optional[T1],
323
+ x2: Optional[T2],
324
+ x3: Optional[T3],
325
+ x4: Optional[T4],
326
+ x5: Optional[T5],
327
+ x6: Optional[T6],
328
+ /,
329
+ ) -> tuple[
330
+ Optional[T1], Optional[T2], Optional[T3], Optional[T4], Optional[T5], Optional[T6]
331
+ ]: ...
332
+
333
+
334
+ @overload
335
+ def together[T1, T2, T3, T4, T5, T6, T7](
336
+ x1: Optional[T1],
337
+ x2: Optional[T2],
338
+ x3: Optional[T3],
339
+ x4: Optional[T4],
340
+ x5: Optional[T5],
341
+ x6: Optional[T6],
342
+ x7: Optional[T7],
343
+ /,
344
+ ) -> tuple[
345
+ Optional[T1],
346
+ Optional[T2],
347
+ Optional[T3],
348
+ Optional[T4],
349
+ Optional[T5],
350
+ Optional[T6],
351
+ Optional[T7],
352
+ ]: ...
353
+
354
+
355
+ @overload
356
+ def together[T1, T2, T3, T4, T5, T6, T7, T8](
357
+ x1: Optional[T1],
358
+ x2: Optional[T2],
359
+ x3: Optional[T3],
360
+ x4: Optional[T4],
361
+ x5: Optional[T5],
362
+ x6: Optional[T6],
363
+ x7: Optional[T7],
364
+ x8: Optional[T8],
365
+ /,
366
+ ) -> tuple[
367
+ Optional[T1],
368
+ Optional[T2],
369
+ Optional[T3],
370
+ Optional[T4],
371
+ Optional[T5],
372
+ Optional[T6],
373
+ Optional[T7],
374
+ Optional[T8],
375
+ ]: ...
376
+
377
+
378
+ def together(*args: Any) -> Any:
379
+ """
380
+ Return a tuple of nones if any argument is None, otherwise return the
381
+ arguments as a tuple.
382
+
383
+ Args:
384
+ x1, x2, ...:
385
+ The optional values to check.
386
+ It accepts any number of arguments (but only type checks up to 8).
387
+
388
+ Examples:
389
+ >>> opt.together(1, 2, 3)
390
+ (1, 2, 3)
391
+ >>> opt.together(1, 2, None)
392
+ (None, None, None)
393
+
394
+ See also:
395
+ - :func:`zip`, which returns None if any argument is None instead of a tuple of nones.
396
+ """
397
+ if any(arg is None for arg in args):
398
+ return tuple(None for _ in args)
399
+ return args
400
+
401
+
402
+ @overload
403
+ def zip[T1, T2](x1: T1 | None, x2: T2 | None, /) -> tuple[T1, T2] | None: ...
404
+
405
+
406
+ @overload
407
+ def zip[T1, T2, T3](
408
+ x1: Optional[T1],
409
+ x2: Optional[T2],
410
+ x3: Optional[T3],
411
+ /,
412
+ ) -> Optional[tuple[T1, T2, T3]]: ...
413
+
414
+
415
+ @overload
416
+ def zip[T1, T2, T3, T4](
417
+ x1: Optional[T1],
418
+ x2: Optional[T2],
419
+ x3: Optional[T3],
420
+ x4: Optional[T4],
421
+ /,
422
+ ) -> Optional[tuple[T1, T2, T3, T4]]: ...
423
+
424
+
425
+ @overload
426
+ def zip[T1, T2, T3, T4, T5](
427
+ x1: Optional[T1],
428
+ x2: Optional[T2],
429
+ x3: Optional[T3],
430
+ x4: Optional[T4],
431
+ x5: Optional[T5],
432
+ /,
433
+ ) -> Optional[tuple[T1, T2, T3, T4, T5]]: ...
434
+
435
+
436
+ @overload
437
+ def zip[T1, T2, T3, T4, T5, T6](
438
+ x1: Optional[T1],
439
+ x2: Optional[T2],
440
+ x3: Optional[T3],
441
+ x4: Optional[T4],
442
+ x5: Optional[T5],
443
+ x6: Optional[T6],
444
+ /,
445
+ ) -> Optional[tuple[T1, T2, T3, T4, T5, T6]]: ...
446
+
447
+
448
+ @overload
449
+ def zip[T1, T2, T3, T4, T5, T6, T7](
450
+ x1: Optional[T1],
451
+ x2: Optional[T2],
452
+ x3: Optional[T3],
453
+ x4: Optional[T4],
454
+ x5: Optional[T5],
455
+ x6: Optional[T6],
456
+ x7: Optional[T7],
457
+ /,
458
+ ) -> Optional[tuple[T1, T2, T3, T4, T5, T6, T7]]: ...
459
+
460
+
461
+ @overload
462
+ def zip[T1, T2, T3, T4, T5, T6, T7, T8](
463
+ x1: Optional[T1],
464
+ x2: Optional[T2],
465
+ x3: Optional[T3],
466
+ x4: Optional[T4],
467
+ x5: Optional[T5],
468
+ x6: Optional[T6],
469
+ x7: Optional[T7],
470
+ x8: Optional[T8],
471
+ /,
472
+ ) -> Optional[tuple[T1, T2, T3, T4, T5, T6, T7, T8]]: ...
473
+
474
+
475
+ def zip(*args: Any) -> Any:
476
+ """
477
+ Return a tuple if no argument is None and None otherwise.
478
+
479
+ Args:
480
+ x1, x2, ...:
481
+ The optional values to check.
482
+ It accepts any number of arguments (but only type checks up to 8).
483
+
484
+ Examples:
485
+ >>> opt.zip(1, 2, 3)
486
+ (1, 2, 3)
487
+ >>> opt.zip(1, 2, None)
488
+ None
489
+
490
+ See also:
491
+ - :func:`together`, which returns a tuple of nones if any argument is None instead of None.
492
+ """
493
+ if any(arg is None for arg in args):
494
+ return None
495
+ return args
496
+
497
+
498
+ def elements[T](value: T | None) -> Iterable[T]:
499
+ """
500
+ Yield nothing or the optional value, if it exists.
501
+
502
+ Args:
503
+ value: The optional value to yield.
504
+
505
+ Examples:
506
+ >>> list(opt.elements(42))
507
+ [42]
508
+ >>> list(opt.elements(None))
509
+ []
510
+ """
511
+ if value is not None:
512
+ yield value
513
+
514
+
515
+ def iter[T](value: Iterable[T] | None) -> Iterable[T]:
516
+ """
517
+ Yield nothing or the elements of an iterable.
518
+
519
+ Args:
520
+ value: The optional iterable to yield from.
521
+
522
+ Examples:
523
+ >>> list(opt.iter([42]))
524
+ [42]
525
+ >>> list(opt.iter(None))
526
+ []
527
+ """
528
+ return iter(()) if value is None else iter(value)
529
+
530
+
531
+ def call[**P, R](
532
+ fn: Optional[Callable[P, R]], *args: P.args, **kwargs: P.kwargs
533
+ ) -> Optional[R]:
534
+ """
535
+ Call the optional function with the given arguments or return None.
536
+
537
+ Args:
538
+ fn: The optional function to call.
539
+ *args: The positional arguments to pass to the function.
540
+ **kwargs: The keyword arguments to pass to the function.
541
+
542
+ Examples:
543
+ >>> opt.call(lambda x: x + 1, 41)
544
+ 42
545
+ >>> opt.call(None, 40, 2)
546
+ None
547
+ """
548
+ return fn(*args, **kwargs) if fn is not None else None
549
+
550
+
551
+ def getattr[T, R: type](
552
+ obj: Optional[T], attr: str, *, type: type[R] = _any(None)
553
+ ) -> Optional[R]:
554
+ """
555
+ Get the attribute of an object if it is not None, otherwise return None.
556
+
557
+ It raises an AttributeError if the attribute do not exist.
558
+
559
+ You can optionally specify the expected result type to get better type checking.
560
+
561
+ Args:
562
+ obj: The optional object to get the attribute from.
563
+ attr: The name of the attribute to get.
564
+ type: The expected type of the attribute (optional).
565
+
566
+ Examples:
567
+ >>> opt.getattr(42, "real")
568
+ 42
569
+ >>> opt.getattr(None, "imag")
570
+ None
571
+ """
572
+ if obj is None:
573
+ return None
574
+
575
+ value = builtins.getattr(obj, attr)
576
+ if type is not None and not isinstance(value, type):
577
+ msg = (
578
+ f"Expected attribute {attr} to be of type {type}, "
579
+ f"got {builtins.type(value)}"
580
+ )
581
+ raise TypeError(msg)
582
+ return value # type: ignore
@@ -0,0 +1,83 @@
1
+ Metadata-Version: 2.4
2
+ Name: optionz
3
+ Version: 0.1.0
4
+ Summary: Some utility functions for Optional values.
5
+ Project-URL: Homepage, http://github.com/fabiomacedomendes/optionz
6
+ Project-URL: Repository, http://github.com/fabiomacedomendes/optionz
7
+ Author-email: Fábio Macêdo Mendes <fabiomacedomendes@gmail.com>
8
+ Maintainer-email: Fábio Macêdo Mendes <fabiomacedomendes@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Utilities
19
+ Requires-Python: >=3.13
20
+ Description-Content-Type: text/markdown
21
+
22
+ # Optionz
23
+
24
+ A nullable type is needed when there can be an absence of a value. Python uses
25
+ `None` to represent the empty value and `Optional[T]`, i.e., `T | None` to
26
+ represent a nullable `T`. The other option is to use exceptions, but they are
27
+ not expressed as values in the type system, which can be brittle and error-prone.
28
+
29
+ Python approach is a compromise between the "million dollar mistake",
30
+ in which `null` is a valid member of every type and the more structured
31
+ `Maybe[T]`, that models nullables as a tagged union of `Just[T]` and `Nothing`.
32
+
33
+ This library provides some utility functions for working with `Optional` values
34
+ in a more functional style, inspired by the `Maybe` monad in Haskell and Rust's
35
+ equivalent `Option` type.
36
+
37
+ The main philosophy is that we *do not* want to introduce a new type like
38
+ [returns](https://returns.readthedocs.io/) and other similar libraries.
39
+ Instead, we want to provide a similar functionality using plain `Optional` values
40
+ so that your code can adopt it incrementally without feeling like an alien in
41
+ the Python ecosystem.
42
+
43
+ As much as I like functional programming and the `Maybe` monad, I think Python's
44
+ approach is fine and offers most of the same static guarantees. The crucial
45
+ difference is that `Maybe` is a **tagged union** of two types and Python's
46
+ `Optional` is a **union of sets**. They behave mostly the same, but the latter do
47
+ not allow nesting: `Optional[Optional[T]]` flattens to `Optional[T]`, while
48
+ `Maybe[Maybe[T]]` is a whole new type. This is a difference that rarely
49
+ matters in practice, and I don't any anyone is clearly superior to the other.
50
+
51
+
52
+ ## Installation
53
+
54
+ Install Optionz using pip/uv/poetry whatever you like. For example:
55
+
56
+ ```bash
57
+ pip install optionz
58
+ ```
59
+
60
+ Optionz consists of a single file, so you can also just copy `opt.py` to your
61
+ project and import it from there. It do not define any new type so there is
62
+ no conflict with code that import vs ones that vendorize it.
63
+
64
+
65
+ ## Usage
66
+
67
+ Import `opt` and use the functions as needed.
68
+
69
+ ```python
70
+ import opt
71
+
72
+ opt.unwrap(42) # 42
73
+ opt.unwrap(None) # raises ValueError
74
+ ```
75
+
76
+ ## Documentation
77
+
78
+ The documentation is available at https://optionz.rtfd.io/ and includes more
79
+ examples and explanations of the functions provided by the library.
80
+
81
+ ## License
82
+
83
+ Optionz is licensed under the MIT License. See [LICENSE](LICENSE) for more details.
@@ -0,0 +1,5 @@
1
+ opt.py,sha256=B86FCvpiW1jOE0yetUo3IAHYggTv8rrM7-RvwDA8cmY,13032
2
+ optionz-0.1.0.dist-info/METADATA,sha256=L-B-Mt1egTbfhTBhQqfr69fq2z-c6WtSXcxDJ25L_74,3214
3
+ optionz-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
4
+ optionz-0.1.0.dist-info/licenses/LICENSE,sha256=H_RNRyTRIzs8yhgeBUPYa1U1MyJZUZtoWBBSDVAhE-c,1079
5
+ optionz-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026, Fábio Macêdo Mendes
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.