bencode2 0.3.27__cp313-cp313-win_arm64.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.
- bencode2/__bencode.cp313-win_arm64.pyd +0 -0
- bencode2/__decoder.py +206 -0
- bencode2/__encoder.py +159 -0
- bencode2/__init__.py +17 -0
- bencode2/__init__.pyi +8 -0
- bencode2/py.typed +0 -0
- bencode2-0.3.27.dist-info/METADATA +153 -0
- bencode2-0.3.27.dist-info/RECORD +9 -0
- bencode2-0.3.27.dist-info/WHEEL +4 -0
|
Binary file
|
bencode2/__decoder.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Final
|
|
4
|
+
|
|
5
|
+
from typing_extensions import Buffer
|
|
6
|
+
|
|
7
|
+
char_l: Final = ord("l")
|
|
8
|
+
char_i: Final = ord("i")
|
|
9
|
+
char_e: Final = ord("e")
|
|
10
|
+
char_d: Final = ord("d")
|
|
11
|
+
char_0: Final = ord("0")
|
|
12
|
+
char_9: Final = ord("9")
|
|
13
|
+
char_dash: Final = ord("-")
|
|
14
|
+
char_colon: Final = ord(":")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BencodeDecodeError(ValueError):
|
|
18
|
+
"""Bencode decode error."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def bdecode(value: Buffer, /) -> Any:
|
|
22
|
+
"""Decode bencode formatted bytes to python value."""
|
|
23
|
+
return Decoder(memoryview(value).cast("B")).decode()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Decoder:
|
|
27
|
+
value: memoryview
|
|
28
|
+
index: int
|
|
29
|
+
size: int
|
|
30
|
+
|
|
31
|
+
__slots__ = ("value", "index", "size")
|
|
32
|
+
|
|
33
|
+
def __init__(self, value: memoryview) -> None:
|
|
34
|
+
self.size = len(value)
|
|
35
|
+
if self.size == 0:
|
|
36
|
+
raise BencodeDecodeError("empty input")
|
|
37
|
+
|
|
38
|
+
self.value = value
|
|
39
|
+
self.index = 0
|
|
40
|
+
|
|
41
|
+
def decode(self) -> object:
|
|
42
|
+
data = self.__decode()
|
|
43
|
+
|
|
44
|
+
if self.index != self.size: # pragma: no cover
|
|
45
|
+
raise BencodeDecodeError("invalid bencode value (data after valid prefix)")
|
|
46
|
+
|
|
47
|
+
return data
|
|
48
|
+
|
|
49
|
+
def __decode(self) -> object:
|
|
50
|
+
if char_0 <= self.value[self.index] <= char_9:
|
|
51
|
+
return self.__decode_bytes()
|
|
52
|
+
if self.value[self.index] == char_i:
|
|
53
|
+
return self.__decode_int()
|
|
54
|
+
if self.value[self.index] == char_d:
|
|
55
|
+
return self.__decode_dict()
|
|
56
|
+
if self.value[self.index] == char_l:
|
|
57
|
+
return self.__decode_list()
|
|
58
|
+
|
|
59
|
+
raise BencodeDecodeError(
|
|
60
|
+
f"unexpected token {self.value[self.index:self.index + 1].tobytes()}. "
|
|
61
|
+
f"index {self.index}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def __decode_int(self) -> int:
|
|
65
|
+
self.index += 1
|
|
66
|
+
for i, c in enumerate(self.value[self.index :]):
|
|
67
|
+
if c == char_e:
|
|
68
|
+
index_end = i + self.index
|
|
69
|
+
break
|
|
70
|
+
else:
|
|
71
|
+
raise BencodeDecodeError(
|
|
72
|
+
f"invalid int, failed to found end. index {self.index}"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if index_end == self.index:
|
|
76
|
+
raise BencodeDecodeError(f"invalid int, found 'ie': {self.index}")
|
|
77
|
+
|
|
78
|
+
n: int = 1
|
|
79
|
+
offset: int = 0
|
|
80
|
+
|
|
81
|
+
if self.value[self.index] == char_dash:
|
|
82
|
+
n = -1
|
|
83
|
+
offset = 1
|
|
84
|
+
|
|
85
|
+
total: int = 0
|
|
86
|
+
for c in self.value[self.index + offset : index_end]:
|
|
87
|
+
if not (char_0 <= c <= char_9):
|
|
88
|
+
raise BencodeDecodeError(
|
|
89
|
+
f"malformed int {self.value[self.index:index_end].tobytes()}. index {self.index}"
|
|
90
|
+
)
|
|
91
|
+
total = total * 10 + (c - char_0)
|
|
92
|
+
|
|
93
|
+
n = total * n
|
|
94
|
+
|
|
95
|
+
if self.value[self.index] == char_dash:
|
|
96
|
+
if self.value[self.index + 1] == char_0:
|
|
97
|
+
raise BencodeDecodeError(
|
|
98
|
+
f"-0 is not allowed in bencoding. index: {self.index}"
|
|
99
|
+
)
|
|
100
|
+
elif self.value[self.index] == char_0 and index_end != self.index + 1:
|
|
101
|
+
raise BencodeDecodeError(
|
|
102
|
+
f"integer with leading zero is not allowed. index: {self.index}"
|
|
103
|
+
)
|
|
104
|
+
self.index = index_end + 1
|
|
105
|
+
return n
|
|
106
|
+
|
|
107
|
+
def __decode_list(self) -> list[Any]:
|
|
108
|
+
r: list[Any] = []
|
|
109
|
+
self.index += 1
|
|
110
|
+
|
|
111
|
+
while True:
|
|
112
|
+
if self.index >= self.size:
|
|
113
|
+
raise BencodeDecodeError(
|
|
114
|
+
f"buffer overflow when decoding array, index {self.index}"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if self.value[self.index] == char_e:
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
v = self.__decode()
|
|
121
|
+
r.append(v)
|
|
122
|
+
|
|
123
|
+
self.index += 1
|
|
124
|
+
return r
|
|
125
|
+
|
|
126
|
+
def __decode_bytes(self) -> bytes:
|
|
127
|
+
for i, c in enumerate(self.value[self.index :]):
|
|
128
|
+
if c == char_colon:
|
|
129
|
+
index_colon = i + self.index
|
|
130
|
+
break
|
|
131
|
+
else:
|
|
132
|
+
raise BencodeDecodeError(
|
|
133
|
+
f"invalid bytes, failed find expected char ':'. index {self.index}"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if self.value[self.index] == char_0:
|
|
137
|
+
if index_colon != self.index + 1:
|
|
138
|
+
raise BencodeDecodeError(
|
|
139
|
+
f"malformed str/bytes length with leading 0. index {self.index}"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
n: int = 0
|
|
143
|
+
for c in self.value[self.index : index_colon]:
|
|
144
|
+
if not (char_0 <= c <= char_9):
|
|
145
|
+
raise BencodeDecodeError(
|
|
146
|
+
f"malformed str/bytes length {self.value[self.index:index_colon].tobytes()!r}."
|
|
147
|
+
f" index {self.index}"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
n = n * 10 + (c - char_0)
|
|
151
|
+
|
|
152
|
+
if index_colon + n >= self.size:
|
|
153
|
+
raise BencodeDecodeError(
|
|
154
|
+
f"malformed str/bytes length, buffer overflow. index {self.index}"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
index_colon += 1
|
|
158
|
+
|
|
159
|
+
s = self.value[index_colon : index_colon + n]
|
|
160
|
+
|
|
161
|
+
self.index = index_colon + n
|
|
162
|
+
|
|
163
|
+
return s.tobytes()
|
|
164
|
+
|
|
165
|
+
def __decode_dict(self) -> dict[str | bytes, Any]:
|
|
166
|
+
start_index = self.index
|
|
167
|
+
self.index += 1
|
|
168
|
+
|
|
169
|
+
items: list[tuple[bytes, Any]] = []
|
|
170
|
+
|
|
171
|
+
while True:
|
|
172
|
+
if self.index >= self.size:
|
|
173
|
+
raise BencodeDecodeError(
|
|
174
|
+
f"buffer overflow when decoding bytes, index {self.index}"
|
|
175
|
+
)
|
|
176
|
+
if self.value[self.index] == char_e:
|
|
177
|
+
break
|
|
178
|
+
if not (char_0 <= self.value[self.index] <= char_9):
|
|
179
|
+
raise BencodeDecodeError(
|
|
180
|
+
f"directory only allow str as keys, "
|
|
181
|
+
f"found unexpected char "
|
|
182
|
+
f"'{self.value[self.index]:c}', index {self.index}"
|
|
183
|
+
)
|
|
184
|
+
k = self.__decode_bytes()
|
|
185
|
+
v = self.__decode()
|
|
186
|
+
items.append((k, v))
|
|
187
|
+
|
|
188
|
+
if not items:
|
|
189
|
+
self.index += 1
|
|
190
|
+
return {}
|
|
191
|
+
|
|
192
|
+
_check_sorted(items, start_index)
|
|
193
|
+
|
|
194
|
+
self.index += 1
|
|
195
|
+
|
|
196
|
+
return dict(items)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _check_sorted(s: list[tuple[bytes, Any]], idx: int) -> None:
|
|
200
|
+
i = 1
|
|
201
|
+
while i < len(s):
|
|
202
|
+
if s[i][0] < s[i - 1][0]:
|
|
203
|
+
raise BencodeDecodeError(f"directory keys is not sorted, index {idx}")
|
|
204
|
+
if s[i][0] == s[i - 1][0]:
|
|
205
|
+
raise BencodeDecodeError(f"found duplicated keys in directory, index {idx}")
|
|
206
|
+
i += 1
|
bencode2/__encoder.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
from collections import OrderedDict
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from dataclasses import fields, is_dataclass
|
|
7
|
+
from types import MappingProxyType
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BencodeEncodeError(ValueError):
|
|
12
|
+
"""Bencode encode error."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def bencode(value: Any, /) -> bytes:
|
|
16
|
+
"""Encode value into the bencode format."""
|
|
17
|
+
with io.BytesIO() as w:
|
|
18
|
+
__encode(w, value, set(), stack_depth=0)
|
|
19
|
+
return w.getvalue()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def __encode(w: io.BytesIO, value: Any, seen: set[int], stack_depth: int) -> None:
|
|
23
|
+
if isinstance(value, str):
|
|
24
|
+
return __encode_bytes(w, value.encode("UTF-8"))
|
|
25
|
+
|
|
26
|
+
if isinstance(value, int):
|
|
27
|
+
w.write(b"i")
|
|
28
|
+
# will handle bool and enum.IntEnum
|
|
29
|
+
w.write(str(int(value)).encode())
|
|
30
|
+
w.write(b"e")
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
if isinstance(value, bytes):
|
|
34
|
+
return __encode_bytes(w, value)
|
|
35
|
+
|
|
36
|
+
stack_depth += 1
|
|
37
|
+
|
|
38
|
+
i = id(value)
|
|
39
|
+
if isinstance(value, (dict, OrderedDict, MappingProxyType)):
|
|
40
|
+
if stack_depth >= 100:
|
|
41
|
+
if i in seen:
|
|
42
|
+
raise BencodeEncodeError(f"circular reference found {value!r}")
|
|
43
|
+
seen.add(i)
|
|
44
|
+
__encode_mapping(w, value, seen, stack_depth=stack_depth)
|
|
45
|
+
if stack_depth >= 100: # pragma: no cover
|
|
46
|
+
seen.remove(i)
|
|
47
|
+
stack_depth -= 1
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
if isinstance(value, (list, tuple)):
|
|
51
|
+
if stack_depth >= 100:
|
|
52
|
+
if i in seen:
|
|
53
|
+
raise BencodeEncodeError(f"circular reference found {value!r}")
|
|
54
|
+
seen.add(i)
|
|
55
|
+
|
|
56
|
+
w.write(b"l")
|
|
57
|
+
for item in value:
|
|
58
|
+
__encode(w, item, seen, stack_depth=stack_depth)
|
|
59
|
+
w.write(b"e")
|
|
60
|
+
|
|
61
|
+
if stack_depth >= 100: # pragma: no cover
|
|
62
|
+
seen.remove(i)
|
|
63
|
+
stack_depth -= 1
|
|
64
|
+
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
if isinstance(value, bytearray):
|
|
68
|
+
__encode_bytes(w, bytes(value))
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
if isinstance(value, memoryview):
|
|
72
|
+
w.write(str(len(value)).encode())
|
|
73
|
+
w.write(b":")
|
|
74
|
+
w.write(value)
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
if is_dataclass(value) and not isinstance(value, type):
|
|
78
|
+
if stack_depth >= 100:
|
|
79
|
+
if i in seen:
|
|
80
|
+
raise BencodeEncodeError(f"circular reference found {value!r}")
|
|
81
|
+
seen.add(i)
|
|
82
|
+
|
|
83
|
+
__encode_dataclass(w, value, seen, stack_depth=stack_depth)
|
|
84
|
+
|
|
85
|
+
if stack_depth >= 100: # pragma: no cover
|
|
86
|
+
seen.remove(i)
|
|
87
|
+
stack_depth -= 1
|
|
88
|
+
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
raise TypeError(f"type '{type(value)!r}' not supported by bencode")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def __encode_bytes(w: io.BytesIO, val: bytes) -> None:
|
|
95
|
+
w.write(str(len(val)).encode())
|
|
96
|
+
w.write(b":")
|
|
97
|
+
w.write(val)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def __encode_mapping(
|
|
101
|
+
w: io.BytesIO,
|
|
102
|
+
val: Mapping[Any, Any],
|
|
103
|
+
seen: set[int],
|
|
104
|
+
stack_depth: int,
|
|
105
|
+
) -> None:
|
|
106
|
+
w.write(b"d")
|
|
107
|
+
|
|
108
|
+
# force all keys to bytes, because str and bytes are incomparable
|
|
109
|
+
i_list: list[tuple[bytes, object]] = [(to_binary(k), v) for k, v in val.items()]
|
|
110
|
+
if not i_list:
|
|
111
|
+
w.write(b"e")
|
|
112
|
+
return
|
|
113
|
+
i_list.sort(key=lambda kv: kv[0])
|
|
114
|
+
__check_duplicated_keys(i_list)
|
|
115
|
+
|
|
116
|
+
for k, v in i_list:
|
|
117
|
+
__encode_bytes(w, k)
|
|
118
|
+
__encode(w, v, seen, stack_depth=stack_depth)
|
|
119
|
+
|
|
120
|
+
w.write(b"e")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def __encode_dataclass(w: io.BytesIO, x: Any, seen: set[int], stack_depth: int) -> None:
|
|
124
|
+
keys = fields(x)
|
|
125
|
+
if not keys:
|
|
126
|
+
w.write(b"de")
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
w.write(b"d")
|
|
130
|
+
|
|
131
|
+
ks = sorted([k.name for k in keys])
|
|
132
|
+
|
|
133
|
+
# no need to check duplicated keys, dataclasses will check this.
|
|
134
|
+
|
|
135
|
+
for k in ks:
|
|
136
|
+
__encode_bytes(w, k.encode())
|
|
137
|
+
__encode(w, getattr(x, k), seen, stack_depth=stack_depth)
|
|
138
|
+
|
|
139
|
+
w.write(b"e")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def __check_duplicated_keys(s: list[tuple[bytes, object]]) -> None:
|
|
143
|
+
last_key: bytes = s[0][0]
|
|
144
|
+
for current, _ in s[1:]:
|
|
145
|
+
if last_key == current:
|
|
146
|
+
raise BencodeEncodeError(
|
|
147
|
+
f"find duplicated keys {last_key!r} and {current.decode()}"
|
|
148
|
+
)
|
|
149
|
+
last_key = current
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def to_binary(s: str | bytes) -> bytes:
|
|
153
|
+
if isinstance(s, bytes):
|
|
154
|
+
return s
|
|
155
|
+
|
|
156
|
+
if isinstance(s, str):
|
|
157
|
+
return s.encode("utf-8", "strict")
|
|
158
|
+
|
|
159
|
+
raise TypeError(f"expected binary or text (found {type(s)})")
|
bencode2/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from .__bencode import BencodeDecodeError, BencodeEncodeError, bdecode, bencode
|
|
3
|
+
|
|
4
|
+
COMPILED = True
|
|
5
|
+
except ModuleNotFoundError:
|
|
6
|
+
from .__decoder import BencodeDecodeError, bdecode
|
|
7
|
+
from .__encoder import BencodeEncodeError, bencode
|
|
8
|
+
|
|
9
|
+
COMPILED = False
|
|
10
|
+
|
|
11
|
+
__all__ = (
|
|
12
|
+
"BencodeDecodeError",
|
|
13
|
+
"BencodeEncodeError",
|
|
14
|
+
"bencode",
|
|
15
|
+
"bdecode",
|
|
16
|
+
"COMPILED",
|
|
17
|
+
)
|
bencode2/__init__.pyi
ADDED
bencode2/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: bencode2
|
|
3
|
+
Version: 0.3.27
|
|
4
|
+
Summary: A fast and correct bencode serialize/deserialize library
|
|
5
|
+
Keywords: bencode,bittorrent,bit-torrent,serialize,deserialize,p2p
|
|
6
|
+
Author-Email: trim21 <trim21me@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Project-URL: Homepage, https://github.com/trim21/bencode-py
|
|
14
|
+
Project-URL: Repository, https://github.com/trim21/bencode-py
|
|
15
|
+
Project-URL: Issues, https://github.com/trim21/bencode-py/issues
|
|
16
|
+
Requires-Python: <4.0,>=3.9
|
|
17
|
+
Requires-Dist: typing_extensions>=4.8.0
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# A fast and correct bencode serialize/deserialize library
|
|
21
|
+
|
|
22
|
+
[](https://pypi.org/project/bencode2/)
|
|
23
|
+
[](https://github.com/trim21/bencode-py/actions/workflows/tests.yaml)
|
|
24
|
+
[](https://dl.circleci.com/status-badge/redirect/gh/trim21/bencode-py/tree/master)
|
|
25
|
+
[](https://pypi.org/project/bencode2/)
|
|
26
|
+
[](https://codecov.io/gh/Trim21/bencode-py/branch/master)
|
|
27
|
+
|
|
28
|
+
## introduction
|
|
29
|
+
|
|
30
|
+
Why yet another bencode package in python?
|
|
31
|
+
|
|
32
|
+
because I need a bencode library:
|
|
33
|
+
|
|
34
|
+
### 1. Correct
|
|
35
|
+
|
|
36
|
+
It should fully validate its inputs, both encoded bencode bytes, or python object to be
|
|
37
|
+
encoded.
|
|
38
|
+
|
|
39
|
+
And it should not decode bencode bytes to `str` by default.
|
|
40
|
+
|
|
41
|
+
Bencode doesn't have a utf-8 str type, only bytes,
|
|
42
|
+
so many decoder try to decode bytes to str and fallback to bytes,
|
|
43
|
+
**this package won't, it parse bencode bytes value as python bytes.**
|
|
44
|
+
|
|
45
|
+
It may be attempting to parse all dictionary keys as string,
|
|
46
|
+
but for BitTorrent v2 torrent, the keys in `pieces root` dictionary is still sha256 hash
|
|
47
|
+
instead of ascii/utf-8 string.
|
|
48
|
+
|
|
49
|
+
If you prefer string as dictionary keys, write a dedicated function to convert parsing
|
|
50
|
+
result.
|
|
51
|
+
|
|
52
|
+
Also be careful! Even file name or torrent name may not be valid utf-8 string.
|
|
53
|
+
|
|
54
|
+
### 2. Fast enough
|
|
55
|
+
|
|
56
|
+
this package is written with c++ in CPython.
|
|
57
|
+
|
|
58
|
+
### 3. still cross implement
|
|
59
|
+
|
|
60
|
+
This package sill have a pure python wheel `bencode2-${version}-py3-none-any.whl` wheel
|
|
61
|
+
on pypi.
|
|
62
|
+
|
|
63
|
+
Which means you can still use it in non-cpython python with same behavior.
|
|
64
|
+
|
|
65
|
+
## install
|
|
66
|
+
|
|
67
|
+
### pypi
|
|
68
|
+
|
|
69
|
+
```shell
|
|
70
|
+
pip install bencode2
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### conda/pixi
|
|
74
|
+
|
|
75
|
+
you can install conda package `bencode2` from <https://prefix.dev/channels/trim21-pkgs/packages/bencode2>.
|
|
76
|
+
|
|
77
|
+
## basic usage
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import bencode2
|
|
81
|
+
|
|
82
|
+
assert bencode2.bdecode(b"d4:spaml1:a1:bee") == {b"spam": [b"a", b"b"]}
|
|
83
|
+
|
|
84
|
+
assert bencode2.bencode({'hello': 'world'}) == b'd5:hello5:worlde'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Decoding
|
|
88
|
+
|
|
89
|
+
| bencode type | python type |
|
|
90
|
+
| :----------: | :---------: |
|
|
91
|
+
| integer | `int` |
|
|
92
|
+
| string | `bytes` |
|
|
93
|
+
| array | `list` |
|
|
94
|
+
| dictionary | `dict` |
|
|
95
|
+
|
|
96
|
+
bencode have 4 native types, integer, string, array and dictionary.
|
|
97
|
+
|
|
98
|
+
This package will decode integer to `int`, array to `list` and
|
|
99
|
+
dictionary to `dict`.
|
|
100
|
+
|
|
101
|
+
Because bencode string is not defined as utf-8 string, and will contain raw bytes
|
|
102
|
+
bencode2 will decode bencode string to python `bytes`.
|
|
103
|
+
|
|
104
|
+
### Encoding
|
|
105
|
+
|
|
106
|
+
| python type | bencode type |
|
|
107
|
+
| :-------------------------------: | :----------: |
|
|
108
|
+
| `bool` | integer 0/1 |
|
|
109
|
+
| `int`, `enum.IntEnum` | integer |
|
|
110
|
+
| `str`, `enum.StrEnum` | string |
|
|
111
|
+
| `bytes`, `bytearray`,`memoryview` | string |
|
|
112
|
+
| `list`, `tuple`, `NamedTuple` | array |
|
|
113
|
+
| `dict`, `OrderedDict` | dictionary |
|
|
114
|
+
| `types.MaapingProxy` | dictionary |
|
|
115
|
+
| dataclasses | dictionary |
|
|
116
|
+
|
|
117
|
+
## free threading
|
|
118
|
+
|
|
119
|
+
bencode2 have a free threading wheel on pypi, build with GIL disabled.
|
|
120
|
+
|
|
121
|
+
When encoding or decoding, it will not acquire GIL and may call non-thread-safy c-api,
|
|
122
|
+
which mean it's the caller's responsibility to ensure thread safety.
|
|
123
|
+
|
|
124
|
+
When calling `bencode`, it's safe to encode same object in multiple threading,
|
|
125
|
+
but it's not safe to encoding a object and change it in another thread at same time.
|
|
126
|
+
|
|
127
|
+
Also, when decoding, `bytes` objects are immutable so it's safe to be used in multiple
|
|
128
|
+
threading,
|
|
129
|
+
but `memoryview` and `bytearray` maybe not, please make sure underlay data doesn't
|
|
130
|
+
change when decoding.
|
|
131
|
+
|
|
132
|
+
## Development
|
|
133
|
+
|
|
134
|
+
This project use [meson](https://github.com/mesonbuild/meson) for building.
|
|
135
|
+
|
|
136
|
+
For testing pure python library,
|
|
137
|
+
make sure all so/pyd files in `src/bencode2` are removed, then run
|
|
138
|
+
`PYTHONPATH=src pytest --assert-pkg-compiled=false`.
|
|
139
|
+
|
|
140
|
+
For testing native extension, meson-python doesn't provide same function with
|
|
141
|
+
`python setup.py build_ext --inplace`.
|
|
142
|
+
|
|
143
|
+
So you will need to run command like this:
|
|
144
|
+
|
|
145
|
+
```shell
|
|
146
|
+
meson setup build
|
|
147
|
+
meson compile -C build
|
|
148
|
+
ninja -C build copy
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
ninja will need to build so/pyd with meson and copy it to `src/bencode2`,
|
|
152
|
+
|
|
153
|
+
then run tests with `PYTHONPATH=src pytest --assert-pkg-compiled=true`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
bencode2-0.3.27.dist-info/METADATA,sha256=i3yZybb2F0ApYlRoPZw9lpuShFgNbByPTz7Xoko9PWM,5283
|
|
2
|
+
bencode2-0.3.27.dist-info/WHEEL,sha256=HxTT_sQ360-hdmVNeweyxE6Ly6SvGGdMmSsv6PXi_1M,85
|
|
3
|
+
bencode2/__bencode.cp313-win_arm64.pyd,sha256=WeFAIO2gGRAy5dlebXgqWbq3EmxwPv8_Cek70AiysQo,191488
|
|
4
|
+
bencode2/__init__.py,sha256=z2lFW5z2l6pTccyzUGXkfFEcxqYwVgZ-iRuqrYC70bE,400
|
|
5
|
+
bencode2/__init__.pyi,sha256=9IRJDThFEll3bdqQTH56r9y0bpI-D3qN5VM1LIRpX04,245
|
|
6
|
+
bencode2/__encoder.py,sha256=ISaM-PLNdv8BKQFTq8YC_28czys9a0PbxzykCESYrKo,4355
|
|
7
|
+
bencode2/__decoder.py,sha256=drQInAg7UdKhfJgXI-40r3tPUvS20ep65lmcAGkGp9M,6398
|
|
8
|
+
bencode2/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
bencode2-0.3.27.dist-info/RECORD,,
|