tsrkit-types 0.1.4__tar.gz → 0.1.9__tar.gz

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.
Files changed (46) hide show
  1. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/CHANGELOG.md +1 -1
  2. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/PKG-INFO +16 -2
  3. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/README.md +15 -1
  4. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/pyproject.toml +2 -2
  5. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tests/test_int.py +19 -0
  6. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tests/test_struct.py +4 -3
  7. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types/__init__.py +1 -1
  8. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types/bytearray.py +3 -1
  9. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types/bytes.py +20 -3
  10. tsrkit_types-0.1.9/tsrkit_types/bytes_common.py +134 -0
  11. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types/choice.py +7 -4
  12. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types/dictionary.py +1 -1
  13. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types/integers.py +62 -25
  14. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types/itf/codable.py +4 -1
  15. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types/null.py +3 -3
  16. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types/option.py +1 -1
  17. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types/struct.py +2 -2
  18. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types.egg-info/PKG-INFO +16 -2
  19. tsrkit_types-0.1.4/tsrkit_types/bytes_common.py +0 -68
  20. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/CONTRIBUTING.md +0 -0
  21. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/LICENSE +0 -0
  22. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/MANIFEST.in +0 -0
  23. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/pytest.ini +0 -0
  24. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/setup.cfg +0 -0
  25. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/setup.py +0 -0
  26. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tests/test_bits.py +0 -0
  27. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tests/test_bytearray.py +0 -0
  28. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tests/test_bytes.py +0 -0
  29. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tests/test_choices.py +0 -0
  30. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tests/test_containers.py +0 -0
  31. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tests/test_enums.py +0 -0
  32. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tests/test_integers.py +0 -0
  33. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tests/test_network.py +0 -0
  34. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tests/test_seq.py +0 -0
  35. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tests/test_strings.py +0 -0
  36. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tests/test_structs.py +0 -0
  37. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tests/type_hints/test_struct_serde.py +0 -0
  38. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types/bits.py +0 -0
  39. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types/bool.py +0 -0
  40. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types/enum.py +0 -0
  41. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types/sequences.py +0 -0
  42. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types/string.py +0 -0
  43. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types.egg-info/SOURCES.txt +0 -0
  44. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types.egg-info/dependency_links.txt +0 -0
  45. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types.egg-info/requires.txt +0 -0
  46. {tsrkit_types-0.1.4 → tsrkit_types-0.1.9}/tsrkit_types.egg-info/top_level.txt +0 -0
@@ -40,7 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
40
40
  ## [0.1.4] - 2025-06-06
41
41
 
42
42
  ### Fixed
43
- - **Documentation**: Corrected release date in changelog from 2025-01-03 to 2025-06-06
43
+ - **Bytes type**: Corrected the name of the Bytes type from `ByteArrayNUM` to `BytesNUM`
44
44
 
45
45
  ## [0.1.3] - 2025-06-06
46
46
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tsrkit-types
3
- Version: 0.1.4
3
+ Version: 0.1.9
4
4
  Summary: Performant Python Typings library for type-safe binary serialization, JSON encoding, and data validation with zero dependencies
5
5
  Author-email: chainscore-labs <hello@chainscore.finance>, prasad-kumkar <prasad@chainscore.finance>
6
6
  License-Expression: MIT
@@ -27,7 +27,7 @@ Classifier: Topic :: Utilities
27
27
  Classifier: Topic :: System :: Archiving
28
28
  Classifier: Topic :: Communications
29
29
  Classifier: Typing :: Typed
30
- Requires-Python: >=3.11
30
+ Requires-Python: <3.13,>=3.11
31
31
  Description-Content-Type: text/markdown
32
32
  License-File: LICENSE
33
33
  Provides-Extra: dev
@@ -794,6 +794,20 @@ pytest -v
794
794
  pytest -m "not slow"
795
795
  ```
796
796
 
797
+ ### Build and Publish
798
+
799
+ 1. Build the package:
800
+
801
+ ```bash
802
+ python3 -m build --wheel
803
+ ```
804
+
805
+ 2. Publish the wheels:
806
+
807
+ ```bash
808
+ twine upload dist/*
809
+ ```
810
+
797
811
  ### Test Coverage
798
812
 
799
813
  View the test coverage report:
@@ -753,6 +753,20 @@ pytest -v
753
753
  pytest -m "not slow"
754
754
  ```
755
755
 
756
+ ### Build and Publish
757
+
758
+ 1. Build the package:
759
+
760
+ ```bash
761
+ python3 -m build --wheel
762
+ ```
763
+
764
+ 2. Publish the wheels:
765
+
766
+ ```bash
767
+ twine upload dist/*
768
+ ```
769
+
756
770
  ### Test Coverage
757
771
 
758
772
  View the test coverage report:
@@ -775,4 +789,4 @@ Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for gui
775
789
 
776
790
  - **Python**: >= 3.11
777
791
  - **Runtime Dependencies**: None (zero dependencies!)
778
- - **Development Dependencies**: pytest and plugins (see `pyproject.toml`)
792
+ - **Development Dependencies**: pytest and plugins (see `pyproject.toml`)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tsrkit-types"
3
- version = "0.1.4"
3
+ version = "0.1.9"
4
4
  description = "Performant Python Typings library for type-safe binary serialization, JSON encoding, and data validation with zero dependencies"
5
5
  authors = [
6
6
  {name = "chainscore-labs", email = "hello@chainscore.finance"},
@@ -8,7 +8,7 @@ authors = [
8
8
  ]
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
- requires-python = ">=3.11"
11
+ requires-python = ">=3.11,<3.13"
12
12
  keywords = [
13
13
  "serialization", "binary", "encoding", "types", "codable", "json",
14
14
  "validation", "data-types", "type-safety", "zero-copy", "protocol",
@@ -56,3 +56,22 @@ def test_static_type_checker():
56
56
  # This is fine
57
57
  DataStore(a=UInt[8](19), b=UInt[16](288))
58
58
 
59
+ def test_int_sub():
60
+ a = UInt[8](100)
61
+ b = UInt[8](80)
62
+ assert a - b == UInt[8](20)
63
+ assert str(a - b) == 'U8(20)'
64
+
65
+ def test_int_compare_with_int():
66
+ a = UInt[8](100)
67
+ assert a > 80
68
+ assert a < 120
69
+ assert a >= 100
70
+ assert a <= 100
71
+ assert a != 101
72
+ assert a == 100
73
+ assert a != 101
74
+
75
+ def test_int_min_max():
76
+ assert min(UInt[8](100), UInt[8](80)) == UInt[8](80)
77
+ assert max(UInt[8](100), UInt[8](80)) == UInt[8](100)
@@ -1,4 +1,5 @@
1
- from tsrkit_types.integers import Uint
1
+ from typing import Literal
2
+ from tsrkit_types.integers import Int, Uint
2
3
  from tsrkit_types.itf.codable import Codable
3
4
  from tsrkit_types.string import String
4
5
  from tsrkit_types.struct import struct, structure
@@ -57,6 +58,6 @@ def test_struct_inheritance():
57
58
  name: String
58
59
  age: Uint[8] = field(metadata={"default": Uint[8](0)})
59
60
 
60
- p = Person(name=String("John"), age=Uint[8](30))
61
+ p = Person(name=String("John"), age=Int[Literal[8]](30))
61
62
  assert isinstance(p, Codable)
62
- assert isinstance(p, Person)
63
+ assert isinstance(p, Person)
@@ -90,6 +90,6 @@ __all__ = [
90
90
  ]
91
91
 
92
92
  # Version information
93
- __version__ = "1.0.0"
93
+ __version__ = "0.1.9"
94
94
  __author__ = "TSRKit Team"
95
95
  __license__ = "MIT"
@@ -28,10 +28,12 @@ class ByteArray(bytearray, Codable, BytesMixin):
28
28
  current_offset = offset
29
29
  _len, _inc_offset = Uint.decode_from(buffer, offset)
30
30
  current_offset += _inc_offset
31
+ if len(buffer[current_offset:current_offset+_len]) < _len:
32
+ raise TypeError("Insufficient buffer")
31
33
  return cls(buffer[current_offset:current_offset+_len]), current_offset + _len - offset
32
34
 
33
35
  # ---------------------------------------------------------------------------- #
34
36
  # JSON Serialization #
35
37
  # ---------------------------------------------------------------------------- #
36
38
  # JSON methods inherited from BytesMixin
37
-
39
+
@@ -4,6 +4,7 @@ from tsrkit_types.integers import Uint
4
4
  from tsrkit_types.itf.codable import Codable
5
5
  from tsrkit_types.bytes_common import BytesMixin
6
6
 
7
+
7
8
  class BytesCheckMeta(abc.ABCMeta):
8
9
  """Meta class to check if the instance is a bytes with the same key and value types"""
9
10
  def __instancecheck__(cls, instance):
@@ -51,11 +52,27 @@ class Bytes(bytes, Codable, BytesMixin, metaclass=BytesCheckMeta):
51
52
  def decode_from(cls, buffer: Union[bytes, bytearray, memoryview], offset: int = 0) -> Tuple["Bytes", int]:
52
53
  current_offset = offset
53
54
  _len = cls._length
55
+
54
56
  if _len is None:
55
57
  _len, _inc_offset = Uint.decode_from(buffer, offset)
56
58
  current_offset += _inc_offset
57
- return cls(buffer[current_offset:current_offset+_len]), current_offset + _len - offset
58
-
59
+
60
+ if len(buffer[current_offset:current_offset+_len]) < _len:
61
+ raise TypeError("Insufficient buffer")
62
+
63
+ result = (cls(buffer[current_offset:current_offset+_len]), current_offset + _len - offset)
64
+
65
+ return result
66
+
67
+ def __deepcopy__(self, memo):
68
+ # immutable; safe to reuse or create a new same-typed instance
69
+ existing = memo.get(id(self))
70
+ if existing is not None:
71
+ return existing
72
+ new = type(self)(bytes(self))
73
+ memo[id(self)] = new
74
+ return new
75
+
59
76
  # ---------------------------------------------------------------------------- #
60
77
  # JSON Serialization #
61
78
  # ---------------------------------------------------------------------------- #
@@ -67,4 +84,4 @@ Bytes64 = Bytes[64]
67
84
  Bytes128 = Bytes[128]
68
85
  Bytes256 = Bytes[256]
69
86
  Bytes512 = Bytes[512]
70
- Bytes1024 = Bytes[1024]
87
+ Bytes1024 = Bytes[1024]
@@ -0,0 +1,134 @@
1
+ """
2
+ Common functionality for Bytes and ByteArray types.
3
+ """
4
+
5
+ from typing import Union
6
+
7
+ # Global lookup tables for maximum performance - initialized once
8
+ _BYTE_TO_BITS_MSB = []
9
+ _BITS_TO_BYTE_MSB = {}
10
+ _TABLES_INITIALIZED = False
11
+
12
+ def _init_lookup_tables():
13
+ """Initialize lookup tables once for optimal performance."""
14
+ global _BYTE_TO_BITS_MSB, _BITS_TO_BYTE_MSB, _TABLES_INITIALIZED
15
+ if not _TABLES_INITIALIZED:
16
+ for i in range(256):
17
+ # Convert byte to 8 bits (MSB first)
18
+ bits = [(i >> (7 - j)) & 1 for j in range(8)]
19
+ _BYTE_TO_BITS_MSB.append(bits)
20
+ # Reverse lookup: bits tuple to byte value
21
+ _BITS_TO_BYTE_MSB[tuple(bits)] = i
22
+ _TABLES_INITIALIZED = True
23
+
24
+
25
+ class BytesMixin:
26
+ """Mixin providing common functionality for bytes-like types."""
27
+
28
+ @classmethod
29
+ def from_bits(cls, bits: list[bool], bit_order: str = "msb"):
30
+ """Convert a list of bits to bytes with specified bit order."""
31
+ # Fast path for MSB (most common case) using lookup tables
32
+ if bit_order == "msb":
33
+ _init_lookup_tables()
34
+
35
+ # Convert and pad to multiple of 8
36
+ int_bits = [int(bool(b)) for b in bits]
37
+ pad = (8 - len(int_bits) % 8) % 8
38
+ int_bits.extend([0] * pad)
39
+
40
+ result = []
41
+ for i in range(0, len(int_bits), 8):
42
+ byte_bits = tuple(int_bits[i:i+8])
43
+ result.append(_BITS_TO_BYTE_MSB[byte_bits])
44
+ return cls(bytes(result))
45
+
46
+ # LSB implementation
47
+ elif bit_order == "lsb":
48
+ int_bits = [int(bool(b)) for b in bits]
49
+ pad = (8 - len(int_bits) % 8) % 8
50
+ int_bits.extend([0] * pad)
51
+
52
+ result = []
53
+ for i in range(0, len(int_bits), 8):
54
+ byte_bits = int_bits[i:i + 8]
55
+ val = 0
56
+ for bit in reversed(byte_bits):
57
+ val = (val << 1) | bit
58
+ result.append(val)
59
+ return cls(bytes(result))
60
+ else:
61
+ raise ValueError(f"Unknown bit_order: {bit_order}")
62
+
63
+ def to_bits(self, bit_order: str = "msb") -> list[bool]:
64
+ """Convert bytes to a list of bits with specified bit order."""
65
+ # Fast path for MSB using lookup table
66
+ if bit_order == "msb":
67
+ _init_lookup_tables()
68
+
69
+ result = []
70
+ for byte in self:
71
+ result.extend(_BYTE_TO_BITS_MSB[byte])
72
+ return [bool(b) for b in result]
73
+
74
+ # LSB implementation
75
+ elif bit_order == "lsb":
76
+ bits = []
77
+ for byte in self:
78
+ bits.extend([bool((byte >> i) & 1) for i in range(8)])
79
+ return bits
80
+ else:
81
+ raise ValueError(f"Unknown bit_order: {bit_order}")
82
+
83
+ def to_json(self):
84
+ """Convert bytes to hex string for JSON serialization."""
85
+ return self.hex()
86
+
87
+ @classmethod
88
+ def from_json(cls, data: str):
89
+ """Create instance from hex string."""
90
+ data = data.replace("0x", "")
91
+ return cls(bytes.fromhex(data))
92
+
93
+ def __str__(self):
94
+ return f"{self.__class__.__name__}({self.hex()})"
95
+
96
+ def slice_bits(self, start_bit: int, end_bit: int) -> list[bool]:
97
+ """Extract bit slice efficiently without converting entire byte array."""
98
+ if start_bit >= end_bit:
99
+ return []
100
+
101
+ start_byte = start_bit // 8
102
+ end_byte = (end_bit - 1) // 8 + 1
103
+ start_bit_offset = start_bit % 8
104
+
105
+ if start_byte >= len(self):
106
+ return [False] * (end_bit - start_bit)
107
+
108
+ # Extract relevant bytes and convert only what we need
109
+ relevant_bytes = self[start_byte:min(end_byte, len(self))]
110
+
111
+ # Use global lookup table for optimal performance
112
+ _init_lookup_tables()
113
+
114
+ result = []
115
+ for byte in relevant_bytes:
116
+ result.extend(_BYTE_TO_BITS_MSB[byte])
117
+
118
+ # Slice to exact range
119
+ local_start = start_bit_offset if start_byte < len(self) else 0
120
+ local_end = len(result) if end_byte > len(self) else (end_bit - start_byte * 8)
121
+
122
+ bits = result[local_start:local_end]
123
+
124
+ # Pad with False if we ran out of data
125
+ if len(bits) < (end_bit - start_bit):
126
+ bits.extend([False] * (end_bit - start_bit - len(bits)))
127
+
128
+ return [bool(b) for b in bits]
129
+
130
+
131
+ def validate_bit_order(bit_order: str) -> None:
132
+ """Validate bit order parameter."""
133
+ if bit_order not in ("msb", "lsb"):
134
+ raise ValueError(f"Unknown bit_order: {bit_order}. Must be 'msb' or 'lsb'")
@@ -3,7 +3,7 @@ from typing import ClassVar, Optional, Union, Tuple, Any
3
3
  from tsrkit_types.integers import Uint
4
4
  from tsrkit_types.itf.codable import Codable
5
5
 
6
- ChoiceType = Tuple[Optional[str], type] | type
6
+ ChoiceType = Union[Tuple[Optional[str], type], type]
7
7
 
8
8
  class Choice(Codable):
9
9
  """
@@ -43,7 +43,7 @@ class Choice(Codable):
43
43
  def _choice_keys(self) -> Tuple[Optional[str]]:
44
44
  return tuple(self._choice[0] for self._choice in self._opt_types)
45
45
 
46
- def __class_getitem__(cls, opt_t: Tuple[type] | type):
46
+ def __class_getitem__(cls, opt_t: Union[Tuple[type], type]):
47
47
  _opt_types = []
48
48
  if isinstance(opt_t, type):
49
49
  _opt_types.append((None, opt_t))
@@ -68,6 +68,9 @@ class Choice(Codable):
68
68
  def unwrap(self) -> Any:
69
69
  return self._value
70
70
 
71
+ def get_key(self):
72
+ return self._choice_key
73
+
71
74
  def __repr__(self) -> str:
72
75
  return f"{self.__class__.__name__}({self._value!r})"
73
76
 
@@ -95,7 +98,7 @@ class Choice(Codable):
95
98
  return self._value.to_json() if not self._choice_key else {self._choice_key: self._value.to_json()}
96
99
 
97
100
  @classmethod
98
- def from_json(cls, data: dict | Any) -> "Choice":
101
+ def from_json(cls, data: Union[dict, Any]) -> "Choice":
99
102
  if isinstance(data, dict):
100
103
  opt_type = next((x for x in cls._opt_types if x[0] == list(data.keys())[0]), None)
101
104
  if opt_type is None:
@@ -129,4 +132,4 @@ class Choice(Codable):
129
132
  tag, tag_size = Uint.decode_from(buffer, offset)
130
133
  value, val_size = cls._opt_types[tag][1].decode_from(buffer, offset+tag_size)
131
134
 
132
- return cls(value), tag_size+val_size
135
+ return cls(value, key=cls._opt_types[tag][0]), tag_size+val_size
@@ -126,7 +126,7 @@ class Dictionary(dict, Codable, Generic[K, V], metaclass=DictCheckMeta):
126
126
  def encode_into(self, buffer: bytearray, offset: int = 0) -> int:
127
127
  current_offset = offset
128
128
  current_offset += Uint(len(self)).encode_into(buffer, current_offset)
129
- for k, v in sorted(self.items(), key=lambda x: x[0].encode()):
129
+ for k, v in sorted(self.items(), key=lambda x: x[0]):
130
130
  current_offset += k.encode_into(buffer, current_offset)
131
131
  current_offset += v.encode_into(buffer, current_offset)
132
132
  return current_offset - offset
@@ -1,7 +1,18 @@
1
1
  import abc
2
2
  from decimal import Decimal
3
3
  import math
4
- from typing import Any, Tuple, Union, Callable
4
+ from typing import Any, Optional, Tuple, Union, Callable
5
+
6
+ try:
7
+ from typing import Self
8
+ except ImportError:
9
+ # For Python < 3.11, use TYPE_CHECKING and forward reference
10
+ from typing import TYPE_CHECKING
11
+ if TYPE_CHECKING:
12
+ from typing import Self
13
+ else:
14
+ Self = "Uint" # Forward reference string
15
+
5
16
  from tsrkit_types.itf.codable import Codable
6
17
 
7
18
 
@@ -11,7 +22,7 @@ class IntCheckMeta(abc.ABCMeta):
11
22
  return isinstance(instance, int) and getattr(instance, "byte_size", 0) == cls.byte_size
12
23
 
13
24
 
14
- class Uint(int, Codable, metaclass=IntCheckMeta):
25
+ class Int(int, Codable, metaclass=IntCheckMeta):
15
26
  """
16
27
  Unsigned integer type.
17
28
 
@@ -52,23 +63,44 @@ class Uint(int, Codable, metaclass=IntCheckMeta):
52
63
  # If the byte_size is set, the integer is fixed size.
53
64
  # Otherwise, the integer is General Integer (supports up to 2**64 - 1)
54
65
  byte_size: int = 0
66
+ signed = False
67
+ _bound = 1 << 64
68
+
69
+ @classmethod
70
+ def __class_getitem__(cls, data: Optional[Union[int, tuple, bool]]):
71
+ """
72
+ Args:
73
+ data: either byte_size or (byte_size, signed)
74
+ """
75
+ if data == None:
76
+ size, signed = 0, False
77
+ # If we have a single value arg - wither byte_size or signed
78
+ elif not isinstance(data, tuple):
79
+ if isinstance(data, int):
80
+ size, signed = data, False
81
+ else:
82
+ size, signed = 0, bool(data)
83
+ else:
84
+ size, signed = data
55
85
 
56
- def __class_getitem__(cls, size: int):
57
- return type(f"U{size}" if size else "Int", (cls,), {"byte_size": size // 8})
86
+ return type(f"U{size}" if size else "Int", (cls,), {
87
+ "byte_size": size // 8,
88
+ "signed": signed,
89
+ "_bound": 1 << size if size > 0 else 1 << 64
90
+ })
58
91
 
59
92
  def __new__(cls, value: Any):
60
93
  value = int(value)
61
94
  if cls.byte_size > 0:
62
- bits = 8 * cls.byte_size
63
- min_v = 0
64
- max_v = (1 << bits) - 1
65
- if not (min_v <= value <= max_v):
66
- raise ValueError(f"Fixed Int: {cls.__name__} out of range: {value!r} "
67
- f"not in [{min_v}, {max_v}]")
95
+ max_v = (cls._bound // 2 if cls.signed else cls._bound) - 1
96
+ min_v = -1 * cls._bound // 2 if cls.signed else 0
68
97
  else:
69
- if not 0 <= value < 2 ** 64:
70
- raise ValueError(f"General Int: Value must be between 0 and 2**64 - 1, got {value}")
71
-
98
+ min_v = -1 * cls._bound // 2 if cls.signed else 0
99
+ max_v = (cls._bound // 2 if cls.signed else cls._bound) - 1
100
+
101
+ if not (min_v <= value <= max_v):
102
+ raise ValueError(f"Int: {cls.__name__} out of range: {value!r} "
103
+ f"not in [{min_v}, {max_v}]")
72
104
  return super().__new__(cls, value)
73
105
 
74
106
  def __repr__(self):
@@ -105,11 +137,11 @@ class Uint(int, Codable, metaclass=IntCheckMeta):
105
137
  # ---------------------------------------------------------------------------- #
106
138
  # JSON Serde #
107
139
  # ---------------------------------------------------------------------------- #
108
- def to_json(self) -> str:
140
+ def to_json(self) -> int:
109
141
  return int(self)
110
142
 
111
143
  @classmethod
112
- def from_json(cls, json_str: str) -> "Uint":
144
+ def from_json(cls, json_str: str) -> "Int":
113
145
  return cls(int(json_str))
114
146
 
115
147
  # ---------------------------------------------------------------------------- #
@@ -118,20 +150,24 @@ class Uint(int, Codable, metaclass=IntCheckMeta):
118
150
  @staticmethod
119
151
  def l(x):
120
152
  return math.floor(Decimal(x).ln() / (Decimal(7) * Decimal(2).ln()))
121
-
153
+
154
+ def to_unsigned(self) -> "Int":
155
+ if not self.signed: return self
156
+ return int(self) + (self._bound // 2)
122
157
 
123
158
  def encode_size(self) -> int:
124
159
  if self.byte_size > 0:
125
160
  return self.byte_size
126
161
  else:
127
- if self < 2**7:
162
+ value = self.to_unsigned()
163
+ if value < 2**7:
128
164
  return 1
129
- elif self < 2 ** (7 * 9):
165
+ elif value < 2 ** (7 * 9):
130
166
  return 1 + self.l(self)
131
- elif self < 2**64:
167
+ elif value < 2**64:
132
168
  return 9
133
169
  else:
134
- raise ValueError("Value too large for encoding. General Uint support up to 2**64 - 1")
170
+ raise ValueError("Value too large for encoding. General Int support up to 2**64 - 1")
135
171
 
136
172
  def encode_into(self, buffer: bytearray, offset: int = 0) -> int:
137
173
  if self.byte_size > 0:
@@ -206,7 +242,7 @@ class Uint(int, Codable, metaclass=IntCheckMeta):
206
242
  raise ValueError(f"Invalid bit order: {bit_order}")
207
243
 
208
244
  @classmethod
209
- def from_bits(cls, bits: list[bool], bit_order: str = "msb") -> "Uint":
245
+ def from_bits(cls, bits: list[bool], bit_order: str = "msb") -> "Int":
210
246
  """Convert bits to an int"""
211
247
  if bit_order == "msb":
212
248
  return cls(int("".join(str(int(b)) for b in bits), 2))
@@ -214,7 +250,8 @@ class Uint(int, Codable, metaclass=IntCheckMeta):
214
250
  return cls(int("".join(str(int(b)) for b in reversed(bits)), 2))
215
251
 
216
252
 
217
- U8 = Uint[8]
218
- U16 = Uint[16]
219
- U32 = Uint[32]
220
- U64 = Uint[64]
253
+ Uint = Int
254
+ U8 = Int[8]
255
+ U16 = Int[16]
256
+ U32 = Int[32]
257
+ U64 = Int[64]
@@ -80,4 +80,7 @@ class Codable(ABC, Generic[T]):
80
80
  offset: The offset at which to start encoding the value.
81
81
  """
82
82
  if len(buffer) - offset < size:
83
- raise ValueError("Buffer too small to encode value")
83
+ raise ValueError("Buffer too small to encode value")
84
+
85
+ def __reduce__(self):
86
+ return (self.__class__.decode, (self.encode(),))
@@ -1,7 +1,7 @@
1
1
  from typing import Optional, Tuple, Union
2
+ from tsrkit_types.itf.codable import Codable
2
3
 
3
-
4
- class NullType:
4
+ class NullType(Codable):
5
5
  def __repr__(self):
6
6
  return "Null"
7
7
 
@@ -40,4 +40,4 @@ class NullType:
40
40
 
41
41
 
42
42
 
43
- Null = NullType()
43
+ Null = NullType()
@@ -18,7 +18,7 @@ class Option(Choice, Generic[T]):
18
18
  (Option,),
19
19
  {"_opt_types": ((None, NullType), (None, opt_t))})
20
20
 
21
- def __init__(self, val: T|NullType = Null):
21
+ def __init__(self, val: T|NullType = Null, key = None):
22
22
  super().__init__(val)
23
23
 
24
24
  def set(self, value: T|NullType = Null, key: Optional[str] = None):
@@ -1,10 +1,11 @@
1
1
  from dataclasses import dataclass, fields
2
- from typing import Any, Tuple, Union
2
+ from typing import Any, Tuple, Union, dataclass_transform
3
3
  from tsrkit_types.itf.codable import Codable
4
4
  from tsrkit_types.null import NullType
5
5
  from tsrkit_types.option import Option
6
6
 
7
7
 
8
+ @dataclass_transform()
8
9
  def structure(_cls=None, *, frozen=False, **kwargs):
9
10
  """Extension of dataclass to support serialization and json operations.
10
11
 
@@ -66,7 +67,6 @@ def structure(_cls=None, *, frozen=False, **kwargs):
66
67
  init_data[field.name] = field.type.from_json(v)
67
68
  return cls(**init_data)
68
69
 
69
-
70
70
  new_cls.__init__ = __init__
71
71
 
72
72
  # Only overwrite if the method is not already defined
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tsrkit-types
3
- Version: 0.1.4
3
+ Version: 0.1.9
4
4
  Summary: Performant Python Typings library for type-safe binary serialization, JSON encoding, and data validation with zero dependencies
5
5
  Author-email: chainscore-labs <hello@chainscore.finance>, prasad-kumkar <prasad@chainscore.finance>
6
6
  License-Expression: MIT
@@ -27,7 +27,7 @@ Classifier: Topic :: Utilities
27
27
  Classifier: Topic :: System :: Archiving
28
28
  Classifier: Topic :: Communications
29
29
  Classifier: Typing :: Typed
30
- Requires-Python: >=3.11
30
+ Requires-Python: <3.13,>=3.11
31
31
  Description-Content-Type: text/markdown
32
32
  License-File: LICENSE
33
33
  Provides-Extra: dev
@@ -794,6 +794,20 @@ pytest -v
794
794
  pytest -m "not slow"
795
795
  ```
796
796
 
797
+ ### Build and Publish
798
+
799
+ 1. Build the package:
800
+
801
+ ```bash
802
+ python3 -m build --wheel
803
+ ```
804
+
805
+ 2. Publish the wheels:
806
+
807
+ ```bash
808
+ twine upload dist/*
809
+ ```
810
+
797
811
  ### Test Coverage
798
812
 
799
813
  View the test coverage report:
@@ -1,68 +0,0 @@
1
- """
2
- Common functionality for Bytes and ByteArray types.
3
- """
4
-
5
- from typing import Union
6
-
7
-
8
- class BytesMixin:
9
- """Mixin providing common functionality for bytes-like types."""
10
-
11
- @classmethod
12
- def from_bits(cls, bits: list[bool], bit_order: str = "msb"):
13
- """Convert a list of bits to bytes with specified bit order."""
14
- # Sanitize input: make sure bits are 0 or 1
15
- bits = [int(bool(b)) for b in bits]
16
- n = len(bits)
17
- # Pad with zeros to multiple of 8
18
- pad = (8 - n % 8) % 8
19
- bits += [0] * pad
20
-
21
- byte_arr = []
22
- for i in range(0, len(bits), 8):
23
- byte_bits = bits[i:i + 8]
24
- if bit_order == "msb":
25
- # Most significant bit first
26
- val = 0
27
- for bit in byte_bits:
28
- val = (val << 1) | bit
29
- elif bit_order == "lsb":
30
- # Least significant bit first
31
- val = 0
32
- for bit in reversed(byte_bits):
33
- val = (val << 1) | bit
34
- else:
35
- raise ValueError(f"Unknown bit_order: {bit_order}")
36
- byte_arr.append(val)
37
- return cls(bytes(byte_arr))
38
-
39
- def to_bits(self, bit_order: str = "msb") -> list[bool]:
40
- """Convert bytes to a list of bits with specified bit order."""
41
- bits = []
42
- for byte in self:
43
- if bit_order == "msb":
44
- bits.extend([bool((byte >> i) & 1) for i in reversed(range(8))])
45
- elif bit_order == "lsb":
46
- bits.extend([bool((byte >> i) & 1) for i in range(8)])
47
- else:
48
- raise ValueError(f"Unknown bit_order: {bit_order}")
49
- return bits
50
-
51
- def to_json(self):
52
- """Convert bytes to hex string for JSON serialization."""
53
- return self.hex()
54
-
55
- @classmethod
56
- def from_json(cls, data: str):
57
- """Create instance from hex string."""
58
- data = data.replace("0x", "")
59
- return cls(bytes.fromhex(data))
60
-
61
- def __str__(self):
62
- return f"{self.__class__.__name__}({self.hex()})"
63
-
64
-
65
- def validate_bit_order(bit_order: str) -> None:
66
- """Validate bit order parameter."""
67
- if bit_order not in ("msb", "lsb"):
68
- raise ValueError(f"Unknown bit_order: {bit_order}. Must be 'msb' or 'lsb'")
File without changes
File without changes
File without changes
File without changes
File without changes