fastbencode 0.2__tar.gz → 0.3.1__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.
- {fastbencode-0.2 → fastbencode-0.3.1}/PKG-INFO +16 -7
- {fastbencode-0.2 → fastbencode-0.3.1}/README.md +6 -0
- {fastbencode-0.2 → fastbencode-0.3.1}/fastbencode/__init__.py +6 -7
- fastbencode-0.3.1/fastbencode/_bencode_py.py +186 -0
- {fastbencode-0.2 → fastbencode-0.3.1}/fastbencode/_bencode_pyx.pyi +1 -1
- {fastbencode-0.2 → fastbencode-0.3.1}/fastbencode/_bencode_pyx.pyx +62 -12
- {fastbencode-0.2 → fastbencode-0.3.1}/fastbencode/tests/test_bencode.py +86 -13
- {fastbencode-0.2 → fastbencode-0.3.1}/fastbencode.egg-info/PKG-INFO +16 -7
- {fastbencode-0.2 → fastbencode-0.3.1}/fastbencode.egg-info/SOURCES.txt +0 -8
- {fastbencode-0.2 → fastbencode-0.3.1}/fastbencode.egg-info/requires.txt +3 -0
- fastbencode-0.3.1/pyproject.toml +75 -0
- fastbencode-0.3.1/setup.cfg +4 -0
- {fastbencode-0.2 → fastbencode-0.3.1}/setup.py +6 -4
- fastbencode-0.2/.github/workflows/disperse.yml +0 -24
- fastbencode-0.2/.github/workflows/pythonpackage.yml +0 -32
- fastbencode-0.2/.github/workflows/pythonpublish.yml +0 -58
- fastbencode-0.2/.gitignore +0 -8
- fastbencode-0.2/CODE_OF_CONDUCT.md +0 -76
- fastbencode-0.2/SECURITY.md +0 -5
- fastbencode-0.2/disperse.conf +0 -8
- fastbencode-0.2/fastbencode/_bencode_py.py +0 -162
- fastbencode-0.2/pyproject.toml +0 -3
- fastbencode-0.2/setup.cfg +0 -35
- {fastbencode-0.2 → fastbencode-0.3.1}/COPYING +0 -0
- {fastbencode-0.2 → fastbencode-0.3.1}/MANIFEST.in +0 -0
- {fastbencode-0.2 → fastbencode-0.3.1}/fastbencode/_bencode_pyx.h +0 -0
- {fastbencode-0.2 → fastbencode-0.3.1}/fastbencode/py.typed +0 -0
- {fastbencode-0.2 → fastbencode-0.3.1}/fastbencode/python-compat.h +0 -0
- {fastbencode-0.2 → fastbencode-0.3.1}/fastbencode/tests/__init__.py +0 -0
- {fastbencode-0.2 → fastbencode-0.3.1}/fastbencode.egg-info/dependency_links.txt +0 -0
- {fastbencode-0.2 → fastbencode-0.3.1}/fastbencode.egg-info/top_level.txt +0 -0
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fastbencode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Implementation of bencode with optional fast C extensions
|
|
5
|
-
|
|
6
|
-
Maintainer: Breezy Developers
|
|
7
|
-
Maintainer-email: breezy-core@googlegroups.com
|
|
5
|
+
Maintainer-email: Breezy Developers <breezy-core@googlegroups.com>
|
|
8
6
|
License: GPLv2 or later
|
|
7
|
+
Project-URL: Homepage, https://github.com/breezy-team/fastbencode
|
|
9
8
|
Project-URL: GitHub, https://github.com/breezy-team/fastbencode
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
11
9
|
Classifier: Programming Language :: Python :: 3.8
|
|
12
10
|
Classifier: Programming Language :: Python :: 3.9
|
|
13
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
14
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
16
15
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
17
16
|
Classifier: Operating System :: POSIX
|
|
18
17
|
Classifier: Operating System :: Microsoft :: Windows
|
|
19
|
-
Requires-Python: >=3.
|
|
20
|
-
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
21
20
|
License-File: COPYING
|
|
21
|
+
Provides-Extra: cext
|
|
22
|
+
Requires-Dist: cython>=0.29; extra == "cext"
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: ruff==0.4.3; extra == "dev"
|
|
22
25
|
|
|
23
26
|
fastbencode
|
|
24
27
|
===========
|
|
@@ -38,6 +41,12 @@ Example:
|
|
|
38
41
|
>>> bdecode(bencode([1, 2, b'a', {b'd': 3}]))
|
|
39
42
|
[1, 2, b'a', {b'd': 3}]
|
|
40
43
|
|
|
44
|
+
The default ``bencode``/``bdecode`` functions just operate on
|
|
45
|
+
bytestrings. Use ``bencode_utf8`` / ``bdecode_utf8`` to
|
|
46
|
+
serialize/deserialize all plain strings as UTF-8 bytestrings.
|
|
47
|
+
Note that for performance reasons, all dictionary keys still have to be
|
|
48
|
+
bytestrings.
|
|
49
|
+
|
|
41
50
|
License
|
|
42
51
|
=======
|
|
43
52
|
fastbencode is available under the GNU GPL, version 2 or later.
|
|
@@ -16,6 +16,12 @@ Example:
|
|
|
16
16
|
>>> bdecode(bencode([1, 2, b'a', {b'd': 3}]))
|
|
17
17
|
[1, 2, b'a', {b'd': 3}]
|
|
18
18
|
|
|
19
|
+
The default ``bencode``/``bdecode`` functions just operate on
|
|
20
|
+
bytestrings. Use ``bencode_utf8`` / ``bdecode_utf8`` to
|
|
21
|
+
serialize/deserialize all plain strings as UTF-8 bytestrings.
|
|
22
|
+
Note that for performance reasons, all dictionary keys still have to be
|
|
23
|
+
bytestrings.
|
|
24
|
+
|
|
19
25
|
License
|
|
20
26
|
=======
|
|
21
27
|
fastbencode is available under the GNU GPL, version 2 or later.
|
|
@@ -14,12 +14,11 @@
|
|
|
14
14
|
# along with this program; if not, write to the Free Software
|
|
15
15
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
16
16
|
|
|
17
|
-
"""Wrapper around the bencode cython and python implementation"""
|
|
17
|
+
"""Wrapper around the bencode cython and python implementation."""
|
|
18
18
|
|
|
19
19
|
from typing import Type
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
__version__ = (0, 2)
|
|
21
|
+
__version__ = (0, 3, 1)
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
_extension_load_failures = []
|
|
@@ -50,7 +49,7 @@ def failed_to_load_extension(exception):
|
|
|
50
49
|
if exception_str not in _extension_load_failures:
|
|
51
50
|
import warnings
|
|
52
51
|
warnings.warn(
|
|
53
|
-
'failed to load compiled extension:
|
|
52
|
+
f'failed to load compiled extension: {exception_str}',
|
|
54
53
|
UserWarning)
|
|
55
54
|
_extension_load_failures.append(exception_str)
|
|
56
55
|
|
|
@@ -58,12 +57,12 @@ def failed_to_load_extension(exception):
|
|
|
58
57
|
Bencached: Type
|
|
59
58
|
|
|
60
59
|
try:
|
|
61
|
-
from ._bencode_pyx import bdecode, bdecode_as_tuple, bencode
|
|
60
|
+
from ._bencode_pyx import Bencached, bdecode, bdecode_as_tuple, bencode
|
|
62
61
|
except ImportError as e:
|
|
63
62
|
failed_to_load_extension(e)
|
|
64
63
|
from ._bencode_py import ( # noqa: F401
|
|
64
|
+
Bencached,
|
|
65
65
|
bdecode,
|
|
66
66
|
bdecode_as_tuple,
|
|
67
67
|
bencode,
|
|
68
|
-
|
|
69
|
-
)
|
|
68
|
+
)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# bencode structured encoding
|
|
2
|
+
#
|
|
3
|
+
# Written by Petru Paler
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person
|
|
6
|
+
# obtaining a copy of this software and associated documentation files
|
|
7
|
+
# (the "Software"), to deal in the Software without restriction,
|
|
8
|
+
# including without limitation the rights to use, copy, modify, merge,
|
|
9
|
+
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
10
|
+
# and to permit persons to whom the Software is furnished to do so,
|
|
11
|
+
# subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be
|
|
14
|
+
# included in all copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# Modifications copyright (C) 2008 Canonical Ltd
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
from typing import Callable, Dict, List, Type
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BDecoder:
|
|
23
|
+
|
|
24
|
+
def __init__(self, yield_tuples=False, bytestring_encoding=None) -> None:
|
|
25
|
+
"""Constructor.
|
|
26
|
+
|
|
27
|
+
:param yield_tuples: if true, decode "l" elements as tuples rather than
|
|
28
|
+
lists.
|
|
29
|
+
"""
|
|
30
|
+
self.yield_tuples = yield_tuples
|
|
31
|
+
self.bytestring_encoding = bytestring_encoding
|
|
32
|
+
decode_func = {}
|
|
33
|
+
decode_func[b'l'] = self.decode_list
|
|
34
|
+
decode_func[b'd'] = self.decode_dict
|
|
35
|
+
decode_func[b'i'] = self.decode_int
|
|
36
|
+
decode_func[b'0'] = self.decode_bytes
|
|
37
|
+
decode_func[b'1'] = self.decode_bytes
|
|
38
|
+
decode_func[b'2'] = self.decode_bytes
|
|
39
|
+
decode_func[b'3'] = self.decode_bytes
|
|
40
|
+
decode_func[b'4'] = self.decode_bytes
|
|
41
|
+
decode_func[b'5'] = self.decode_bytes
|
|
42
|
+
decode_func[b'6'] = self.decode_bytes
|
|
43
|
+
decode_func[b'7'] = self.decode_bytes
|
|
44
|
+
decode_func[b'8'] = self.decode_bytes
|
|
45
|
+
decode_func[b'9'] = self.decode_bytes
|
|
46
|
+
self.decode_func = decode_func
|
|
47
|
+
|
|
48
|
+
def decode_int(self, x, f):
|
|
49
|
+
f += 1
|
|
50
|
+
newf = x.index(b'e', f)
|
|
51
|
+
n = int(x[f:newf])
|
|
52
|
+
if x[f:f + 2] == b'-0':
|
|
53
|
+
raise ValueError
|
|
54
|
+
elif x[f:f + 1] == b'0' and newf != f + 1:
|
|
55
|
+
raise ValueError
|
|
56
|
+
return (n, newf + 1)
|
|
57
|
+
|
|
58
|
+
def decode_bytes(self, x, f):
|
|
59
|
+
colon = x.index(b':', f)
|
|
60
|
+
n = int(x[f:colon])
|
|
61
|
+
if x[f:f + 1] == b'0' and colon != f + 1:
|
|
62
|
+
raise ValueError
|
|
63
|
+
colon += 1
|
|
64
|
+
d = x[colon:colon + n]
|
|
65
|
+
if self.bytestring_encoding:
|
|
66
|
+
d = d.decode(self.bytestring_encoding)
|
|
67
|
+
return (d, colon + n)
|
|
68
|
+
|
|
69
|
+
def decode_list(self, x, f):
|
|
70
|
+
r, f = [], f + 1
|
|
71
|
+
while x[f:f + 1] != b'e':
|
|
72
|
+
v, f = self.decode_func[x[f:f + 1]](x, f)
|
|
73
|
+
r.append(v)
|
|
74
|
+
if self.yield_tuples:
|
|
75
|
+
r = tuple(r)
|
|
76
|
+
return (r, f + 1)
|
|
77
|
+
|
|
78
|
+
def decode_dict(self, x, f):
|
|
79
|
+
r, f = {}, f + 1
|
|
80
|
+
lastkey = None
|
|
81
|
+
while x[f:f + 1] != b'e':
|
|
82
|
+
k, f = self.decode_bytes(x, f)
|
|
83
|
+
if lastkey is not None and lastkey >= k:
|
|
84
|
+
raise ValueError
|
|
85
|
+
lastkey = k
|
|
86
|
+
r[k], f = self.decode_func[x[f:f + 1]](x, f)
|
|
87
|
+
return (r, f + 1)
|
|
88
|
+
|
|
89
|
+
def bdecode(self, x):
|
|
90
|
+
if not isinstance(x, bytes):
|
|
91
|
+
raise TypeError
|
|
92
|
+
try:
|
|
93
|
+
r, l = self.decode_func[x[:1]](x, 0) # noqa: E741
|
|
94
|
+
except (IndexError, KeyError, OverflowError) as e:
|
|
95
|
+
raise ValueError(str(e))
|
|
96
|
+
if l != len(x): # noqa: E741
|
|
97
|
+
raise ValueError
|
|
98
|
+
return r
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
_decoder = BDecoder()
|
|
102
|
+
bdecode = _decoder.bdecode
|
|
103
|
+
|
|
104
|
+
_tuple_decoder = BDecoder(True)
|
|
105
|
+
bdecode_as_tuple = _tuple_decoder.bdecode
|
|
106
|
+
|
|
107
|
+
_utf8_decoder = BDecoder(bytestring_encoding='utf-8')
|
|
108
|
+
bdecode_utf8 = _utf8_decoder.bdecode
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class Bencached:
|
|
112
|
+
__slots__ = ['bencoded']
|
|
113
|
+
|
|
114
|
+
def __init__(self, s) -> None:
|
|
115
|
+
self.bencoded = s
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class BEncoder:
|
|
119
|
+
|
|
120
|
+
def __init__(self, bytestring_encoding=None):
|
|
121
|
+
self.bytestring_encoding = bytestring_encoding
|
|
122
|
+
self.encode_func: Dict[Type, Callable[[object, List[bytes]], None]] = {
|
|
123
|
+
Bencached: self.encode_bencached,
|
|
124
|
+
int: self.encode_int,
|
|
125
|
+
bytes: self.encode_bytes,
|
|
126
|
+
list: self.encode_list,
|
|
127
|
+
tuple: self.encode_list,
|
|
128
|
+
dict: self.encode_dict,
|
|
129
|
+
bool: self.encode_bool,
|
|
130
|
+
str: self.encode_str,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
def encode_bencached(self, x, r):
|
|
134
|
+
r.append(x.bencoded)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def encode_bool(self, x, r):
|
|
138
|
+
self.encode_int(int(x), r)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def encode_int(self, x, r):
|
|
142
|
+
r.extend((b'i', int_to_bytes(x), b'e'))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def encode_bytes(self, x, r):
|
|
146
|
+
r.extend((int_to_bytes(len(x)), b':', x))
|
|
147
|
+
|
|
148
|
+
def encode_list(self, x, r):
|
|
149
|
+
r.append(b'l')
|
|
150
|
+
for i in x:
|
|
151
|
+
self.encode(i, r)
|
|
152
|
+
r.append(b'e')
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def encode_dict(self, x, r):
|
|
156
|
+
r.append(b'd')
|
|
157
|
+
ilist = sorted(x.items())
|
|
158
|
+
for k, v in ilist:
|
|
159
|
+
r.extend((int_to_bytes(len(k)), b':', k))
|
|
160
|
+
self.encode(v, r)
|
|
161
|
+
r.append(b'e')
|
|
162
|
+
|
|
163
|
+
def encode_str(self, x, r):
|
|
164
|
+
if self.bytestring_encoding is None:
|
|
165
|
+
raise TypeError("string found but no encoding specified. "
|
|
166
|
+
"Use bencode_utf8 rather bencode?")
|
|
167
|
+
return self.encode_bytes(x.encode(self.bytestring_encoding), r)
|
|
168
|
+
|
|
169
|
+
def encode(self, x, r):
|
|
170
|
+
self.encode_func[type(x)](x, r)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def int_to_bytes(n):
|
|
174
|
+
return b'%d' % n
|
|
175
|
+
|
|
176
|
+
def bencode(x):
|
|
177
|
+
r = []
|
|
178
|
+
encoder = BEncoder()
|
|
179
|
+
encoder.encode(x, r)
|
|
180
|
+
return b''.join(r)
|
|
181
|
+
|
|
182
|
+
def bencode_utf8(x):
|
|
183
|
+
r = []
|
|
184
|
+
encoder = BEncoder(bytestring_encoding='utf-8')
|
|
185
|
+
encoder.encode(x, r)
|
|
186
|
+
return b''.join(r)
|
|
@@ -46,15 +46,22 @@ from cpython.mem cimport (
|
|
|
46
46
|
PyMem_Malloc,
|
|
47
47
|
PyMem_Realloc,
|
|
48
48
|
)
|
|
49
|
+
from cpython.unicode cimport (
|
|
50
|
+
PyUnicode_FromEncodedObject,
|
|
51
|
+
PyUnicode_FromStringAndSize,
|
|
52
|
+
PyUnicode_Check,
|
|
53
|
+
)
|
|
49
54
|
from cpython.tuple cimport (
|
|
50
55
|
PyTuple_CheckExact,
|
|
51
56
|
)
|
|
52
57
|
|
|
53
58
|
from libc.stdlib cimport (
|
|
54
59
|
strtol,
|
|
60
|
+
free,
|
|
55
61
|
)
|
|
56
62
|
from libc.string cimport (
|
|
57
63
|
memcpy,
|
|
64
|
+
strdup,
|
|
58
65
|
)
|
|
59
66
|
|
|
60
67
|
cdef extern from "python-compat.h":
|
|
@@ -79,9 +86,10 @@ cdef class Decoder:
|
|
|
79
86
|
cdef readonly char *tail
|
|
80
87
|
cdef readonly int size
|
|
81
88
|
cdef readonly int _yield_tuples
|
|
89
|
+
cdef readonly char *_bytestring_encoding
|
|
82
90
|
cdef object text
|
|
83
91
|
|
|
84
|
-
def __init__(self, s, yield_tuples=0):
|
|
92
|
+
def __init__(self, s, yield_tuples=0, str bytestring_encoding=None):
|
|
85
93
|
"""Initialize decoder engine.
|
|
86
94
|
@param s: Python string.
|
|
87
95
|
"""
|
|
@@ -92,6 +100,13 @@ cdef class Decoder:
|
|
|
92
100
|
self.tail = PyBytes_AS_STRING(s)
|
|
93
101
|
self.size = PyBytes_GET_SIZE(s)
|
|
94
102
|
self._yield_tuples = int(yield_tuples)
|
|
103
|
+
if bytestring_encoding is None:
|
|
104
|
+
self._bytestring_encoding = NULL
|
|
105
|
+
else:
|
|
106
|
+
self._bytestring_encoding = strdup(bytestring_encoding.encode('utf-8'))
|
|
107
|
+
|
|
108
|
+
def __dealloc__(self):
|
|
109
|
+
free(self._bytestring_encoding)
|
|
95
110
|
|
|
96
111
|
def decode(self):
|
|
97
112
|
result = self._decode_object()
|
|
@@ -112,7 +127,7 @@ cdef class Decoder:
|
|
|
112
127
|
try:
|
|
113
128
|
ch = self.tail[0]
|
|
114
129
|
if c'0' <= ch <= c'9':
|
|
115
|
-
return self.
|
|
130
|
+
return self._decode_bytes()
|
|
116
131
|
elif ch == c'l':
|
|
117
132
|
D_UPDATE_TAIL(self, 1)
|
|
118
133
|
return self._decode_list()
|
|
@@ -155,12 +170,12 @@ cdef class Decoder:
|
|
|
155
170
|
D_UPDATE_TAIL(self, i+1)
|
|
156
171
|
return ret
|
|
157
172
|
|
|
158
|
-
cdef object
|
|
173
|
+
cdef object _decode_bytes(self):
|
|
159
174
|
cdef int n
|
|
160
175
|
cdef char *next_tail
|
|
161
176
|
# strtol allows leading whitespace, negatives, and leading zeros
|
|
162
177
|
# however, all callers have already checked that '0' <= tail[0] <= '9'
|
|
163
|
-
# or they wouldn't have called
|
|
178
|
+
# or they wouldn't have called _decode_bytes
|
|
164
179
|
# strtol will stop at trailing whitespace, etc
|
|
165
180
|
n = strtol(self.tail, &next_tail, 10)
|
|
166
181
|
if next_tail == NULL or next_tail[0] != c':':
|
|
@@ -171,13 +186,22 @@ cdef class Decoder:
|
|
|
171
186
|
raise ValueError('leading zeros are not allowed')
|
|
172
187
|
D_UPDATE_TAIL(self, next_tail - self.tail + 1)
|
|
173
188
|
if n == 0:
|
|
174
|
-
|
|
189
|
+
if self._bytestring_encoding == NULL:
|
|
190
|
+
return b''
|
|
191
|
+
else:
|
|
192
|
+
return ''
|
|
175
193
|
if n > self.size:
|
|
176
194
|
raise ValueError('stream underflow')
|
|
177
195
|
if n < 0:
|
|
178
196
|
raise ValueError('string size below zero: %d' % n)
|
|
179
197
|
|
|
180
|
-
|
|
198
|
+
if self._bytestring_encoding == NULL:
|
|
199
|
+
result = PyBytes_FromStringAndSize(self.tail, n)
|
|
200
|
+
elif self._bytestring_encoding == b'utf-8':
|
|
201
|
+
result = PyUnicode_FromStringAndSize(self.tail, n)
|
|
202
|
+
else:
|
|
203
|
+
result = PyBytes_FromStringAndSize(self.tail, n)
|
|
204
|
+
result = PyUnicode_FromEncodedObject(result, self._bytestring_encoding, NULL)
|
|
181
205
|
D_UPDATE_TAIL(self, n)
|
|
182
206
|
return result
|
|
183
207
|
|
|
@@ -214,7 +238,7 @@ cdef class Decoder:
|
|
|
214
238
|
# keys should be strings only
|
|
215
239
|
if self.tail[0] < c'0' or self.tail[0] > c'9':
|
|
216
240
|
raise ValueError('key was not a simple string.')
|
|
217
|
-
key = self.
|
|
241
|
+
key = self._decode_bytes()
|
|
218
242
|
if lastkey is not None and lastkey >= key:
|
|
219
243
|
raise ValueError('dict keys disordered')
|
|
220
244
|
else:
|
|
@@ -235,6 +259,11 @@ def bdecode_as_tuple(object s):
|
|
|
235
259
|
return Decoder(s, True).decode()
|
|
236
260
|
|
|
237
261
|
|
|
262
|
+
def bdecode_utf8(object s):
|
|
263
|
+
"""Decode string x to Python object, decoding bytestrings as UTF8 strings."""
|
|
264
|
+
return Decoder(s, bytestring_encoding='utf-8').decode()
|
|
265
|
+
|
|
266
|
+
|
|
238
267
|
class Bencached(object):
|
|
239
268
|
__slots__ = ['bencoded']
|
|
240
269
|
|
|
@@ -254,8 +283,9 @@ cdef class Encoder:
|
|
|
254
283
|
cdef readonly int size
|
|
255
284
|
cdef readonly char *buffer
|
|
256
285
|
cdef readonly int maxsize
|
|
286
|
+
cdef readonly object _bytestring_encoding
|
|
257
287
|
|
|
258
|
-
def __init__(self, int maxsize=INITSIZE):
|
|
288
|
+
def __init__(self, int maxsize=INITSIZE, str bytestring_encoding=None):
|
|
259
289
|
"""Initialize encoder engine
|
|
260
290
|
@param maxsize: initial size of internal char buffer
|
|
261
291
|
"""
|
|
@@ -273,6 +303,8 @@ cdef class Encoder:
|
|
|
273
303
|
self.maxsize = maxsize
|
|
274
304
|
self.tail = p
|
|
275
305
|
|
|
306
|
+
self._bytestring_encoding = bytestring_encoding
|
|
307
|
+
|
|
276
308
|
def __dealloc__(self):
|
|
277
309
|
PyMem_Free(self.buffer)
|
|
278
310
|
self.buffer = NULL
|
|
@@ -329,7 +361,7 @@ cdef class Encoder:
|
|
|
329
361
|
E_UPDATE_TAIL(self, n)
|
|
330
362
|
return 1
|
|
331
363
|
|
|
332
|
-
cdef int
|
|
364
|
+
cdef int _encode_bytes(self, x) except 0:
|
|
333
365
|
cdef int n
|
|
334
366
|
cdef Py_ssize_t x_len
|
|
335
367
|
x_len = PyBytes_GET_SIZE(x)
|
|
@@ -341,6 +373,12 @@ cdef class Encoder:
|
|
|
341
373
|
E_UPDATE_TAIL(self, n + x_len)
|
|
342
374
|
return 1
|
|
343
375
|
|
|
376
|
+
cdef int _encode_string(self, x) except 0:
|
|
377
|
+
if self._bytestring_encoding is None:
|
|
378
|
+
raise TypeError("string found but no encoding specified. "
|
|
379
|
+
"Use bencode_utf8 rather bencode?")
|
|
380
|
+
return self._encode_bytes(x.encode(self._bytestring_encoding))
|
|
381
|
+
|
|
344
382
|
cdef int _encode_list(self, x) except 0:
|
|
345
383
|
self._ensure_buffer(1)
|
|
346
384
|
self.tail[0] = c'l'
|
|
@@ -362,7 +400,7 @@ cdef class Encoder:
|
|
|
362
400
|
for k in sorted(x):
|
|
363
401
|
if not PyBytes_CheckExact(k):
|
|
364
402
|
raise TypeError('key in dict should be string')
|
|
365
|
-
self.
|
|
403
|
+
self._encode_bytes(k)
|
|
366
404
|
self.process(x[k])
|
|
367
405
|
|
|
368
406
|
self._ensure_buffer(1)
|
|
@@ -374,7 +412,7 @@ cdef class Encoder:
|
|
|
374
412
|
BrzPy_EnterRecursiveCall(" while bencode encoding")
|
|
375
413
|
try:
|
|
376
414
|
if PyBytes_CheckExact(x):
|
|
377
|
-
self.
|
|
415
|
+
self._encode_bytes(x)
|
|
378
416
|
elif PyInt_CheckExact(x) and x.bit_length() < 32:
|
|
379
417
|
self._encode_int(x)
|
|
380
418
|
elif PyLong_CheckExact(x):
|
|
@@ -385,6 +423,8 @@ cdef class Encoder:
|
|
|
385
423
|
self._encode_dict(x)
|
|
386
424
|
elif PyBool_Check(x):
|
|
387
425
|
self._encode_int(int(x))
|
|
426
|
+
elif PyUnicode_Check(x):
|
|
427
|
+
self._encode_string(x)
|
|
388
428
|
elif isinstance(x, Bencached):
|
|
389
429
|
self._append_string(x.bencoded)
|
|
390
430
|
else:
|
|
@@ -394,7 +434,17 @@ cdef class Encoder:
|
|
|
394
434
|
|
|
395
435
|
|
|
396
436
|
def bencode(x):
|
|
397
|
-
"""Encode Python object x to
|
|
437
|
+
"""Encode Python object x to bytestring"""
|
|
398
438
|
encoder = Encoder()
|
|
399
439
|
encoder.process(x)
|
|
400
440
|
return encoder.to_bytes()
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def bencode_utf8(x):
|
|
444
|
+
"""Encode Python object x to bytestring.
|
|
445
|
+
|
|
446
|
+
Encode any strings as UTF8
|
|
447
|
+
"""
|
|
448
|
+
encoder = Encoder(bytestring_encoding='utf-8')
|
|
449
|
+
encoder.process(x)
|
|
450
|
+
return encoder.to_bytes()
|
|
@@ -14,11 +14,10 @@
|
|
|
14
14
|
# along with this program; if not, write to the Free Software
|
|
15
15
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
16
16
|
|
|
17
|
-
"""Tests for bencode structured encoding"""
|
|
17
|
+
"""Tests for bencode structured encoding."""
|
|
18
18
|
|
|
19
19
|
import copy
|
|
20
20
|
import sys
|
|
21
|
-
|
|
22
21
|
from unittest import TestCase, TestSuite
|
|
23
22
|
|
|
24
23
|
|
|
@@ -62,15 +61,14 @@ def get_named_object(module_name, member_name=None):
|
|
|
62
61
|
|
|
63
62
|
|
|
64
63
|
def iter_suite_tests(suite):
|
|
65
|
-
"""Return all tests in a suite, recursing through nested suites"""
|
|
64
|
+
"""Return all tests in a suite, recursing through nested suites."""
|
|
66
65
|
if isinstance(suite, TestCase):
|
|
67
66
|
yield suite
|
|
68
67
|
elif isinstance(suite, TestSuite):
|
|
69
68
|
for item in suite:
|
|
70
69
|
yield from iter_suite_tests(item)
|
|
71
70
|
else:
|
|
72
|
-
raise Exception('unknown type
|
|
73
|
-
% (type(suite), suite))
|
|
71
|
+
raise Exception(f'unknown type {type(suite)!r} for object {suite!r}')
|
|
74
72
|
|
|
75
73
|
|
|
76
74
|
def clone_test(test, new_id):
|
|
@@ -107,7 +105,7 @@ def apply_scenario(test, scenario):
|
|
|
107
105
|
test.
|
|
108
106
|
:return: The adapted test.
|
|
109
107
|
"""
|
|
110
|
-
new_id = "{
|
|
108
|
+
new_id = f"{test.id()}({scenario[0]})"
|
|
111
109
|
new_test = clone_test(test, new_id)
|
|
112
110
|
for name, value in scenario[1].items():
|
|
113
111
|
setattr(new_test, name, value)
|
|
@@ -188,7 +186,6 @@ def permute_tests_for_extension(standard_tests, loader, py_module_name,
|
|
|
188
186
|
tests. feature is the Feature object that can be used to determine if
|
|
189
187
|
the module is available.
|
|
190
188
|
"""
|
|
191
|
-
|
|
192
189
|
py_module = get_named_object(py_module_name)
|
|
193
190
|
scenarios = [
|
|
194
191
|
('python', {'module': py_module}),
|
|
@@ -213,7 +210,7 @@ def load_tests(loader, standard_tests, pattern):
|
|
|
213
210
|
class RecursionLimit:
|
|
214
211
|
"""Context manager that lowers recursion limit for testing."""
|
|
215
212
|
|
|
216
|
-
def __init__(self, limit=100):
|
|
213
|
+
def __init__(self, limit=100) -> None:
|
|
217
214
|
self._new_limit = limit
|
|
218
215
|
self._old_limit = sys.getrecursionlimit()
|
|
219
216
|
|
|
@@ -290,10 +287,16 @@ class TestBencodeDecode(TestCase):
|
|
|
290
287
|
|
|
291
288
|
def test_list_deepnested(self):
|
|
292
289
|
import platform
|
|
293
|
-
if platform.python_implementation() == 'PyPy'
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
290
|
+
if (platform.python_implementation() == 'PyPy'
|
|
291
|
+
or sys.version_info[:2] >= (3, 12)):
|
|
292
|
+
expected = []
|
|
293
|
+
for i in range(99):
|
|
294
|
+
expected = [expected]
|
|
295
|
+
self._check(expected, (b"l" * 100) + (b"e" * 100))
|
|
296
|
+
else:
|
|
297
|
+
with RecursionLimit():
|
|
298
|
+
self._run_check_error(
|
|
299
|
+
RuntimeError, (b"l" * 100) + (b"e" * 100))
|
|
297
300
|
|
|
298
301
|
def test_malformed_list(self):
|
|
299
302
|
self._run_check_error(ValueError, b'l')
|
|
@@ -338,7 +341,7 @@ class TestBencodeDecode(TestCase):
|
|
|
338
341
|
self.assertRaises(ValueError, self.module.bdecode, b'relwjhrlewjh')
|
|
339
342
|
|
|
340
343
|
def test_unsupported_type(self):
|
|
341
|
-
self._run_check_error(TypeError,
|
|
344
|
+
self._run_check_error(TypeError, 1.5)
|
|
342
345
|
self._run_check_error(TypeError, None)
|
|
343
346
|
self._run_check_error(TypeError, lambda x: x)
|
|
344
347
|
self._run_check_error(TypeError, object)
|
|
@@ -348,6 +351,47 @@ class TestBencodeDecode(TestCase):
|
|
|
348
351
|
self.assertRaises(TypeError, self.module.bdecode, 1)
|
|
349
352
|
|
|
350
353
|
|
|
354
|
+
class TestBdecodeUtf8(TestCase):
|
|
355
|
+
|
|
356
|
+
module = None
|
|
357
|
+
|
|
358
|
+
def _check(self, expected, source):
|
|
359
|
+
self.assertEqual(expected, self.module.bdecode_utf8(source))
|
|
360
|
+
|
|
361
|
+
def _run_check_error(self, exc, bad):
|
|
362
|
+
"""Check that bdecoding a string raises a particular exception."""
|
|
363
|
+
self.assertRaises(exc, self.module.bdecode_utf8, bad)
|
|
364
|
+
|
|
365
|
+
def test_string(self):
|
|
366
|
+
self._check('', b'0:')
|
|
367
|
+
self._check('aäc', b'4:a\xc3\xa4c')
|
|
368
|
+
self._check('1234567890', b'10:1234567890')
|
|
369
|
+
|
|
370
|
+
def test_large_string(self):
|
|
371
|
+
self.assertRaises(
|
|
372
|
+
ValueError, self.module.bdecode_utf8, b"2147483639:foo")
|
|
373
|
+
|
|
374
|
+
def test_malformed_string(self):
|
|
375
|
+
self._run_check_error(ValueError, b'10:x')
|
|
376
|
+
self._run_check_error(ValueError, b'10:')
|
|
377
|
+
self._run_check_error(ValueError, b'10')
|
|
378
|
+
self._run_check_error(ValueError, b'01:x')
|
|
379
|
+
self._run_check_error(ValueError, b'00:')
|
|
380
|
+
self._run_check_error(ValueError, b'35208734823ljdahflajhdf')
|
|
381
|
+
self._run_check_error(ValueError, b'432432432432432:foo')
|
|
382
|
+
self._run_check_error(ValueError, b' 1:x') # leading whitespace
|
|
383
|
+
self._run_check_error(ValueError, b'-1:x') # negative
|
|
384
|
+
self._run_check_error(ValueError, b'1 x') # space vs colon
|
|
385
|
+
self._run_check_error(ValueError, b'1x') # missing colon
|
|
386
|
+
self._run_check_error(ValueError, (b'1' * 1000) + b':')
|
|
387
|
+
|
|
388
|
+
def test_empty_string(self):
|
|
389
|
+
self.assertRaises(ValueError, self.module.bdecode_utf8, b'')
|
|
390
|
+
|
|
391
|
+
def test_invalid_utf8(self):
|
|
392
|
+
self._run_check_error(UnicodeDecodeError, b'3:\xff\xfe\xfd')
|
|
393
|
+
|
|
394
|
+
|
|
351
395
|
class TestBencodeEncode(TestCase):
|
|
352
396
|
|
|
353
397
|
module = None
|
|
@@ -411,3 +455,32 @@ class TestBencodeEncode(TestCase):
|
|
|
411
455
|
def test_bool(self):
|
|
412
456
|
self._check(b'i1e', True)
|
|
413
457
|
self._check(b'i0e', False)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
class TestBencodeEncodeUtf8(TestCase):
|
|
461
|
+
|
|
462
|
+
module = None
|
|
463
|
+
|
|
464
|
+
def _check(self, expected, source):
|
|
465
|
+
self.assertEqual(expected, self.module.bencode_utf8(source))
|
|
466
|
+
|
|
467
|
+
def test_string(self):
|
|
468
|
+
self._check(b'0:', '')
|
|
469
|
+
self._check(b'3:abc', 'abc')
|
|
470
|
+
self._check(b'10:1234567890', '1234567890')
|
|
471
|
+
|
|
472
|
+
def test_list(self):
|
|
473
|
+
self._check(b'le', [])
|
|
474
|
+
self._check(b'li1ei2ei3ee', [1, 2, 3])
|
|
475
|
+
self._check(b'll5:Alice3:Bobeli2ei3eee', [['Alice', 'Bob'], [2, 3]])
|
|
476
|
+
|
|
477
|
+
def test_list_as_tuple(self):
|
|
478
|
+
self._check(b'le', ())
|
|
479
|
+
self._check(b'li1ei2ei3ee', (1, 2, 3))
|
|
480
|
+
self._check(b'll5:Alice3:Bobeli2ei3eee', (('Alice', 'Bob'), (2, 3)))
|
|
481
|
+
|
|
482
|
+
def test_dict(self):
|
|
483
|
+
self._check(b'de', {})
|
|
484
|
+
self._check(b'd3:agei25e4:eyes4:bluee', {b'age': 25, b'eyes': 'blue'})
|
|
485
|
+
self._check(b'd8:spam.mp3d6:author5:Alice6:lengthi100000eee',
|
|
486
|
+
{b'spam.mp3': {b'author': b'Alice', b'length': 100000}})
|
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fastbencode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Implementation of bencode with optional fast C extensions
|
|
5
|
-
|
|
6
|
-
Maintainer: Breezy Developers
|
|
7
|
-
Maintainer-email: breezy-core@googlegroups.com
|
|
5
|
+
Maintainer-email: Breezy Developers <breezy-core@googlegroups.com>
|
|
8
6
|
License: GPLv2 or later
|
|
7
|
+
Project-URL: Homepage, https://github.com/breezy-team/fastbencode
|
|
9
8
|
Project-URL: GitHub, https://github.com/breezy-team/fastbencode
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
11
9
|
Classifier: Programming Language :: Python :: 3.8
|
|
12
10
|
Classifier: Programming Language :: Python :: 3.9
|
|
13
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
14
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
16
15
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
17
16
|
Classifier: Operating System :: POSIX
|
|
18
17
|
Classifier: Operating System :: Microsoft :: Windows
|
|
19
|
-
Requires-Python: >=3.
|
|
20
|
-
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
21
20
|
License-File: COPYING
|
|
21
|
+
Provides-Extra: cext
|
|
22
|
+
Requires-Dist: cython>=0.29; extra == "cext"
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: ruff==0.4.3; extra == "dev"
|
|
22
25
|
|
|
23
26
|
fastbencode
|
|
24
27
|
===========
|
|
@@ -38,6 +41,12 @@ Example:
|
|
|
38
41
|
>>> bdecode(bencode([1, 2, b'a', {b'd': 3}]))
|
|
39
42
|
[1, 2, b'a', {b'd': 3}]
|
|
40
43
|
|
|
44
|
+
The default ``bencode``/``bdecode`` functions just operate on
|
|
45
|
+
bytestrings. Use ``bencode_utf8`` / ``bdecode_utf8`` to
|
|
46
|
+
serialize/deserialize all plain strings as UTF-8 bytestrings.
|
|
47
|
+
Note that for performance reasons, all dictionary keys still have to be
|
|
48
|
+
bytestrings.
|
|
49
|
+
|
|
41
50
|
License
|
|
42
51
|
=======
|
|
43
52
|
fastbencode is available under the GNU GPL, version 2 or later.
|
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
.gitignore
|
|
2
|
-
CODE_OF_CONDUCT.md
|
|
3
1
|
COPYING
|
|
4
2
|
MANIFEST.in
|
|
5
3
|
README.md
|
|
6
|
-
SECURITY.md
|
|
7
|
-
disperse.conf
|
|
8
4
|
pyproject.toml
|
|
9
|
-
setup.cfg
|
|
10
5
|
setup.py
|
|
11
|
-
.github/workflows/disperse.yml
|
|
12
|
-
.github/workflows/pythonpackage.yml
|
|
13
|
-
.github/workflows/pythonpublish.yml
|
|
14
6
|
fastbencode/__init__.py
|
|
15
7
|
fastbencode/_bencode_py.py
|
|
16
8
|
fastbencode/_bencode_pyx.h
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [
|
|
3
|
+
"setuptools>=61.2",
|
|
4
|
+
"cython>=0.29",
|
|
5
|
+
]
|
|
6
|
+
build-backend = "setuptools.build_meta"
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "fastbencode"
|
|
10
|
+
description = "Implementation of bencode with optional fast C extensions"
|
|
11
|
+
maintainers = [{name = "Breezy Developers", email = "breezy-core@googlegroups.com"}]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = {text = "GPLv2 or later"}
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3.8",
|
|
16
|
+
"Programming Language :: Python :: 3.9",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
21
|
+
"Programming Language :: Python :: Implementation :: PyPy",
|
|
22
|
+
"Operating System :: POSIX",
|
|
23
|
+
"Operating System :: Microsoft :: Windows",
|
|
24
|
+
]
|
|
25
|
+
requires-python = ">=3.8"
|
|
26
|
+
dynamic = ["version"]
|
|
27
|
+
dependencies = []
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/breezy-team/fastbencode"
|
|
31
|
+
GitHub = "https://github.com/breezy-team/fastbencode"
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
cext = ["cython>=0.29"]
|
|
35
|
+
dev = [
|
|
36
|
+
"ruff==0.4.3"
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[tool.setuptools]
|
|
40
|
+
packages = ["fastbencode"]
|
|
41
|
+
include-package-data = false
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.dynamic]
|
|
44
|
+
version = {attr = "fastbencode.__version__"}
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
target-version = "py37"
|
|
48
|
+
line-length = 79
|
|
49
|
+
|
|
50
|
+
[tool.ruff.lint]
|
|
51
|
+
select = [
|
|
52
|
+
"ANN",
|
|
53
|
+
"D",
|
|
54
|
+
"E",
|
|
55
|
+
"F",
|
|
56
|
+
"I",
|
|
57
|
+
"UP",
|
|
58
|
+
]
|
|
59
|
+
ignore = [
|
|
60
|
+
"ANN001",
|
|
61
|
+
"ANN002",
|
|
62
|
+
"ANN101",
|
|
63
|
+
"ANN201",
|
|
64
|
+
"ANN202",
|
|
65
|
+
"ANN204",
|
|
66
|
+
"D100",
|
|
67
|
+
"D101",
|
|
68
|
+
"D102",
|
|
69
|
+
"D103",
|
|
70
|
+
"D105",
|
|
71
|
+
"D107",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
[tool.ruff.lint.pydocstyle]
|
|
75
|
+
convention = "google"
|
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
from setuptools import Extension, setup
|
|
7
|
+
|
|
6
8
|
try:
|
|
7
9
|
from packaging.version import Version
|
|
8
10
|
except ImportError:
|
|
9
11
|
from distutils.version import LooseVersion as Version
|
|
10
12
|
|
|
11
13
|
try:
|
|
12
|
-
from Cython.Distutils import build_ext
|
|
13
14
|
from Cython.Compiler.Version import version as cython_version
|
|
15
|
+
from Cython.Distutils import build_ext
|
|
14
16
|
except ImportError:
|
|
15
17
|
have_cython = False
|
|
16
18
|
# try to build the extension from the prior generated source.
|
|
@@ -26,8 +28,8 @@ else:
|
|
|
26
28
|
cython_version_info = Version(cython_version)
|
|
27
29
|
if cython_version_info < Version(minimum_cython_version):
|
|
28
30
|
print("Version of Cython is too old. "
|
|
29
|
-
"Current is
|
|
30
|
-
|
|
31
|
+
f"Current is {cython_version}, "
|
|
32
|
+
f"need at least {minimum_cython_version}.")
|
|
31
33
|
print("If the .c files are available, they will be built,"
|
|
32
34
|
" but modifying the .pyx files will not rebuild them.")
|
|
33
35
|
have_cython = False
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: Disperse configuration
|
|
3
|
-
|
|
4
|
-
"on":
|
|
5
|
-
- push
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
build:
|
|
9
|
-
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
|
|
12
|
-
steps:
|
|
13
|
-
- uses: actions/checkout@v2
|
|
14
|
-
- name: Set up Python
|
|
15
|
-
uses: actions/setup-python@v2
|
|
16
|
-
- name: Install dependencies
|
|
17
|
-
run: |
|
|
18
|
-
sudo apt install protobuf-compiler
|
|
19
|
-
- name: Install disperse
|
|
20
|
-
run: |
|
|
21
|
-
pip install git+https://github.com/jelmer/disperse
|
|
22
|
-
- name: Validate disperse.conf
|
|
23
|
-
run: |
|
|
24
|
-
PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python disperse validate .
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
name: Python package
|
|
2
|
-
|
|
3
|
-
on: [push, pull_request]
|
|
4
|
-
|
|
5
|
-
jobs:
|
|
6
|
-
build:
|
|
7
|
-
|
|
8
|
-
runs-on: ${{ matrix.os }}
|
|
9
|
-
strategy:
|
|
10
|
-
matrix:
|
|
11
|
-
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
12
|
-
python-version: [3.7, 3.8, 3.9, '3.10', '3.11']
|
|
13
|
-
fail-fast: false
|
|
14
|
-
|
|
15
|
-
steps:
|
|
16
|
-
- uses: actions/checkout@v2
|
|
17
|
-
- name: Set up Python ${{ matrix.python-version }}
|
|
18
|
-
uses: actions/setup-python@v2
|
|
19
|
-
with:
|
|
20
|
-
python-version: ${{ matrix.python-version }}
|
|
21
|
-
- name: Install dependencies
|
|
22
|
-
run: |
|
|
23
|
-
python -m pip install --upgrade pip cython
|
|
24
|
-
pip install -U pip flake8
|
|
25
|
-
- name: Style checks
|
|
26
|
-
run: |
|
|
27
|
-
python -m flake8
|
|
28
|
-
- name: Test suite run
|
|
29
|
-
run: |
|
|
30
|
-
python -m unittest fastbencode.tests.test_suite
|
|
31
|
-
env:
|
|
32
|
-
PYTHONHASHSEED: random
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: Build Python Wheels
|
|
3
|
-
|
|
4
|
-
"on":
|
|
5
|
-
push:
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
build:
|
|
9
|
-
runs-on: ${{ matrix.os }}
|
|
10
|
-
strategy:
|
|
11
|
-
matrix:
|
|
12
|
-
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
13
|
-
fail-fast: true
|
|
14
|
-
|
|
15
|
-
steps:
|
|
16
|
-
- uses: actions/checkout@v3
|
|
17
|
-
- uses: actions/setup-python@v3
|
|
18
|
-
- name: Install dependencies
|
|
19
|
-
run: |
|
|
20
|
-
python -m pip install --upgrade pip cython
|
|
21
|
-
pip install setuptools wheel cibuildwheel
|
|
22
|
-
- name: Run test suite
|
|
23
|
-
run: python -m unittest fastbencode.tests.test_suite
|
|
24
|
-
- name: Set up QEMU
|
|
25
|
-
uses: docker/setup-qemu-action@v1
|
|
26
|
-
if: "matrix.os == 'ubuntu-latest'"
|
|
27
|
-
- name: Build wheels
|
|
28
|
-
run: python -m cibuildwheel --output-dir wheelhouse
|
|
29
|
-
env:
|
|
30
|
-
CIBW_ARCHS_LINUX: x86_64 aarch64
|
|
31
|
-
CIBW_ARCHS_MACOS: x86_64 arm64 universal2
|
|
32
|
-
CIBW_ARCHS_WINDOWS: AMD64 x86
|
|
33
|
-
- name: Upload wheels
|
|
34
|
-
uses: actions/upload-artifact@v3
|
|
35
|
-
with:
|
|
36
|
-
path: ./wheelhouse/*.whl
|
|
37
|
-
|
|
38
|
-
publish:
|
|
39
|
-
runs-on: ubuntu-latest
|
|
40
|
-
|
|
41
|
-
needs: build
|
|
42
|
-
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
|
43
|
-
steps:
|
|
44
|
-
- uses: actions/setup-python@v3
|
|
45
|
-
|
|
46
|
-
- name: Install twine
|
|
47
|
-
run: |
|
|
48
|
-
python -m pip install --upgrade pip
|
|
49
|
-
pip install twine
|
|
50
|
-
- name: Download wheels
|
|
51
|
-
uses: actions/download-artifact@v2
|
|
52
|
-
- name: Display structure of downloaded files
|
|
53
|
-
run: ls -R
|
|
54
|
-
- name: Publish wheels
|
|
55
|
-
env:
|
|
56
|
-
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
|
57
|
-
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
|
58
|
-
run: twine upload artifact/*.whl
|
fastbencode-0.2/.gitignore
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# Contributor Covenant Code of Conduct
|
|
2
|
-
|
|
3
|
-
## Our Pledge
|
|
4
|
-
|
|
5
|
-
In the interest of fostering an open and welcoming environment, we as
|
|
6
|
-
contributors and maintainers pledge to making participation in our project and
|
|
7
|
-
our community a harassment-free experience for everyone, regardless of age, body
|
|
8
|
-
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
|
9
|
-
level of experience, education, socio-economic status, nationality, personal
|
|
10
|
-
appearance, race, religion, or sexual identity and orientation.
|
|
11
|
-
|
|
12
|
-
## Our Standards
|
|
13
|
-
|
|
14
|
-
Examples of behavior that contributes to creating a positive environment
|
|
15
|
-
include:
|
|
16
|
-
|
|
17
|
-
* Using welcoming and inclusive language
|
|
18
|
-
* Being respectful of differing viewpoints and experiences
|
|
19
|
-
* Gracefully accepting constructive criticism
|
|
20
|
-
* Focusing on what is best for the community
|
|
21
|
-
* Showing empathy towards other community members
|
|
22
|
-
|
|
23
|
-
Examples of unacceptable behavior by participants include:
|
|
24
|
-
|
|
25
|
-
* The use of sexualized language or imagery and unwelcome sexual attention or
|
|
26
|
-
advances
|
|
27
|
-
* Trolling, insulting/derogatory comments, and personal or political attacks
|
|
28
|
-
* Public or private harassment
|
|
29
|
-
* Publishing others' private information, such as a physical or electronic
|
|
30
|
-
address, without explicit permission
|
|
31
|
-
* Other conduct which could reasonably be considered inappropriate in a
|
|
32
|
-
professional setting
|
|
33
|
-
|
|
34
|
-
## Our Responsibilities
|
|
35
|
-
|
|
36
|
-
Project maintainers are responsible for clarifying the standards of acceptable
|
|
37
|
-
behavior and are expected to take appropriate and fair corrective action in
|
|
38
|
-
response to any instances of unacceptable behavior.
|
|
39
|
-
|
|
40
|
-
Project maintainers have the right and responsibility to remove, edit, or
|
|
41
|
-
reject comments, commits, code, wiki edits, issues, and other contributions
|
|
42
|
-
that are not aligned to this Code of Conduct, or to ban temporarily or
|
|
43
|
-
permanently any contributor for other behaviors that they deem inappropriate,
|
|
44
|
-
threatening, offensive, or harmful.
|
|
45
|
-
|
|
46
|
-
## Scope
|
|
47
|
-
|
|
48
|
-
This Code of Conduct applies both within project spaces and in public spaces
|
|
49
|
-
when an individual is representing the project or its community. Examples of
|
|
50
|
-
representing a project or community include using an official project e-mail
|
|
51
|
-
address, posting via an official social media account, or acting as an appointed
|
|
52
|
-
representative at an online or offline event. Representation of a project may be
|
|
53
|
-
further defined and clarified by project maintainers.
|
|
54
|
-
|
|
55
|
-
## Enforcement
|
|
56
|
-
|
|
57
|
-
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
58
|
-
reported by contacting the project team at core@breezy-vcs.org. All
|
|
59
|
-
complaints will be reviewed and investigated and will result in a response that
|
|
60
|
-
is deemed necessary and appropriate to the circumstances. The project team is
|
|
61
|
-
obligated to maintain confidentiality with regard to the reporter of an incident.
|
|
62
|
-
Further details of specific enforcement policies may be posted separately.
|
|
63
|
-
|
|
64
|
-
Project maintainers who do not follow or enforce the Code of Conduct in good
|
|
65
|
-
faith may face temporary or permanent repercussions as determined by other
|
|
66
|
-
members of the project's leadership.
|
|
67
|
-
|
|
68
|
-
## Attribution
|
|
69
|
-
|
|
70
|
-
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
|
71
|
-
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
|
72
|
-
|
|
73
|
-
[homepage]: https://www.contributor-covenant.org
|
|
74
|
-
|
|
75
|
-
For answers to common questions about this code of conduct, see
|
|
76
|
-
https://www.contributor-covenant.org/faq
|
fastbencode-0.2/SECURITY.md
DELETED
fastbencode-0.2/disperse.conf
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
# bencode structured encoding
|
|
2
|
-
#
|
|
3
|
-
# Written by Petru Paler
|
|
4
|
-
#
|
|
5
|
-
# Permission is hereby granted, free of charge, to any person
|
|
6
|
-
# obtaining a copy of this software and associated documentation files
|
|
7
|
-
# (the "Software"), to deal in the Software without restriction,
|
|
8
|
-
# including without limitation the rights to use, copy, modify, merge,
|
|
9
|
-
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
10
|
-
# and to permit persons to whom the Software is furnished to do so,
|
|
11
|
-
# subject to the following conditions:
|
|
12
|
-
#
|
|
13
|
-
# The above copyright notice and this permission notice shall be
|
|
14
|
-
# included in all copies or substantial portions of the Software.
|
|
15
|
-
#
|
|
16
|
-
# Modifications copyright (C) 2008 Canonical Ltd
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
from typing import Dict, Type, Callable, List
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class BDecoder(object):
|
|
23
|
-
|
|
24
|
-
def __init__(self, yield_tuples=False):
|
|
25
|
-
"""Constructor.
|
|
26
|
-
|
|
27
|
-
:param yield_tuples: if true, decode "l" elements as tuples rather than
|
|
28
|
-
lists.
|
|
29
|
-
"""
|
|
30
|
-
self.yield_tuples = yield_tuples
|
|
31
|
-
decode_func = {}
|
|
32
|
-
decode_func[b'l'] = self.decode_list
|
|
33
|
-
decode_func[b'd'] = self.decode_dict
|
|
34
|
-
decode_func[b'i'] = self.decode_int
|
|
35
|
-
decode_func[b'0'] = self.decode_string
|
|
36
|
-
decode_func[b'1'] = self.decode_string
|
|
37
|
-
decode_func[b'2'] = self.decode_string
|
|
38
|
-
decode_func[b'3'] = self.decode_string
|
|
39
|
-
decode_func[b'4'] = self.decode_string
|
|
40
|
-
decode_func[b'5'] = self.decode_string
|
|
41
|
-
decode_func[b'6'] = self.decode_string
|
|
42
|
-
decode_func[b'7'] = self.decode_string
|
|
43
|
-
decode_func[b'8'] = self.decode_string
|
|
44
|
-
decode_func[b'9'] = self.decode_string
|
|
45
|
-
self.decode_func = decode_func
|
|
46
|
-
|
|
47
|
-
def decode_int(self, x, f):
|
|
48
|
-
f += 1
|
|
49
|
-
newf = x.index(b'e', f)
|
|
50
|
-
n = int(x[f:newf])
|
|
51
|
-
if x[f:f + 2] == b'-0':
|
|
52
|
-
raise ValueError
|
|
53
|
-
elif x[f:f + 1] == b'0' and newf != f + 1:
|
|
54
|
-
raise ValueError
|
|
55
|
-
return (n, newf + 1)
|
|
56
|
-
|
|
57
|
-
def decode_string(self, x, f):
|
|
58
|
-
colon = x.index(b':', f)
|
|
59
|
-
n = int(x[f:colon])
|
|
60
|
-
if x[f:f + 1] == b'0' and colon != f + 1:
|
|
61
|
-
raise ValueError
|
|
62
|
-
colon += 1
|
|
63
|
-
return (x[colon:colon + n], colon + n)
|
|
64
|
-
|
|
65
|
-
def decode_list(self, x, f):
|
|
66
|
-
r, f = [], f + 1
|
|
67
|
-
while x[f:f + 1] != b'e':
|
|
68
|
-
v, f = self.decode_func[x[f:f + 1]](x, f)
|
|
69
|
-
r.append(v)
|
|
70
|
-
if self.yield_tuples:
|
|
71
|
-
r = tuple(r)
|
|
72
|
-
return (r, f + 1)
|
|
73
|
-
|
|
74
|
-
def decode_dict(self, x, f):
|
|
75
|
-
r, f = {}, f + 1
|
|
76
|
-
lastkey = None
|
|
77
|
-
while x[f:f + 1] != b'e':
|
|
78
|
-
k, f = self.decode_string(x, f)
|
|
79
|
-
if lastkey is not None and lastkey >= k:
|
|
80
|
-
raise ValueError
|
|
81
|
-
lastkey = k
|
|
82
|
-
r[k], f = self.decode_func[x[f:f + 1]](x, f)
|
|
83
|
-
return (r, f + 1)
|
|
84
|
-
|
|
85
|
-
def bdecode(self, x):
|
|
86
|
-
if not isinstance(x, bytes):
|
|
87
|
-
raise TypeError
|
|
88
|
-
try:
|
|
89
|
-
r, l = self.decode_func[x[:1]](x, 0) # noqa: E741
|
|
90
|
-
except (IndexError, KeyError, OverflowError) as e:
|
|
91
|
-
raise ValueError(str(e))
|
|
92
|
-
if l != len(x): # noqa: E741
|
|
93
|
-
raise ValueError
|
|
94
|
-
return r
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
_decoder = BDecoder()
|
|
98
|
-
bdecode = _decoder.bdecode
|
|
99
|
-
|
|
100
|
-
_tuple_decoder = BDecoder(True)
|
|
101
|
-
bdecode_as_tuple = _tuple_decoder.bdecode
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
class Bencached(object):
|
|
105
|
-
__slots__ = ['bencoded']
|
|
106
|
-
|
|
107
|
-
def __init__(self, s):
|
|
108
|
-
self.bencoded = s
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def encode_bencached(x, r):
|
|
112
|
-
r.append(x.bencoded)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def encode_bool(x, r):
|
|
116
|
-
encode_int(int(x), r)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def encode_int(x, r):
|
|
120
|
-
r.extend((b'i', int_to_bytes(x), b'e'))
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def encode_string(x, r):
|
|
124
|
-
r.extend((int_to_bytes(len(x)), b':', x))
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def encode_list(x, r):
|
|
128
|
-
r.append(b'l')
|
|
129
|
-
for i in x:
|
|
130
|
-
encode_func[type(i)](i, r)
|
|
131
|
-
r.append(b'e')
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def encode_dict(x, r):
|
|
135
|
-
r.append(b'd')
|
|
136
|
-
ilist = sorted(x.items())
|
|
137
|
-
for k, v in ilist:
|
|
138
|
-
r.extend((int_to_bytes(len(k)), b':', k))
|
|
139
|
-
encode_func[type(v)](v, r)
|
|
140
|
-
r.append(b'e')
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
encode_func: Dict[Type, Callable[[object, List[bytes]], None]] = {}
|
|
144
|
-
encode_func[type(Bencached(0))] = encode_bencached
|
|
145
|
-
encode_func[int] = encode_int
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
def int_to_bytes(n):
|
|
149
|
-
return b'%d' % n
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
encode_func[bytes] = encode_string
|
|
153
|
-
encode_func[list] = encode_list
|
|
154
|
-
encode_func[tuple] = encode_list
|
|
155
|
-
encode_func[dict] = encode_dict
|
|
156
|
-
encode_func[bool] = encode_bool
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def bencode(x):
|
|
160
|
-
r = []
|
|
161
|
-
encode_func[type(x)](x, r)
|
|
162
|
-
return b''.join(r)
|
fastbencode-0.2/pyproject.toml
DELETED
fastbencode-0.2/setup.cfg
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
[metadata]
|
|
2
|
-
name = fastbencode
|
|
3
|
-
description = Implementation of bencode with optional fast C extensions
|
|
4
|
-
maintainer = Breezy Developers
|
|
5
|
-
maintainer_email = breezy-core@googlegroups.com
|
|
6
|
-
url = https://github.com/breezy-team/fastbencode
|
|
7
|
-
long_description = file:README.md
|
|
8
|
-
license = GPLv2 or later
|
|
9
|
-
version = attr:fastbencode.__version__
|
|
10
|
-
project_urls =
|
|
11
|
-
GitHub = https://github.com/breezy-team/fastbencode
|
|
12
|
-
classifiers =
|
|
13
|
-
Programming Language :: Python :: 3.7
|
|
14
|
-
Programming Language :: Python :: 3.8
|
|
15
|
-
Programming Language :: Python :: 3.9
|
|
16
|
-
Programming Language :: Python :: 3.10
|
|
17
|
-
Programming Language :: Python :: 3.11
|
|
18
|
-
Programming Language :: Python :: Implementation :: CPython
|
|
19
|
-
Programming Language :: Python :: Implementation :: PyPy
|
|
20
|
-
Operating System :: POSIX
|
|
21
|
-
Operating System :: Microsoft :: Windows
|
|
22
|
-
|
|
23
|
-
[options]
|
|
24
|
-
python_requires = >=3.7
|
|
25
|
-
packages = fastbencode
|
|
26
|
-
setup_requires =
|
|
27
|
-
cython>=0.29
|
|
28
|
-
|
|
29
|
-
[options.extras_require]
|
|
30
|
-
cext = cython>=0.29
|
|
31
|
-
|
|
32
|
-
[egg_info]
|
|
33
|
-
tag_build =
|
|
34
|
-
tag_date = 0
|
|
35
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|