dhi 1.1.1__cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.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.
- dhi/__init__.py +173 -0
- dhi/_dhi_native.cpython-313-aarch64-linux-gnu.so +0 -0
- dhi/_native.c +379 -0
- dhi/batch.py +236 -0
- dhi/constraints.py +358 -0
- dhi/datetime_types.py +108 -0
- dhi/fields.py +187 -0
- dhi/functional_validators.py +108 -0
- dhi/libsatya.so +0 -0
- dhi/model.py +658 -0
- dhi/networks.py +290 -0
- dhi/secret.py +105 -0
- dhi/special_types.py +359 -0
- dhi/types.py +345 -0
- dhi/validator.py +212 -0
- dhi-1.1.1.dist-info/METADATA +115 -0
- dhi-1.1.1.dist-info/RECORD +21 -0
- dhi-1.1.1.dist-info/WHEEL +6 -0
- dhi-1.1.1.dist-info/licenses/LICENSE +21 -0
- dhi-1.1.1.dist-info/top_level.txt +1 -0
- dhi.libs/libsatya-a22d98f4.so +0 -0
dhi/validator.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core validation classes for dhi
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import ctypes
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
# Try to import native extension first (fastest)
|
|
11
|
+
try:
|
|
12
|
+
from . import _dhi_native
|
|
13
|
+
HAS_NATIVE_EXT = True
|
|
14
|
+
except ImportError:
|
|
15
|
+
HAS_NATIVE_EXT = False
|
|
16
|
+
_dhi_native = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ValidationError(Exception):
|
|
20
|
+
"""Single validation error"""
|
|
21
|
+
def __init__(self, field: str, message: str):
|
|
22
|
+
self.field = field
|
|
23
|
+
self.message = message
|
|
24
|
+
super().__init__(f"{field}: {message}")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ValidationErrors(Exception):
|
|
28
|
+
"""Multiple validation errors"""
|
|
29
|
+
def __init__(self, errors: List[ValidationError]):
|
|
30
|
+
self.errors = errors
|
|
31
|
+
messages = "\n".join(str(e) for e in errors)
|
|
32
|
+
super().__init__(f"Validation failed:\n{messages}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class BoundedInt:
|
|
36
|
+
"""Integer with min/max bounds validation"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, min_val: int, max_val: int):
|
|
39
|
+
self.min_val = min_val
|
|
40
|
+
self.max_val = max_val
|
|
41
|
+
|
|
42
|
+
def validate(self, value: int) -> int:
|
|
43
|
+
"""Validate integer is within bounds"""
|
|
44
|
+
if not isinstance(value, int):
|
|
45
|
+
raise ValidationError("value", f"Expected int, got {type(value).__name__}")
|
|
46
|
+
|
|
47
|
+
# Use native extension (fastest) or ctypes (fast) or pure Python (fallback)
|
|
48
|
+
if HAS_NATIVE_EXT:
|
|
49
|
+
if not _dhi_native.validate_int(value, self.min_val, self.max_val):
|
|
50
|
+
if value < self.min_val:
|
|
51
|
+
raise ValidationError("value", f"Value {value} must be >= {self.min_val}")
|
|
52
|
+
else:
|
|
53
|
+
raise ValidationError("value", f"Value {value} must be <= {self.max_val}")
|
|
54
|
+
elif _zig.available:
|
|
55
|
+
if not _zig.validate_int(value, self.min_val, self.max_val):
|
|
56
|
+
if value < self.min_val:
|
|
57
|
+
raise ValidationError("value", f"Value {value} must be >= {self.min_val}")
|
|
58
|
+
else:
|
|
59
|
+
raise ValidationError("value", f"Value {value} must be <= {self.max_val}")
|
|
60
|
+
else:
|
|
61
|
+
# Fallback to pure Python
|
|
62
|
+
if value < self.min_val:
|
|
63
|
+
raise ValidationError("value", f"Value {value} must be >= {self.min_val}")
|
|
64
|
+
|
|
65
|
+
if value > self.max_val:
|
|
66
|
+
raise ValidationError("value", f"Value {value} must be <= {self.max_val}")
|
|
67
|
+
|
|
68
|
+
return value
|
|
69
|
+
|
|
70
|
+
def __call__(self, value: int) -> int:
|
|
71
|
+
"""Allow using as a callable validator"""
|
|
72
|
+
return self.validate(value)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class BoundedString:
|
|
76
|
+
"""String with length bounds validation"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, min_len: int, max_len: int):
|
|
79
|
+
self.min_len = min_len
|
|
80
|
+
self.max_len = max_len
|
|
81
|
+
|
|
82
|
+
def validate(self, value: str) -> str:
|
|
83
|
+
"""Validate string length is within bounds"""
|
|
84
|
+
if not isinstance(value, str):
|
|
85
|
+
raise ValidationError("value", f"Expected str, got {type(value).__name__}")
|
|
86
|
+
|
|
87
|
+
# Use native extension (fastest) or ctypes (fast) or pure Python (fallback)
|
|
88
|
+
if HAS_NATIVE_EXT:
|
|
89
|
+
if not _dhi_native.validate_string_length(value, self.min_len, self.max_len):
|
|
90
|
+
if len(value) < self.min_len:
|
|
91
|
+
raise ValidationError("value", f"String length {len(value)} must be >= {self.min_len}")
|
|
92
|
+
else:
|
|
93
|
+
raise ValidationError("value", f"String length {len(value)} must be <= {self.max_len}")
|
|
94
|
+
elif _zig.available:
|
|
95
|
+
if not _zig.validate_string_length(value, self.min_len, self.max_len):
|
|
96
|
+
if len(value) < self.min_len:
|
|
97
|
+
raise ValidationError("value", f"String length {len(value)} must be >= {self.min_len}")
|
|
98
|
+
else:
|
|
99
|
+
raise ValidationError("value", f"String length {len(value)} must be <= {self.max_len}")
|
|
100
|
+
else:
|
|
101
|
+
# Fallback to pure Python
|
|
102
|
+
if len(value) < self.min_len:
|
|
103
|
+
raise ValidationError("value", f"String length {len(value)} must be >= {self.min_len}")
|
|
104
|
+
|
|
105
|
+
if len(value) > self.max_len:
|
|
106
|
+
raise ValidationError("value", f"String length {len(value)} must be <= {self.max_len}")
|
|
107
|
+
|
|
108
|
+
return value
|
|
109
|
+
|
|
110
|
+
def __call__(self, value: str) -> str:
|
|
111
|
+
"""Allow using as a callable validator"""
|
|
112
|
+
return self.validate(value)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Email:
|
|
116
|
+
"""Email format validation"""
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def validate(value: str) -> str:
|
|
120
|
+
"""Validate email format (simple check)"""
|
|
121
|
+
if not isinstance(value, str):
|
|
122
|
+
raise ValidationError("value", f"Expected str, got {type(value).__name__}")
|
|
123
|
+
|
|
124
|
+
# Use native extension (fastest) or ctypes (fast) or pure Python (fallback)
|
|
125
|
+
if HAS_NATIVE_EXT:
|
|
126
|
+
if not _dhi_native.validate_email(value):
|
|
127
|
+
raise ValidationError("value", "Invalid email format (expected: local@domain)")
|
|
128
|
+
elif _zig.available:
|
|
129
|
+
if not _zig.validate_email(value):
|
|
130
|
+
raise ValidationError("value", "Invalid email format (expected: local@domain)")
|
|
131
|
+
else:
|
|
132
|
+
# Fallback to pure Python
|
|
133
|
+
if "@" not in value or "." not in value.split("@")[-1]:
|
|
134
|
+
raise ValidationError("value", "Invalid email format (expected: local@domain)")
|
|
135
|
+
|
|
136
|
+
return value
|
|
137
|
+
|
|
138
|
+
@staticmethod
|
|
139
|
+
def __call__(value: str) -> str:
|
|
140
|
+
"""Allow using as a callable validator"""
|
|
141
|
+
return Email.validate(value)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# TODO: Load Zig shared library for native performance
|
|
145
|
+
# This is a pure Python implementation for now
|
|
146
|
+
# Future: Use ctypes to call into libsatya.so for 100x+ speedup
|
|
147
|
+
|
|
148
|
+
class _ZigValidator:
|
|
149
|
+
"""Native Zig validator"""
|
|
150
|
+
|
|
151
|
+
def __init__(self):
|
|
152
|
+
self._lib = None
|
|
153
|
+
self._try_load_native()
|
|
154
|
+
|
|
155
|
+
def _try_load_native(self):
|
|
156
|
+
"""Try to load native Zig library"""
|
|
157
|
+
lib_path = Path(__file__).parent.parent.parent / "zig-out" / "lib"
|
|
158
|
+
|
|
159
|
+
# Try different library names
|
|
160
|
+
for name in ["libsatya.dylib", "libsatya.so", "satya.dll"]:
|
|
161
|
+
full_path = lib_path / name
|
|
162
|
+
if full_path.exists():
|
|
163
|
+
try:
|
|
164
|
+
self._lib = ctypes.CDLL(str(full_path))
|
|
165
|
+
|
|
166
|
+
# Define function signatures
|
|
167
|
+
self._lib.satya_validate_int.argtypes = [ctypes.c_int64, ctypes.c_int64, ctypes.c_int64]
|
|
168
|
+
self._lib.satya_validate_int.restype = ctypes.c_int32
|
|
169
|
+
|
|
170
|
+
self._lib.satya_validate_string_length.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.c_size_t]
|
|
171
|
+
self._lib.satya_validate_string_length.restype = ctypes.c_int32
|
|
172
|
+
|
|
173
|
+
self._lib.satya_validate_email.argtypes = [ctypes.c_char_p]
|
|
174
|
+
self._lib.satya_validate_email.restype = ctypes.c_int32
|
|
175
|
+
|
|
176
|
+
self._lib.satya_version.argtypes = []
|
|
177
|
+
self._lib.satya_version.restype = ctypes.c_char_p
|
|
178
|
+
|
|
179
|
+
version = self._lib.satya_version().decode('utf-8')
|
|
180
|
+
print(f"✅ Loaded native Zig library v{version}: {name}")
|
|
181
|
+
return
|
|
182
|
+
except Exception as e:
|
|
183
|
+
print(f"⚠️ Failed to load {name}: {e}")
|
|
184
|
+
|
|
185
|
+
print("ℹ️ Using pure Python implementation (slower)")
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def available(self) -> bool:
|
|
189
|
+
"""Check if native library is available"""
|
|
190
|
+
return self._lib is not None
|
|
191
|
+
|
|
192
|
+
def validate_int(self, value: int, min_val: int, max_val: int) -> bool:
|
|
193
|
+
"""Validate integer using native Zig"""
|
|
194
|
+
if not self.available:
|
|
195
|
+
return min_val <= value <= max_val
|
|
196
|
+
return bool(self._lib.satya_validate_int(value, min_val, max_val))
|
|
197
|
+
|
|
198
|
+
def validate_string_length(self, value: str, min_len: int, max_len: int) -> bool:
|
|
199
|
+
"""Validate string length using native Zig"""
|
|
200
|
+
if not self.available:
|
|
201
|
+
return min_len <= len(value) <= max_len
|
|
202
|
+
return bool(self._lib.satya_validate_string_length(value.encode('utf-8'), min_len, max_len))
|
|
203
|
+
|
|
204
|
+
def validate_email(self, value: str) -> bool:
|
|
205
|
+
"""Validate email using native Zig"""
|
|
206
|
+
if not self.available:
|
|
207
|
+
return "@" in value and "." in value.split("@")[-1]
|
|
208
|
+
return bool(self._lib.satya_validate_email(value.encode('utf-8')))
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# Global instance
|
|
212
|
+
_zig = _ZigValidator()
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dhi
|
|
3
|
+
Version: 1.1.1
|
|
4
|
+
Summary: Ultra-fast data validation for Python - 28M validations/sec, 3x faster than Rust alternatives
|
|
5
|
+
Author-email: Rach Pradhan <rach@rachpradhan.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/justrach/satya-zig
|
|
8
|
+
Project-URL: Documentation, https://github.com/justrach/satya-zig#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/justrach/satya-zig
|
|
10
|
+
Project-URL: Issues, https://github.com/justrach/satya-zig/issues
|
|
11
|
+
Keywords: validation,data-validation,pydantic,zig,performance,fast,validator,email,url,uuid,schema
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Other
|
|
25
|
+
Classifier: Operating System :: OS Independent
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-benchmark>=4.0; extra == "dev"
|
|
33
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# dhi - Ultra-Fast Data Validation for Python
|
|
38
|
+
|
|
39
|
+
**The fastest data validation library for Python.** Powered by Zig for maximum performance.
|
|
40
|
+
|
|
41
|
+
## 🚀 Performance
|
|
42
|
+
|
|
43
|
+
**28 million validations/sec** - 3x faster than satya (Rust), 3x faster than msgspec (C)
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
# Validate 10,000 users in 0.36ms
|
|
47
|
+
from dhi import _dhi_native
|
|
48
|
+
|
|
49
|
+
users = [{"name": "Alice", "email": "alice@example.com", "age": 25}, ...]
|
|
50
|
+
|
|
51
|
+
field_specs = {
|
|
52
|
+
'name': ('string', 2, 100),
|
|
53
|
+
'email': ('email',),
|
|
54
|
+
'age': ('int_positive',),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
results, valid_count = _dhi_native.validate_batch_direct(users, field_specs)
|
|
58
|
+
# 28M users/sec! 🔥
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## ✨ Features
|
|
62
|
+
|
|
63
|
+
- **�� Fastest**: 3x faster than satya (Rust) and msgspec (C)
|
|
64
|
+
- **🎯 24+ Validators**: Email, URL, UUID, IPv4, dates, numbers, strings
|
|
65
|
+
- **🔋 Zero Python Overhead**: C extension extracts directly from dicts
|
|
66
|
+
- **🌍 General Purpose**: Works with any dict structure
|
|
67
|
+
- **💪 Production Ready**: Thoroughly tested and benchmarked
|
|
68
|
+
|
|
69
|
+
## 📦 Installation
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install dhi
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 🎯 Quick Start
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from dhi import _dhi_native
|
|
79
|
+
|
|
80
|
+
users = [
|
|
81
|
+
{"name": "Alice", "email": "alice@example.com", "age": 25},
|
|
82
|
+
{"name": "Bob", "email": "bob@example.com", "age": 30},
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
field_specs = {
|
|
86
|
+
'name': ('string', 2, 100),
|
|
87
|
+
'email': ('email',),
|
|
88
|
+
'age': ('int_positive',),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
results, valid_count = _dhi_native.validate_batch_direct(users, field_specs)
|
|
92
|
+
print(f"Valid: {valid_count}/{len(users)}")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## �� Available Validators
|
|
96
|
+
|
|
97
|
+
### String: `email`, `url`, `uuid`, `ipv4`, `base64`, `iso_date`, `iso_datetime`, `string`
|
|
98
|
+
### Number: `int`, `int_gt`, `int_gte`, `int_lt`, `int_lte`, `int_positive`, `int_non_negative`, `int_multiple_of`
|
|
99
|
+
|
|
100
|
+
## 🏆 Benchmarks
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
dhi: 28M users/sec 🥇
|
|
104
|
+
satya: 9M users/sec (3.0x slower)
|
|
105
|
+
msgspec: 9M users/sec (3.1x slower)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 📝 License
|
|
109
|
+
|
|
110
|
+
MIT License - see LICENSE file
|
|
111
|
+
|
|
112
|
+
## 🔗 Links
|
|
113
|
+
|
|
114
|
+
- GitHub: https://github.com/justrach/satya-zig
|
|
115
|
+
- PyPI: https://pypi.org/project/dhi/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
dhi/batch.py,sha256=fBCCF4Eo3oU4ppXrZaCm4rwFDDa6iM1F3htzhKj02xY,7216
|
|
2
|
+
dhi/functional_validators.py,sha256=2-VTl8nX-DNpDUvOjzfevbJj5AAmc1MhdfAcTncmy8o,2966
|
|
3
|
+
dhi/_dhi_native.cpython-313-aarch64-linux-gnu.so,sha256=iBt4y3FddvhOufom13RYU_AEM7NiOxhuTeyusHKlHzs,198049
|
|
4
|
+
dhi/special_types.py,sha256=jInp2NijeTxj7ZVjtU00ZZUmV4DPeWAN8rlm4dbdaXk,12525
|
|
5
|
+
dhi/fields.py,sha256=vn1lKortQgxBe0KnLOhjFBmKUbDNxi7lvNv0kjVLYtc,6067
|
|
6
|
+
dhi/networks.py,sha256=CmspeR3GuBHzoWH2q2FqFuqrMKGK3DtO_V-TO826B3g,10123
|
|
7
|
+
dhi/types.py,sha256=zvDFdy9k9kUsL-5yQ3YJpX-QoZWcW7sv1GQUVcCtZxg,9885
|
|
8
|
+
dhi/validator.py,sha256=suqs8mF1lZ2-KzjF8gIHNt5X4kAbrupTMd1Lxpyx06Y,8453
|
|
9
|
+
dhi/libsatya.so,sha256=oi2Y9AFxPR2bJYAm0jb7oPeM_ac9ZSdwjKst3FU0bOc,89848
|
|
10
|
+
dhi/secret.py,sha256=hCWQ9gLLNxDxwEOtxMVOZ0TDkmtGDRfz6bNu3N_c3EE,2896
|
|
11
|
+
dhi/datetime_types.py,sha256=C0Gq9gW6gvv2U6dYTcqCNkkDHqNzqPbaqfQG2xL3GKQ,3786
|
|
12
|
+
dhi/model.py,sha256=-oP338Q5udfuar5n24ro5J9cDQDNw94VwcsKLkbKcVA,25532
|
|
13
|
+
dhi/_native.c,sha256=zWEXy3O0dMaPBTLBkh1k9efez5-CN9hTmYGrBWpL-W4,14316
|
|
14
|
+
dhi/__init__.py,sha256=aU-_6BemI32y5Bs0HFFv-_2vCcn8zzJ2BQwa9anvyl8,4752
|
|
15
|
+
dhi/constraints.py,sha256=pzzFbfoQzSx-y-RAvM5PfINHoQOGg6V545LajxZpDKA,10683
|
|
16
|
+
dhi.libs/libsatya-a22d98f4.so,sha256=SexQ3zPPR65XiDZxajyF99pKqkWSMJNv_Tq4McPcCmg,133105
|
|
17
|
+
dhi-1.1.1.dist-info/WHEEL,sha256=wXgLOoUZn6w376EcY-MIDI9QZ4oT7tzW-k6jucYkm10,154
|
|
18
|
+
dhi-1.1.1.dist-info/top_level.txt,sha256=iq71wPTnUMM4ZJ61Eg64c1msSlpnWAiWz66ZpD6ohLs,4
|
|
19
|
+
dhi-1.1.1.dist-info/METADATA,sha256=DtQQdaNTsr0XgbWBb2-ua-4sTRh2CGKYeLnUzeb1UkE,3605
|
|
20
|
+
dhi-1.1.1.dist-info/RECORD,,
|
|
21
|
+
dhi-1.1.1.dist-info/licenses/LICENSE,sha256=5NTWM6gyO_xYeJfEx0dbtR4g0g2axgUpCykd_BfCb8s,1069
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Rach Pradhan
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dhi
|
|
Binary file
|