tsrkit-types 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.
- tsrkit_types/__init__.py +76 -0
- tsrkit_types/bits.py +115 -0
- tsrkit_types/bool.py +41 -0
- tsrkit_types/bytes.py +102 -0
- tsrkit_types/choice.py +127 -0
- tsrkit_types/dictionary.py +145 -0
- tsrkit_types/enum.py +128 -0
- tsrkit_types/integers.py +203 -0
- tsrkit_types/itf/codable.py +82 -0
- tsrkit_types/null.py +43 -0
- tsrkit_types/option.py +30 -0
- tsrkit_types/sequences.py +212 -0
- tsrkit_types/string.py +66 -0
- tsrkit_types/struct.py +82 -0
- tsrkit_types-0.1.0.dist-info/METADATA +750 -0
- tsrkit_types-0.1.0.dist-info/RECORD +19 -0
- tsrkit_types-0.1.0.dist-info/WHEEL +5 -0
- tsrkit_types-0.1.0.dist-info/licenses/LICENSE +21 -0
- tsrkit_types-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tsrkit-types
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: High-performance Python library for type-safe binary serialization, JSON encoding, and data validation with zero dependencies
|
|
5
|
+
Author-email: chainscore-labs <hello@chainscore.finance>, prasad-kumkar <prasad@chainscore.finance>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/chainscore/tsrkit-types
|
|
8
|
+
Project-URL: Repository, https://github.com/chainscore/tsrkit-types
|
|
9
|
+
Project-URL: Issues, https://github.com/chainscore/tsrkit-types/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/chainscore/tsrkit-types#readme
|
|
11
|
+
Project-URL: Changelog, https://github.com/chainscore/tsrkit-types/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: serialization,binary,encoding,types,codable,json,validation,data-types,type-safety,zero-copy,protocol,struct,networking,performance,bytes,integers,strings
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Classifier: Topic :: System :: Networking
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
24
|
+
Classifier: Topic :: Database
|
|
25
|
+
Classifier: Topic :: Utilities
|
|
26
|
+
Classifier: Topic :: System :: Archiving
|
|
27
|
+
Classifier: Topic :: Communications
|
|
28
|
+
Classifier: Typing :: Typed
|
|
29
|
+
Requires-Python: >=3.12
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-timeout>=2.1.0; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest-xdist>=3.0.0; extra == "dev"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# TSRKit Types
|
|
42
|
+
|
|
43
|
+
Performant Python library for type-safe binary serialization, JSON encoding, and data validation with zero dependencies.
|
|
44
|
+
|
|
45
|
+
Perfect for network protocols, game state serialization, configuration files, and any application requiring efficient, validated data handling.
|
|
46
|
+
|
|
47
|
+
## Features
|
|
48
|
+
|
|
49
|
+
- **🔒 Type Safe**: Strong typing with runtime validation and type hints
|
|
50
|
+
- **âš¡ High Performance**: Efficient binary encoding with zero-copy operations where possible
|
|
51
|
+
- **📦 Zero Dependencies**: No external runtime dependencies
|
|
52
|
+
- **🔄 Dual Serialization**: Both binary and JSON serialization support
|
|
53
|
+
- **🧩 Generic Support**: Parameterized types for flexible, reusable code
|
|
54
|
+
- **🎯 Memory Efficient**: Minimal overhead, extends built-in Python types
|
|
55
|
+
- **🚀 Easy to Use**: Intuitive API with comprehensive type system
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install tsrkit-types
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Type Categories
|
|
64
|
+
|
|
65
|
+
### Integer Types
|
|
66
|
+
|
|
67
|
+
#### Unsigned Integers (`Uint`)
|
|
68
|
+
|
|
69
|
+
The `Uint` class provides both fixed-size and variable-size unsigned integers.
|
|
70
|
+
|
|
71
|
+
**Fixed-Size Integers:**
|
|
72
|
+
```python
|
|
73
|
+
from tsrkit_types.integers import Uint, U8, U16, U32, U64
|
|
74
|
+
|
|
75
|
+
# Pre-defined types
|
|
76
|
+
value = U8(255) # 8-bit unsigned integer (0-255)
|
|
77
|
+
value = U16(65535) # 16-bit unsigned integer (0-65535)
|
|
78
|
+
value = U32(42949) # 32-bit unsigned integer
|
|
79
|
+
value = U64(1844674) # 64-bit unsigned integer
|
|
80
|
+
|
|
81
|
+
# Dynamic size specification
|
|
82
|
+
U128 = Uint[128] # 128-bit unsigned integer
|
|
83
|
+
value = U128(123456789)
|
|
84
|
+
|
|
85
|
+
# Encoding/Decoding
|
|
86
|
+
encoded = value.encode() # Encode to bytes
|
|
87
|
+
decoded = U8.decode(encoded) # Decode from bytes
|
|
88
|
+
size = value.encode_size() # Get encoded size
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Variable-Size General Integers:**
|
|
92
|
+
```python
|
|
93
|
+
# General integers (supports up to 2^64 - 1 with variable encoding)
|
|
94
|
+
num = Uint(1000) # Variable-length encoding
|
|
95
|
+
encoded = num.encode()
|
|
96
|
+
decoded = Uint.decode(encoded)
|
|
97
|
+
|
|
98
|
+
# Arithmetic operations preserve type
|
|
99
|
+
a = U8(10)
|
|
100
|
+
b = U8(20)
|
|
101
|
+
result = a + b # result is U8(30)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Encoding Details:**
|
|
105
|
+
- Fixed-size integers use little-endian encoding
|
|
106
|
+
- Variable-size integers use a compact encoding scheme that optimizes for smaller values
|
|
107
|
+
- Values < 2^7 are encoded in 1 byte
|
|
108
|
+
- Larger values use a variable-length prefix encoding
|
|
109
|
+
|
|
110
|
+
### String Types
|
|
111
|
+
|
|
112
|
+
#### UTF-8 Strings (`String`)
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from tsrkit_types.string import String
|
|
116
|
+
|
|
117
|
+
# Creation
|
|
118
|
+
text = String("Hello, World!")
|
|
119
|
+
text = String("Unicode: 🚀🔥")
|
|
120
|
+
|
|
121
|
+
# Properties
|
|
122
|
+
length = len(text) # Character count
|
|
123
|
+
text_str = str(text) # Convert to Python str
|
|
124
|
+
|
|
125
|
+
# Encoding/Decoding
|
|
126
|
+
encoded = text.encode() # [length][utf8_bytes]
|
|
127
|
+
decoded = String.decode(encoded)
|
|
128
|
+
|
|
129
|
+
# JSON serialization
|
|
130
|
+
json_data = text.to_json() # Returns the string value
|
|
131
|
+
restored = String.from_json(json_data)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Encoding Format:**
|
|
135
|
+
- Length prefix (variable-length `Uint`) followed by UTF-8 bytes
|
|
136
|
+
- String length is measured in UTF-16 code units (like Python strings)
|
|
137
|
+
|
|
138
|
+
### Boolean Types
|
|
139
|
+
|
|
140
|
+
#### Boolean (`Bool`)
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from tsrkit_types.bool import Bool
|
|
144
|
+
|
|
145
|
+
# Creation
|
|
146
|
+
true_val = Bool(True)
|
|
147
|
+
false_val = Bool(False)
|
|
148
|
+
|
|
149
|
+
# Usage
|
|
150
|
+
if true_val: # Supports truthiness testing
|
|
151
|
+
print("It's true!")
|
|
152
|
+
|
|
153
|
+
# Encoding/Decoding
|
|
154
|
+
encoded = true_val.encode() # 1 byte: 0x01 or 0x00
|
|
155
|
+
decoded = Bool.decode(encoded)
|
|
156
|
+
|
|
157
|
+
# JSON serialization
|
|
158
|
+
json_str = true_val.to_json() # "true" or "false"
|
|
159
|
+
restored = Bool.from_json("true")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Null Types
|
|
163
|
+
|
|
164
|
+
#### Null and Nullable
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from tsrkit_types.null import Null, Nullable
|
|
168
|
+
|
|
169
|
+
# Null type
|
|
170
|
+
null_val = Null # Singleton null value
|
|
171
|
+
|
|
172
|
+
# Nullable wrapper for optional values
|
|
173
|
+
maybe_string = Nullable[String](String("hello"))
|
|
174
|
+
empty_val = Nullable[String]() # Contains null
|
|
175
|
+
|
|
176
|
+
# Check for null
|
|
177
|
+
if maybe_string.is_null():
|
|
178
|
+
print("Value is null")
|
|
179
|
+
else:
|
|
180
|
+
value = maybe_string.unwrap() # Get the wrapped value
|
|
181
|
+
|
|
182
|
+
# Encoding
|
|
183
|
+
encoded = maybe_string.encode() # 1 byte flag + optional value
|
|
184
|
+
decoded = Nullable[String].decode(encoded)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Choice and Option Types
|
|
188
|
+
|
|
189
|
+
#### Choice (Union Types)
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from tsrkit_types.choice import Choice
|
|
193
|
+
from tsrkit_types.integers import U8, U16
|
|
194
|
+
from tsrkit_types.string import String
|
|
195
|
+
|
|
196
|
+
# Anonymous choice
|
|
197
|
+
IntOrString = Choice[U8, String]
|
|
198
|
+
value = IntOrString(U8(42))
|
|
199
|
+
value = IntOrString(String("hello"))
|
|
200
|
+
|
|
201
|
+
# Switch the choice
|
|
202
|
+
value.set(String("world"))
|
|
203
|
+
inner = value.unwrap() # Get the inner value
|
|
204
|
+
|
|
205
|
+
# Named choice with custom keys
|
|
206
|
+
class Result(Choice):
|
|
207
|
+
success: String
|
|
208
|
+
error: U8
|
|
209
|
+
|
|
210
|
+
result = Result(String("OK"))
|
|
211
|
+
result = Result(U8(404), key="error")
|
|
212
|
+
|
|
213
|
+
# JSON serialization
|
|
214
|
+
json_data = result.to_json() # {"success": "OK"} or {"error": 404}
|
|
215
|
+
restored = Result.from_json(json_data)
|
|
216
|
+
|
|
217
|
+
# Encoding
|
|
218
|
+
encoded = result.encode() # [variant_index][value]
|
|
219
|
+
decoded = Result.decode(encoded)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### Option (Optional Values)
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
from tsrkit_types.option import Option
|
|
226
|
+
from tsrkit_types.integers import U32
|
|
227
|
+
|
|
228
|
+
# Optional value
|
|
229
|
+
opt_val = Option[U32](U32(100)) # Some value
|
|
230
|
+
empty_opt = Option[U32]() # None/Null
|
|
231
|
+
|
|
232
|
+
# Check if has value
|
|
233
|
+
if opt_val: # Truthiness test
|
|
234
|
+
value = opt_val.unwrap() # Get the U32 value
|
|
235
|
+
|
|
236
|
+
# Encoding (more efficient than general Choice)
|
|
237
|
+
encoded = opt_val.encode() # 1 byte tag + optional value
|
|
238
|
+
decoded = Option[U32].decode(encoded)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Container Types
|
|
242
|
+
|
|
243
|
+
#### Sequences (Arrays and Vectors)
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
from tsrkit_types.sequences import Array, Vector, TypedArray, TypedVector
|
|
247
|
+
from tsrkit_types.integers import U16
|
|
248
|
+
|
|
249
|
+
# Fixed-size array
|
|
250
|
+
FixedArray = Array[10] # Array of exactly 10 elements
|
|
251
|
+
arr = FixedArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
|
|
252
|
+
|
|
253
|
+
# Typed fixed-size array
|
|
254
|
+
TypedFixedArray = TypedArray[U16, 5] # 5 U16 elements
|
|
255
|
+
typed_arr = TypedFixedArray([U16(1), U16(2), U16(3), U16(4), U16(5)])
|
|
256
|
+
|
|
257
|
+
# Variable-size vector
|
|
258
|
+
DynamicVector = Vector[100] # Vector with max 100 elements
|
|
259
|
+
vec = DynamicVector([1, 2, 3])
|
|
260
|
+
|
|
261
|
+
# Typed variable-size vector
|
|
262
|
+
TypedDynamicVector = TypedVector[U16] # Vector of U16 elements
|
|
263
|
+
typed_vec = TypedDynamicVector([U16(1), U16(2)])
|
|
264
|
+
|
|
265
|
+
# Operations
|
|
266
|
+
vec.append(4) # Add element with validation
|
|
267
|
+
vec.extend([5, 6, 7]) # Add multiple elements
|
|
268
|
+
vec[0] = 100 # Set element with validation
|
|
269
|
+
|
|
270
|
+
# Encoding
|
|
271
|
+
encoded = typed_vec.encode() # [length?][element1][element2]...
|
|
272
|
+
decoded = TypedDynamicVector.decode(encoded)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Sequence Types:**
|
|
276
|
+
- `Array[N]`: Fixed size, any element type
|
|
277
|
+
- `Vector[max]`: Variable size up to max, any element type
|
|
278
|
+
- `TypedArray[T, N]`: Fixed size with typed elements
|
|
279
|
+
- `TypedVector[T]`: Variable size with typed elements
|
|
280
|
+
- `BoundedVector[min, max]`: Size constrained vector
|
|
281
|
+
- `TypedBoundedVector[T, min, max]`: Typed and size constrained
|
|
282
|
+
|
|
283
|
+
#### Dictionary
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
from tsrkit_types.dictionary import Dictionary
|
|
287
|
+
from tsrkit_types.string import String
|
|
288
|
+
from tsrkit_types.integers import U32
|
|
289
|
+
|
|
290
|
+
# Create dictionary type
|
|
291
|
+
StringToInt = Dictionary[String, U32]
|
|
292
|
+
data = StringToInt({
|
|
293
|
+
String("key1"): U32(100),
|
|
294
|
+
String("key2"): U32(200)
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
# Operations
|
|
298
|
+
data[String("key3")] = U32(300) # Add entry
|
|
299
|
+
value = data[String("key1")] # Get value
|
|
300
|
+
del data[String("key2")] # Remove entry
|
|
301
|
+
|
|
302
|
+
# Iteration
|
|
303
|
+
for key, value in data.items():
|
|
304
|
+
print(f"{key}: {value}")
|
|
305
|
+
|
|
306
|
+
# Encoding
|
|
307
|
+
encoded = data.encode() # [length][key1][value1][key2][value2]...
|
|
308
|
+
decoded = StringToInt.decode(encoded)
|
|
309
|
+
|
|
310
|
+
# JSON serialization
|
|
311
|
+
json_data = data.to_json() # {"key1": 100, "key3": 300}
|
|
312
|
+
restored = StringToInt.from_json(json_data)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Bytes Types
|
|
316
|
+
|
|
317
|
+
#### Raw Bytes
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
from tsrkit_types.bytes import Bytes
|
|
321
|
+
|
|
322
|
+
# Creation
|
|
323
|
+
data = Bytes(b"Hello, binary world!")
|
|
324
|
+
data = Bytes([0x01, 0x02, 0x03, 0x04])
|
|
325
|
+
|
|
326
|
+
# Operations
|
|
327
|
+
length = len(data) # Byte length
|
|
328
|
+
raw_bytes = bytes(data) # Convert to Python bytes
|
|
329
|
+
|
|
330
|
+
# Encoding
|
|
331
|
+
encoded = data.encode() # [length][raw_bytes]
|
|
332
|
+
decoded = Bytes.decode(encoded)
|
|
333
|
+
|
|
334
|
+
# JSON serialization (hex encoded)
|
|
335
|
+
json_str = data.to_json() # "48656c6c6f2c2062696e61727920776f726c6421"
|
|
336
|
+
restored = Bytes.from_json(json_str)
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
#### Bit Arrays
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
from tsrkit_types.bits import BitArray
|
|
343
|
+
|
|
344
|
+
# Creation
|
|
345
|
+
bits = BitArray([True, False, True, True, False])
|
|
346
|
+
bits = BitArray.from_hex("1A3F") # From hex string
|
|
347
|
+
bits = BitArray.from_int(42, 8) # From integer with bit width
|
|
348
|
+
|
|
349
|
+
# Operations
|
|
350
|
+
bit_val = bits[0] # Get bit at index
|
|
351
|
+
bits[1] = True # Set bit at index
|
|
352
|
+
bits.append(False) # Add bit
|
|
353
|
+
bits.extend([True, False]) # Add multiple bits
|
|
354
|
+
|
|
355
|
+
# Conversion
|
|
356
|
+
hex_str = bits.to_hex() # Convert to hex string
|
|
357
|
+
int_val = bits.to_int() # Convert to integer
|
|
358
|
+
|
|
359
|
+
# Encoding
|
|
360
|
+
encoded = bits.encode() # [length][packed_bits]
|
|
361
|
+
decoded = BitArray.decode(encoded)
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Enumeration Types
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
from tsrkit_types.enum import Enum
|
|
368
|
+
|
|
369
|
+
# Define enum
|
|
370
|
+
class Color(Enum):
|
|
371
|
+
RED = 0
|
|
372
|
+
GREEN = 1
|
|
373
|
+
BLUE = 2
|
|
374
|
+
|
|
375
|
+
# Usage
|
|
376
|
+
color = Color.RED
|
|
377
|
+
color_val = Color(1) # Color.GREEN
|
|
378
|
+
|
|
379
|
+
# String conversion
|
|
380
|
+
name = color.name # "RED"
|
|
381
|
+
value = color.value # 0
|
|
382
|
+
|
|
383
|
+
# Encoding
|
|
384
|
+
encoded = color.encode() # Variable-length uint encoding
|
|
385
|
+
decoded = Color.decode(encoded)
|
|
386
|
+
|
|
387
|
+
# JSON serialization
|
|
388
|
+
json_data = color.to_json() # "RED"
|
|
389
|
+
restored = Color.from_json("GREEN")
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Structured Types
|
|
393
|
+
|
|
394
|
+
#### Struct Decorator
|
|
395
|
+
|
|
396
|
+
```python
|
|
397
|
+
from tsrkit_types.struct import struct
|
|
398
|
+
from tsrkit_types.string import String
|
|
399
|
+
from tsrkit_types.integers import U8, U32
|
|
400
|
+
from dataclasses import field
|
|
401
|
+
|
|
402
|
+
@struct
|
|
403
|
+
class Person:
|
|
404
|
+
name: String
|
|
405
|
+
age: U8
|
|
406
|
+
|
|
407
|
+
@struct
|
|
408
|
+
class Employee:
|
|
409
|
+
person: Person
|
|
410
|
+
employee_id: U32
|
|
411
|
+
department: String = field(metadata={"default": String("Unknown")})
|
|
412
|
+
|
|
413
|
+
# Creation
|
|
414
|
+
person = Person(name=String("John Doe"), age=U8(30))
|
|
415
|
+
employee = Employee(
|
|
416
|
+
person=person,
|
|
417
|
+
employee_id=U32(12345),
|
|
418
|
+
department=String("Engineering")
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
# Access fields
|
|
422
|
+
print(employee.person.name) # "John Doe"
|
|
423
|
+
print(employee.employee_id) # 12345
|
|
424
|
+
|
|
425
|
+
# Encoding/Decoding
|
|
426
|
+
encoded = employee.encode() # Concatenated field encodings
|
|
427
|
+
decoded = Employee.decode(encoded)
|
|
428
|
+
|
|
429
|
+
# JSON serialization with custom field names
|
|
430
|
+
@struct
|
|
431
|
+
class CustomPerson:
|
|
432
|
+
name: String = field(metadata={"name": "full_name"})
|
|
433
|
+
age: U8
|
|
434
|
+
|
|
435
|
+
person = CustomPerson(name=String("Jane"), age=U8(25))
|
|
436
|
+
json_data = person.to_json() # {"full_name": "Jane", "age": 25}
|
|
437
|
+
restored = CustomPerson.from_json({"full_name": "Jane", "age": 25})
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**Struct Features:**
|
|
441
|
+
- Automatic `Codable` implementation
|
|
442
|
+
- Field validation and type checking
|
|
443
|
+
- Default values via metadata
|
|
444
|
+
- Custom JSON field mapping
|
|
445
|
+
- Inheritance support
|
|
446
|
+
- Frozen/immutable variants
|
|
447
|
+
|
|
448
|
+
## Advanced Usage
|
|
449
|
+
|
|
450
|
+
### Custom Types
|
|
451
|
+
|
|
452
|
+
Implement your own `Codable` types:
|
|
453
|
+
|
|
454
|
+
```python
|
|
455
|
+
from tsrkit_types.itf.codable import Codable
|
|
456
|
+
from typing import Tuple, Union
|
|
457
|
+
|
|
458
|
+
class Point3D(Codable):
|
|
459
|
+
def __init__(self, x: float, y: float, z: float):
|
|
460
|
+
self.x, self.y, self.z = x, y, z
|
|
461
|
+
|
|
462
|
+
def encode_size(self) -> int:
|
|
463
|
+
return 24 # 3 doubles = 24 bytes
|
|
464
|
+
|
|
465
|
+
def encode_into(self, buffer: bytearray, offset: int = 0) -> int:
|
|
466
|
+
import struct
|
|
467
|
+
struct.pack_into('<ddd', buffer, offset, self.x, self.y, self.z)
|
|
468
|
+
return 24
|
|
469
|
+
|
|
470
|
+
@classmethod
|
|
471
|
+
def decode_from(cls, buffer: Union[bytes, bytearray, memoryview],
|
|
472
|
+
offset: int = 0) -> Tuple['Point3D', int]:
|
|
473
|
+
import struct
|
|
474
|
+
x, y, z = struct.unpack_from('<ddd', buffer, offset)
|
|
475
|
+
return cls(x, y, z), 24
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Configuration and Optimization
|
|
479
|
+
|
|
480
|
+
```python
|
|
481
|
+
# Optimize for specific use cases
|
|
482
|
+
@struct(frozen=True) # Immutable structs
|
|
483
|
+
class ImmutableData:
|
|
484
|
+
value: U64
|
|
485
|
+
|
|
486
|
+
# Memory-efficient sequences
|
|
487
|
+
CompactArray = TypedArray[U8, 1000] # 1000 bytes exactly
|
|
488
|
+
data = CompactArray([0] * 1000)
|
|
489
|
+
|
|
490
|
+
# Bounded containers for validation
|
|
491
|
+
BoundedList = BoundedVector[10, 100] # Between 10 and 100 elements
|
|
492
|
+
safe_list = BoundedList([0] * 50)
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### Error Handling
|
|
496
|
+
|
|
497
|
+
```python
|
|
498
|
+
from tsrkit_types.integers import U8
|
|
499
|
+
|
|
500
|
+
try:
|
|
501
|
+
# Value out of range
|
|
502
|
+
invalid = U8(256) # Raises ValueError
|
|
503
|
+
except ValueError as e:
|
|
504
|
+
print(f"Range error: {e}")
|
|
505
|
+
|
|
506
|
+
try:
|
|
507
|
+
# Type mismatch in Choice
|
|
508
|
+
choice = Choice[U8, String](42) # Raises TypeError
|
|
509
|
+
except TypeError as e:
|
|
510
|
+
print(f"Type error: {e}")
|
|
511
|
+
|
|
512
|
+
try:
|
|
513
|
+
# Buffer too small for decoding
|
|
514
|
+
corrupted = b"\x01"
|
|
515
|
+
U32.decode(corrupted) # Raises ValueError
|
|
516
|
+
except ValueError as e:
|
|
517
|
+
print(f"Decode error: {e}")
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
## Core Interface
|
|
522
|
+
|
|
523
|
+
All types implement the `Codable` interface:
|
|
524
|
+
|
|
525
|
+
```python
|
|
526
|
+
from tsrkit_types.itf.codable import Codable
|
|
527
|
+
|
|
528
|
+
class MyType(Codable):
|
|
529
|
+
def encode_size(self) -> int: ... # Size needed for encoding
|
|
530
|
+
def encode_into(self, buffer: bytearray, offset: int = 0) -> int: ... # Encode into buffer
|
|
531
|
+
def encode(self) -> bytes: ... # Encode to new bytes object
|
|
532
|
+
|
|
533
|
+
@classmethod
|
|
534
|
+
def decode_from(cls, buffer: bytes, offset: int = 0) -> Tuple[T, int]: ... # Decode from buffer
|
|
535
|
+
@classmethod
|
|
536
|
+
def decode(cls, buffer: bytes, offset: int = 0) -> T: ... # Decode from buffer (convenience)
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
## Performance Considerations
|
|
540
|
+
|
|
541
|
+
### Encoding Efficiency
|
|
542
|
+
|
|
543
|
+
- **Fixed-size types** (U8, U16, etc.) have constant encoding size
|
|
544
|
+
- **Variable-size types** (general Uint) optimize for smaller values
|
|
545
|
+
- **Sequences** encode length only when necessary (variable-size)
|
|
546
|
+
- **Strings** use UTF-8 encoding with variable-length prefix
|
|
547
|
+
|
|
548
|
+
### Memory Usage
|
|
549
|
+
|
|
550
|
+
- Types extend built-in Python types where possible (int, str, list, dict)
|
|
551
|
+
- Zero-copy operations where feasible
|
|
552
|
+
- Minimal overhead for type metadata
|
|
553
|
+
|
|
554
|
+
### Best Practices
|
|
555
|
+
|
|
556
|
+
```python
|
|
557
|
+
# Prefer fixed-size types when range is known
|
|
558
|
+
user_id = U32(123456) # Better than Uint(123456)
|
|
559
|
+
|
|
560
|
+
# Use typed containers for homogeneous data
|
|
561
|
+
scores = TypedVector[U16]([100, 95, 87, 92])
|
|
562
|
+
|
|
563
|
+
# Batch operations for better performance
|
|
564
|
+
data = TypedArray[U8, 1000]([0] * 1000)
|
|
565
|
+
encoded = data.encode() # Single operation vs. encoding each element
|
|
566
|
+
|
|
567
|
+
# Reuse buffer for multiple encodings
|
|
568
|
+
buffer = bytearray(1024)
|
|
569
|
+
offset = 0
|
|
570
|
+
offset += value1.encode_into(buffer, offset)
|
|
571
|
+
offset += value2.encode_into(buffer, offset)
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
## Examples
|
|
575
|
+
|
|
576
|
+
### Network Protocol
|
|
577
|
+
|
|
578
|
+
```python
|
|
579
|
+
@struct
|
|
580
|
+
class NetworkPacket:
|
|
581
|
+
packet_type: U8
|
|
582
|
+
session_id: U32
|
|
583
|
+
payload_length: U16
|
|
584
|
+
payload: Bytes
|
|
585
|
+
|
|
586
|
+
# Create packet
|
|
587
|
+
packet = NetworkPacket(
|
|
588
|
+
packet_type=U8(1),
|
|
589
|
+
session_id=U32(0x12345678),
|
|
590
|
+
payload_length=U16(13),
|
|
591
|
+
payload=Bytes(b"Hello, World!")
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
# Serialize for transmission
|
|
595
|
+
wire_data = packet.encode()
|
|
596
|
+
|
|
597
|
+
# Deserialize on receiver
|
|
598
|
+
received_packet = NetworkPacket.decode(wire_data)
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### Configuration File
|
|
602
|
+
|
|
603
|
+
```python
|
|
604
|
+
@struct
|
|
605
|
+
class DatabaseConfig:
|
|
606
|
+
host: String
|
|
607
|
+
port: U16
|
|
608
|
+
username: String
|
|
609
|
+
password: String
|
|
610
|
+
max_connections: U8 = field(metadata={"default": U8(10)})
|
|
611
|
+
ssl_enabled: Bool = field(metadata={"default": Bool(True)})
|
|
612
|
+
|
|
613
|
+
# Create config
|
|
614
|
+
config = DatabaseConfig(
|
|
615
|
+
host=String("localhost"),
|
|
616
|
+
port=U16(5432),
|
|
617
|
+
username=String("admin"),
|
|
618
|
+
password=String("secret")
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
# Save to JSON
|
|
622
|
+
import json
|
|
623
|
+
with open("db_config.json", "w") as f:
|
|
624
|
+
json.dump(config.to_json(), f)
|
|
625
|
+
|
|
626
|
+
# Load from JSON
|
|
627
|
+
with open("db_config.json", "r") as f:
|
|
628
|
+
data = json.load(f)
|
|
629
|
+
config = DatabaseConfig.from_json(data)
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### Game State Serialization
|
|
633
|
+
|
|
634
|
+
```python
|
|
635
|
+
class GameEntityType(Enum):
|
|
636
|
+
PLAYER = 0
|
|
637
|
+
ENEMY = 1
|
|
638
|
+
ITEM = 2
|
|
639
|
+
|
|
640
|
+
@struct
|
|
641
|
+
class Position:
|
|
642
|
+
x: U16
|
|
643
|
+
y: U16
|
|
644
|
+
|
|
645
|
+
@struct
|
|
646
|
+
class GameEntity:
|
|
647
|
+
entity_type: GameEntityType
|
|
648
|
+
position: Position
|
|
649
|
+
health: U8
|
|
650
|
+
name: String
|
|
651
|
+
|
|
652
|
+
@struct
|
|
653
|
+
class GameState:
|
|
654
|
+
level: U8
|
|
655
|
+
score: U32
|
|
656
|
+
entities: TypedVector[GameEntity]
|
|
657
|
+
|
|
658
|
+
# Create game state
|
|
659
|
+
state = GameState(
|
|
660
|
+
level=U8(1),
|
|
661
|
+
score=U32(1500),
|
|
662
|
+
entities=TypedVector[GameEntity]([
|
|
663
|
+
GameEntity(
|
|
664
|
+
entity_type=GameEntityType.PLAYER,
|
|
665
|
+
position=Position(x=U16(100), y=U16(200)),
|
|
666
|
+
health=U8(100),
|
|
667
|
+
name=String("Hero")
|
|
668
|
+
)
|
|
669
|
+
])
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
# Save/load game
|
|
673
|
+
save_data = state.encode()
|
|
674
|
+
loaded_state = GameState.decode(save_data)
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
## Development
|
|
678
|
+
|
|
679
|
+
### Setup
|
|
680
|
+
|
|
681
|
+
1. **Clone the repository:**
|
|
682
|
+
```bash
|
|
683
|
+
git clone https://github.com/chainscore/tsrkit-types.git
|
|
684
|
+
cd tsrkit-types
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
2. **Install development dependencies:**
|
|
688
|
+
```bash
|
|
689
|
+
pip install -e ".[dev]"
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### Running Tests
|
|
693
|
+
|
|
694
|
+
**Run the full test suite:**
|
|
695
|
+
```bash
|
|
696
|
+
pytest
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
**Run tests with coverage:**
|
|
700
|
+
```bash
|
|
701
|
+
pytest --cov=tsrkit_types --cov-report=html
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
**Run tests in parallel (faster):**
|
|
705
|
+
```bash
|
|
706
|
+
pytest -n auto
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
**Run specific test categories:**
|
|
710
|
+
```bash
|
|
711
|
+
pytest tests/test_integers.py # Integer type tests
|
|
712
|
+
pytest tests/test_strings.py # String type tests
|
|
713
|
+
pytest tests/test_containers.py # Container type tests
|
|
714
|
+
pytest tests/test_structs.py # Struct tests
|
|
715
|
+
pytest tests/test_network.py # Network protocol tests
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
**Run tests with verbose output:**
|
|
719
|
+
```bash
|
|
720
|
+
pytest -v
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
**Skip slow tests:**
|
|
724
|
+
```bash
|
|
725
|
+
pytest -m "not slow"
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
### Test Coverage
|
|
729
|
+
|
|
730
|
+
View the test coverage report:
|
|
731
|
+
```bash
|
|
732
|
+
# Generate HTML coverage report
|
|
733
|
+
pytest --cov=tsrkit_types --cov-report=html
|
|
734
|
+
open htmlcov/index.html # macOS
|
|
735
|
+
# or xdg-open htmlcov/index.html # Linux
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
## License
|
|
739
|
+
|
|
740
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
741
|
+
|
|
742
|
+
## Contributing
|
|
743
|
+
|
|
744
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
745
|
+
|
|
746
|
+
## Requirements
|
|
747
|
+
|
|
748
|
+
- **Python**: >= 3.12
|
|
749
|
+
- **Runtime Dependencies**: None (zero dependencies!)
|
|
750
|
+
- **Development Dependencies**: pytest and plugins (see `pyproject.toml`)
|