typing-arguments 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.
- typing_arguments/__init__.py +4 -0
- typing_arguments/generic_arguments.py +235 -0
- typing_arguments/py.typed +0 -0
- typing_arguments-0.1.0.dist-info/METADATA +158 -0
- typing_arguments-0.1.0.dist-info/RECORD +7 -0
- typing_arguments-0.1.0.dist-info/WHEEL +4 -0
- typing_arguments-0.1.0.dist-info/licenses/LICENSE +20 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generic method to make type arguments of generic models available in the class.
|
|
3
|
+
|
|
4
|
+
Example:
|
|
5
|
+
-------
|
|
6
|
+
```python
|
|
7
|
+
T1 = TypeVar("T1")
|
|
8
|
+
T2 = TypeVar("T2", bound="SomeBaseClass")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Something(
|
|
12
|
+
GenericArgumentsMixin,
|
|
13
|
+
Generic[T1, T2],
|
|
14
|
+
):
|
|
15
|
+
t1 = typing_arg(T1)
|
|
16
|
+
t2 = typing_arg(T2)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
ConcreteClass = Something[str, SomeBaseClassChild]
|
|
20
|
+
ConcreteClass.t1 # str
|
|
21
|
+
ConcreteClass.t2 # SomeBaseClassChild
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
You may also use this with pydantic models:
|
|
25
|
+
```python
|
|
26
|
+
T1 = TypeVar("T1")
|
|
27
|
+
T2 = TypeVar("T2", bound="SomeBaseClass")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SomethingModel(
|
|
31
|
+
GenericArgumentsMixin,
|
|
32
|
+
BaseModel,
|
|
33
|
+
Generic[T1, T2],
|
|
34
|
+
):
|
|
35
|
+
t1: ClassVar = typing_arg(T1)
|
|
36
|
+
t2: ClassVar = typing_arg(T2)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
ConcreteClassModel = SomethingModel[str, SomeBaseClassChild]
|
|
40
|
+
ConcreteClassModel.t1 # str
|
|
41
|
+
ConcreteClassModel.t2 # SomeBaseClassChild
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Internally `GenericArgumentsMixin` will create a new attribute `__typing_arguments__`
|
|
45
|
+
inside the class and its instances. This attribute is a dictionary mapping the type
|
|
46
|
+
variables to their concrete types. This is useful if you want to access the type
|
|
47
|
+
arguments in a generic way.
|
|
48
|
+
|
|
49
|
+
The `typing_arg` function is a helper function to make the type arguments available
|
|
50
|
+
in the class and its instances using a nicely named attribute. This is just a
|
|
51
|
+
convenience function, as you can also access the type arguments directly from the
|
|
52
|
+
`__typing_arguments__` attribute.
|
|
53
|
+
|
|
54
|
+
**Note:** If you are using pydantic models you should use the `ClassVar` annotation
|
|
55
|
+
to ensure pydantic will not try to catch and validate the type arguments as normal
|
|
56
|
+
model fields.
|
|
57
|
+
|
|
58
|
+
You may also mix different generic base classes like so:
|
|
59
|
+
```python
|
|
60
|
+
T1 = TypeVar("T1")
|
|
61
|
+
T2 = TypeVar("T2", bound="SomeBaseClass")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Base1(
|
|
65
|
+
GenericArgumentsMixin,
|
|
66
|
+
Generic[T1],
|
|
67
|
+
):
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Base2(
|
|
72
|
+
GenericArgumentsMixin,
|
|
73
|
+
Generic[T2],
|
|
74
|
+
):
|
|
75
|
+
t2 = typing_arg(T2)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Something(
|
|
79
|
+
Base1[str],
|
|
80
|
+
Base2[SomeBaseClassChild],
|
|
81
|
+
):
|
|
82
|
+
t1 = typing_arg(T1)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
Something.t1 # str
|
|
86
|
+
Something.t2 # SomeBaseClassChild
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
In this example `Base1` and `Base2` are both generic base classes. `Base1` only
|
|
90
|
+
defines a type argument `T1` and `Base2` only defines a type argument `T2`. The
|
|
91
|
+
`Something` class inherits from both `Base1` and `Base2`. Note that `Base1` does
|
|
92
|
+
not define a simple accessor like `t1` using `typing_arg`, while `Base2` does. This
|
|
93
|
+
is not a problem and can be later added by `Something` using `typing_arg` as well.
|
|
94
|
+
|
|
95
|
+
You may encounter issues using the `typing_arg` function when using type validator
|
|
96
|
+
like mypy or your IDE. If so you might need to use `cast` to tell the type checker
|
|
97
|
+
you are sure about what you are doing. For example:
|
|
98
|
+
```python
|
|
99
|
+
T1 = TypeVar("T1", bound="SomeBaseClass")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class Something(
|
|
103
|
+
GenericArgumentsMixin,
|
|
104
|
+
Generic[T1],
|
|
105
|
+
):
|
|
106
|
+
t1 = cast(type[SomeBaseClass], typing_arg(T1))
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Note:** You will still need to use `ClassVar` when using pydantic models. This
|
|
110
|
+
might result in using the same type twice (inside `ClassVar` and `cast`).
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
import functools
|
|
114
|
+
import types
|
|
115
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast
|
|
116
|
+
from typing import _GenericAlias as TypingGenericAlias # pyright: ignore[reportAttributeAccessIssue]
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
from pydantic import BaseModel as _PydanticBaseModel # pyright: ignore[reportAssignmentType]
|
|
120
|
+
except ImportError: # pragma: no cover
|
|
121
|
+
# Provide fake pydantic base model
|
|
122
|
+
class _PydanticBaseModel:
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
GenericTrackerMixinT = TypeVar("GenericTrackerMixinT")
|
|
126
|
+
|
|
127
|
+
TYPING_ATTRIBUTE_NAME = "__typing_arguments__"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class GenericArgumentsMixin:
|
|
131
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
132
|
+
# Same as TYPING_ATTRIBUTE_NAME
|
|
133
|
+
__typing_arguments__: dict[TypeVar, type[Any]]
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
@functools.lru_cache(maxsize=None, typed=True)
|
|
137
|
+
def __class_getitem__(
|
|
138
|
+
cls: type[GenericTrackerMixinT],
|
|
139
|
+
params: type[Any] | tuple[type[Any], ...],
|
|
140
|
+
) -> type[Any]:
|
|
141
|
+
if (
|
|
142
|
+
cls is GenericArgumentsMixin
|
|
143
|
+
and params
|
|
144
|
+
):
|
|
145
|
+
raise TypeError(
|
|
146
|
+
'Type parameters should be placed on typing.Generic, '
|
|
147
|
+
'not GenericArgumentsMixin',
|
|
148
|
+
)
|
|
149
|
+
if Generic not in cls.__bases__:
|
|
150
|
+
raise TypeError(
|
|
151
|
+
'Cannot provide type arguments to a non-generic class, must '
|
|
152
|
+
'inherit from typing.Generic first',
|
|
153
|
+
)
|
|
154
|
+
if not isinstance(params, tuple):
|
|
155
|
+
params = (params,)
|
|
156
|
+
|
|
157
|
+
base_cls = cls
|
|
158
|
+
if issubclass(cls, _PydanticBaseModel):
|
|
159
|
+
base_cls = super().__class_getitem__(params) # pyright: ignore[reportAttributeAccessIssue]
|
|
160
|
+
|
|
161
|
+
if len(cls.__parameters__) != len(params): # pyright: ignore[reportAttributeAccessIssue]
|
|
162
|
+
raise TypeError(
|
|
163
|
+
f'Type {cls.__name__} expects {len(cls.__parameters__)} ' # pyright: ignore[reportAttributeAccessIssue]
|
|
164
|
+
f'parameters, got {len(params)}',
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
typing_args = dict(zip(cls.__parameters__, params, strict=True)) # pyright: ignore[reportAttributeAccessIssue]
|
|
168
|
+
if hasattr(cls, TYPING_ATTRIBUTE_NAME):
|
|
169
|
+
typing_args = {
|
|
170
|
+
**getattr(cls, TYPING_ATTRIBUTE_NAME),
|
|
171
|
+
**typing_args,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
typed_cls = cast(
|
|
175
|
+
type[GenericArgumentsMixin],
|
|
176
|
+
types.new_class(
|
|
177
|
+
f"Typed{cls.__name__}",
|
|
178
|
+
(base_cls,),
|
|
179
|
+
{},
|
|
180
|
+
lambda ns: ns.update({TYPING_ATTRIBUTE_NAME: typing_args}),
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if issubclass(cls, _PydanticBaseModel):
|
|
185
|
+
return typed_cls
|
|
186
|
+
|
|
187
|
+
typed_alias = TypingGenericAlias(typed_cls, params)
|
|
188
|
+
setattr(typed_alias, TYPING_ATTRIBUTE_NAME, typing_args)
|
|
189
|
+
return typed_alias
|
|
190
|
+
|
|
191
|
+
def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None:
|
|
192
|
+
super().__init_subclass__(*args, **kwargs)
|
|
193
|
+
|
|
194
|
+
if not hasattr(cls, TYPING_ATTRIBUTE_NAME):
|
|
195
|
+
setattr(cls, TYPING_ATTRIBUTE_NAME, {})
|
|
196
|
+
|
|
197
|
+
base_typing_args = {}
|
|
198
|
+
typing_args = getattr(cls, TYPING_ATTRIBUTE_NAME)
|
|
199
|
+
for base in reversed(cls.__bases__):
|
|
200
|
+
if hasattr(base, TYPING_ATTRIBUTE_NAME):
|
|
201
|
+
base_typing_args.update(getattr(base, TYPING_ATTRIBUTE_NAME))
|
|
202
|
+
|
|
203
|
+
setattr(
|
|
204
|
+
cls, TYPING_ATTRIBUTE_NAME, {
|
|
205
|
+
**base_typing_args,
|
|
206
|
+
**typing_args,
|
|
207
|
+
},
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class typing_arg: # noqa
|
|
212
|
+
__slots__ = ("type_argument",)
|
|
213
|
+
|
|
214
|
+
def __init__(self, type_argument: TypeVar, /) -> None:
|
|
215
|
+
self.type_argument = type_argument
|
|
216
|
+
|
|
217
|
+
def __get__(
|
|
218
|
+
self,
|
|
219
|
+
obj: GenericArgumentsMixin,
|
|
220
|
+
obj_class: type[GenericArgumentsMixin] | None = None,
|
|
221
|
+
) -> type[Any]:
|
|
222
|
+
if obj_class is None: # pragma: no cover
|
|
223
|
+
obj_class = obj.__class__
|
|
224
|
+
|
|
225
|
+
if not hasattr(obj_class, TYPING_ATTRIBUTE_NAME):
|
|
226
|
+
raise TypeError(
|
|
227
|
+
f"{obj_class} seems not be be using GenericArgumentsMixin or "
|
|
228
|
+
f"no arguments were provided",
|
|
229
|
+
)
|
|
230
|
+
if self.type_argument not in getattr(obj_class, TYPING_ATTRIBUTE_NAME):
|
|
231
|
+
raise TypeError(
|
|
232
|
+
f"Type argument {self.type_argument} not found in {obj_class}",
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
return getattr(obj_class, TYPING_ATTRIBUTE_NAME)[self.type_argument]
|
|
File without changes
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: typing-arguments
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Store references of your typing arguments to be available at runtime.
|
|
5
|
+
Project-URL: Repository, https://github.com/team23/typing-arguments
|
|
6
|
+
Author-email: TEAM23 GmbH <info@team23.de>
|
|
7
|
+
License: The MIT License (MIT)
|
|
8
|
+
Copyright (c) 2024 TEAM23 GmbH
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
21
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
22
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
23
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
24
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
25
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
|
26
|
+
OR OTHER DEALINGS IN THE SOFTWARE.
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Provides-Extra: pydantic
|
|
29
|
+
Requires-Dist: pydantic<3.0.0,>=2.0.0; extra == 'pydantic'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# `typing-arguments`
|
|
33
|
+
|
|
34
|
+
Typing arguments using the `Generic` base class in python are great, but they lack the ability to
|
|
35
|
+
easily access the type arguments at runtime. This library provides a mixin class that can be used
|
|
36
|
+
to make type arguments available in the class and its instances.
|
|
37
|
+
|
|
38
|
+
This is also true for classes based on `pydantic.BaseModel` for pydantic > 2.x.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
Just use `pip install typing-arguments` to install the library.
|
|
43
|
+
|
|
44
|
+
**Note:** `typing-arguments` is tested on Python `3.10`, `3.11`, `3.12` and `3.13`. This is also ensured running
|
|
45
|
+
all tests on all those versions using `tox`.
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
### Quick Example
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
T1 = TypeVar("T1")
|
|
53
|
+
T2 = TypeVar("T2", bound="SomeBaseClass")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Something(
|
|
57
|
+
GenericArgumentsMixin,
|
|
58
|
+
Generic[T1, T2],
|
|
59
|
+
):
|
|
60
|
+
t1 = typing_arg(T1)
|
|
61
|
+
t2 = typing_arg(T2)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
ConcreteClass = Something[str, SomeBaseClassChild]
|
|
65
|
+
ConcreteClass.t1 # str
|
|
66
|
+
ConcreteClass.t2 # SomeBaseClassChild
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Hint:** You may also use this with pydantic models:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
T1 = TypeVar("T1")
|
|
73
|
+
T2 = TypeVar("T2", bound="SomeBaseClass")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class SomethingModel(
|
|
77
|
+
GenericArgumentsMixin,
|
|
78
|
+
BaseModel,
|
|
79
|
+
Generic[T1, T2],
|
|
80
|
+
):
|
|
81
|
+
t1: ClassVar = typing_arg(T1)
|
|
82
|
+
t2: ClassVar = typing_arg(T2)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
ConcreteClassModel = SomethingModel[str, SomeBaseClassChild]
|
|
86
|
+
ConcreteClassModel.t1 # str
|
|
87
|
+
ConcreteClassModel.t2 # SomeBaseClassChild
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Internally `GenericArgumentsMixin` will create a new attribute `__typing_arguments__`
|
|
91
|
+
inside the class and its instances. This attribute is a dictionary mapping the type
|
|
92
|
+
variables to their concrete types. This is useful if you want to access the type
|
|
93
|
+
arguments in a generic way.
|
|
94
|
+
|
|
95
|
+
The `typing_arg` function is a helper function to make the type arguments available
|
|
96
|
+
in the class and its instances using a nicely named attribute. This is just a
|
|
97
|
+
convenience function, as you can also access the type arguments directly from the
|
|
98
|
+
`__typing_arguments__` attribute.
|
|
99
|
+
|
|
100
|
+
**Note:** If you are using pydantic models you should use the `ClassVar` annotation
|
|
101
|
+
to ensure pydantic will not try to catch and validate the type arguments as normal
|
|
102
|
+
model fields.
|
|
103
|
+
|
|
104
|
+
You may also mix different generic base classes like so:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
T1 = TypeVar("T1")
|
|
108
|
+
T2 = TypeVar("T2", bound="SomeBaseClass")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class Base1(
|
|
112
|
+
GenericArgumentsMixin,
|
|
113
|
+
Generic[T1],
|
|
114
|
+
):
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class Base2(
|
|
119
|
+
GenericArgumentsMixin,
|
|
120
|
+
Generic[T2],
|
|
121
|
+
):
|
|
122
|
+
t2 = typing_arg(T2)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class Something(
|
|
126
|
+
Base1[str],
|
|
127
|
+
Base2[SomeBaseClassChild],
|
|
128
|
+
):
|
|
129
|
+
t1 = typing_arg(T1)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
Something.t1 # str
|
|
133
|
+
Something.t2 # SomeBaseClassChild
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
In this example `Base1` and `Base2` are both generic base classes. `Base1` only
|
|
137
|
+
defines a type argument `T1` and `Base2` only defines a type argument `T2`. The
|
|
138
|
+
`Something` class inherits from both `Base1` and `Base2`. Note that `Base1` does
|
|
139
|
+
not define a simple accessor like `t1` using `typing_arg`, while `Base2` does. This
|
|
140
|
+
is not a problem and can be later added by `Something` using `typing_arg` as well.
|
|
141
|
+
|
|
142
|
+
You may encounter issues using the `typing_arg` function when using type validator
|
|
143
|
+
like mypy or your IDE. If so you might need to use `cast` to tell the type checker
|
|
144
|
+
you are sure about what you are doing. For example:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
T1 = TypeVar("T1", bound="SomeBaseClass")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class Something(
|
|
151
|
+
GenericArgumentsMixin,
|
|
152
|
+
Generic[T1],
|
|
153
|
+
):
|
|
154
|
+
t1 = cast(type[SomeBaseClass], typing_arg(T1))
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Note:** You will still need to use `ClassVar` when using pydantic models. This
|
|
158
|
+
might result in using the same type twice (inside `ClassVar` and `cast`).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
typing_arguments/__init__.py,sha256=V972KeXUuWIGb1BCOiOGb3XAtNzN7aMr2jDJLcG-4c8,133
|
|
2
|
+
typing_arguments/generic_arguments.py,sha256=HFnKoo3Y0Ji8mmf-SwO4DDVqIRO60fgT3DKNEF9bzvU,7261
|
|
3
|
+
typing_arguments/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
typing_arguments-0.1.0.dist-info/METADATA,sha256=6tlYTe56qfUVO2-ajFs24Vq9MWJS6VagLJLQ2kygh1U,5145
|
|
5
|
+
typing_arguments-0.1.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
6
|
+
typing_arguments-0.1.0.dist-info/licenses/LICENSE,sha256=OnyZNTSOmdt31eMGJMg3fPFUfGP32Y-nvqDTeZnLiCM,1077
|
|
7
|
+
typing_arguments-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
Copyright (c) 2024 TEAM23 GmbH
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
18
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
19
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
|
20
|
+
OR OTHER DEALINGS IN THE SOFTWARE.
|