From 58364dfb3f46d271ce6e51535db421037a331f23 Mon Sep 17 00:00:00 2001 From: "Bora M. Alper" Date: Mon, 5 Jun 2017 22:26:08 +0300 Subject: [PATCH] replaced better_bencode with bencoder.pyx fixes #72 --- magneticod/magneticod/bencode.py | 22 +- magneticod/magneticod/bencoder/LICENSE | 32 ++ magneticod/magneticod/bencoder/README.rst | 10 + magneticod/magneticod/bencoder/__init__.py | 6 + magneticod/magneticod/bencoder/_fast.c | 545 +++++++++++++++++++++ magneticod/magneticod/bencoder/_pure.py | 152 ++++++ magneticod/setup.py | 11 +- 7 files changed, 759 insertions(+), 19 deletions(-) create mode 100644 magneticod/magneticod/bencoder/LICENSE create mode 100644 magneticod/magneticod/bencoder/README.rst create mode 100644 magneticod/magneticod/bencoder/__init__.py create mode 100644 magneticod/magneticod/bencoder/_fast.c create mode 100644 magneticod/magneticod/bencoder/_pure.py diff --git a/magneticod/magneticod/bencode.py b/magneticod/magneticod/bencode.py index e65e209..126af0c 100755 --- a/magneticod/magneticod/bencode.py +++ b/magneticod/magneticod/bencode.py @@ -13,26 +13,14 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . - """ -bencode - Wrapper around bencoder.pyx library. - -bencoder.pyx - Copyright (c) 2016, whtsky - All rights reserved. - https://github.com/whtsky/bencoder.pyx - Warning: Encoders do NOT check for circular objects! (and will NEVER check due to speed concerns). - -TODO: - Support bytearrays as well! (Currently, only bytes). """ + import typing -import bencoder - +from magneticod.bencoder import dumps, loads, loads2 Types = typing.Union[int, bytes, list, "KRPCDict"] KRPCDict = typing.Dict[bytes, Types] @@ -40,14 +28,14 @@ KRPCDict = typing.Dict[bytes, Types] def dumps(obj) -> bytes: try: - return bencoder.bencode(obj) + return dumps(obj) except: raise BencodeEncodingError() def loads(bytes_object: bytes) -> Types: try: - return bencoder.decode_func[bytes_object[0]](bytes_object, 0)[0] + return loads(bytes_object) except Exception as exc: raise BencodeDecodingError(exc) @@ -61,7 +49,7 @@ def loads2(bytes_object: bytes) -> typing.Tuple[Types, int]: print(">>>", dump[i:]) # OUTPUT: >>> b'OH YEAH' """ try: - return bencoder.decode_func[bytes_object[0]](bytes_object, 0) + return loads2(bytes_object) except Exception as exc: raise BencodeDecodingError(exc) diff --git a/magneticod/magneticod/bencoder/LICENSE b/magneticod/magneticod/bencoder/LICENSE new file mode 100644 index 0000000..70a2b4e --- /dev/null +++ b/magneticod/magneticod/bencoder/LICENSE @@ -0,0 +1,32 @@ +Copyright (c) 2015 by Krzysztof Kosyl + +Some rights reserved. + +Redistribution and use in source and binary forms of the software as well +as documentation, with or without modification, are permitted provided +that the following conditions are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + +* The names of the contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/magneticod/magneticod/bencoder/README.rst b/magneticod/magneticod/bencoder/README.rst new file mode 100644 index 0000000..52f3fc3 --- /dev/null +++ b/magneticod/magneticod/bencoder/README.rst @@ -0,0 +1,10 @@ +This sub-module is a fork of Krzysztof Kosyl's [better-bencode](https://github.com/kosqx/better-bencode/) for the +specific needs of **magetico**. + +The original repository is forked at commit `46bdc09f1b3003b39aa4263e0a052883a5209c2a`. + +Key Differenes from *better-bencode*: + +* Python 2 support is removed. +* Removed `dump` and `load` functions, as they are not used and most likely will not be maintained. It's better not to + have them than to have two different set of functions with inconsistent, confusing behaviour. diff --git a/magneticod/magneticod/bencoder/__init__.py b/magneticod/magneticod/bencoder/__init__.py new file mode 100644 index 0000000..6a753fd --- /dev/null +++ b/magneticod/magneticod/bencoder/__init__.py @@ -0,0 +1,6 @@ +try: + from magneticod.bencoder._fast import dumps, loads, loads2 + from magneticod.bencoder._fast import BencodeValueError, BencodeTypeError +except ImportError: + from magneticod.bencoder._pure import dumps, loads, loads2 + from magneticod.bencoder._pure import BencodeValueError, BencodeTypeError diff --git a/magneticod/magneticod/bencoder/_fast.c b/magneticod/magneticod/bencoder/_fast.c new file mode 100644 index 0000000..d2aeb01 --- /dev/null +++ b/magneticod/magneticod/bencoder/_fast.c @@ -0,0 +1,545 @@ +#include + + +#define PY_BUILD_VALUE_BYTES "y#" +#define PyString_FromStringAndSize PyBytes_FromStringAndSize +#define PyString_AsStringAndSize PyBytes_AsStringAndSize +#define PyString_Size PyBytes_Size +#define PyInt_CheckExact(obj) 0 + + +struct benc_state { + unsigned int cast : 1; + + int size; + int offset; + char* buffer; + PyObject* file; + + PyObject** references_stack; + int references_size; + int references_top; +}; + + +PyObject* BencodeValueError; +PyObject* BencodeTypeError; + + +static void benc_state_init(struct benc_state* bs) { + bs->size = 256; + bs->offset = 0; + bs->buffer = malloc(bs->size); + bs->file = NULL; + + bs->references_size = 8; + bs->references_top = 0; + bs->references_stack = malloc(sizeof(PyObject*) * bs->references_size); +} + + +static void benc_state_free(struct benc_state* bs) { + if (bs->buffer != NULL) { + free(bs->buffer); + } + if (bs->references_stack != NULL) { + free(bs->references_stack); + } +} + + +static void benc_state_flush(struct benc_state* bs) { + if (bs->offset > 0) { + PyObject_CallMethod(bs->file, "write", PY_BUILD_VALUE_BYTES, bs->buffer, bs->offset); + bs->offset = 0; + } +} + + +static void benc_state_write_char(struct benc_state* bs, char c) { + if (bs->file == NULL) { + if ((bs->offset + 1) >= bs->size) { + bs->buffer = realloc(bs->buffer, bs->size * 2); + } + bs->buffer[bs->offset++] = c; + } else { + if ((bs->offset + 1) >= bs->size) { + PyObject_CallMethod(bs->file, "write", PY_BUILD_VALUE_BYTES, bs->buffer, bs->offset); + bs->offset = 0; + } + bs->buffer[bs->offset++] = c; + } +} + + +static void benc_state_write_buffer(struct benc_state* bs, char* buff, int size) { + if (bs->file == NULL) { + int new_size; + for (new_size = bs->size; new_size <= (bs->offset + size); new_size *= 2); + if (new_size > bs->size) { + bs->buffer = realloc(bs->buffer, new_size); + bs->size = new_size; + } + memcpy(bs->buffer + bs->offset, buff, size); + bs->offset += size; + } else { + if (bs->offset + size >= bs->size) { + PyObject_CallMethod(bs->file, "write", PY_BUILD_VALUE_BYTES, bs->buffer, bs->offset); + bs->offset = 0; + } + if (size >= bs->size) { + PyObject_CallMethod(bs->file, "write", PY_BUILD_VALUE_BYTES, buff, size); + } else { + memcpy(bs->buffer + bs->offset, buff, size); + bs->offset += size; + } + } +} + + +static void benc_state_write_format(struct benc_state* bs, const int limit, const void *format, ...) { + char buffer[limit + 1]; // moze by malloca()? + + va_list ap; + va_start(ap, format); + int size = vsnprintf(buffer, limit, format, ap); + va_end(ap); + + return benc_state_write_buffer(bs, buffer, (size < limit) ? size : (limit - 1)); +} + + +static int benc_state_read_char(struct benc_state* bs) { + if (bs->file == NULL) { + if (bs->offset < bs->size) { + return bs->buffer[bs->offset++]; + } else { + return -1; + } + } else { + char *buffer; + int result; + Py_ssize_t length; + PyObject *data = PyObject_CallMethod(bs->file, "read", "i", 1); + if (-1 == PyString_AsStringAndSize(data, &buffer, &length)) { + return -1; + } + if (length == 1) { + result = buffer[0]; + } else { + result = -1; + } + Py_DECREF(data); + return result; + } +} + + +static PyObject *benc_state_read_pystring(struct benc_state* bs, int size) { + if (bs->file == NULL) { + if (bs->offset + size <= bs->size) { + PyObject *result = PyString_FromStringAndSize(bs->buffer + bs->offset, size); + bs->offset += size; + return result; + } else { + PyErr_Format( + BencodeValueError, + "unexpected end of data" + ); + return NULL; + } + } else { + PyObject *result = PyObject_CallMethod(bs->file, "read", "i", size); + if (PyString_Size(result) == size) { + return result; + } else { + Py_DECREF(result); + PyErr_Format( + BencodeValueError, + "unexpected end of data" + ); + return NULL; + } + } +} + +static void benc_state_references_push(struct benc_state* bs, PyObject *obj) { + if ((bs->references_top + 1) == bs->references_size) { + bs->references_size *= 2; + bs->references_stack = realloc( + bs->references_stack, + sizeof(PyObject*) * bs->references_size + ); + } + bs->references_stack[bs->references_top++] = obj; +} + +static void benc_state_references_pop(struct benc_state* bs) { + bs->references_top--; +} + +static int benc_state_references_contains(struct benc_state* bs, PyObject *obj) { + int i; + for (i = 0; i < bs->references_top; i++) { + if (bs->references_stack[i] == obj) { + return 1; + } + } + return 0; +} + + +static int do_dump(struct benc_state *bs, PyObject* obj); + +static int do_dump(struct benc_state *bs, PyObject* obj) { + int i = 0, n = 0; + + if (benc_state_references_contains(bs, obj)) { + PyErr_Format( + BencodeValueError, + "circular reference detected" + ); + return 0; + } + + if (PyBytes_CheckExact(obj)) { + char *buff = PyBytes_AS_STRING(obj); + int size = PyBytes_GET_SIZE(obj); + + benc_state_write_format(bs, 12, "%d:", size); + benc_state_write_buffer(bs, buff, size); + } else if (PyInt_CheckExact(obj) || PyLong_CheckExact(obj)) { + long x = PyLong_AsLong(obj); + benc_state_write_format(bs, 23, "i%lde", x); + } else if (bs->cast && PyBool_Check(obj)) { + long x = PyLong_AsLong(obj); + benc_state_write_format(bs, 4, "i%lde", x); + } else if (PyList_CheckExact(obj) || (bs->cast && PyList_Check(obj))) { + n = PyList_GET_SIZE(obj); + benc_state_references_push(bs, obj); + benc_state_write_char(bs, 'l'); + for (i = 0; i < n; i++) { + do_dump(bs, PyList_GET_ITEM(obj, i)); + } + benc_state_write_char(bs, 'e'); + benc_state_references_pop(bs); + } else if (bs->cast && PyTuple_Check(obj)) { + n = PyTuple_GET_SIZE(obj); + benc_state_references_push(bs, obj); + benc_state_write_char(bs, 'l'); + for (i = 0; i < n; i++) { + do_dump(bs, PyTuple_GET_ITEM(obj, i)); + } + benc_state_write_char(bs, 'e'); + benc_state_references_pop(bs); + } else if (PyDict_CheckExact(obj)) { + Py_ssize_t index = 0; + PyObject *keys, *key, *value; + keys = PyDict_Keys(obj); + PyList_Sort(keys); + + benc_state_references_push(bs, obj); + benc_state_write_char(bs, 'd'); + for (index = 0; index < PyList_Size(keys); index++) { + key = PyList_GetItem(keys, index); + value = PyDict_GetItem(obj, key); + do_dump(bs, key); + do_dump(bs, value); + } + benc_state_write_char(bs, 'e'); + benc_state_references_pop(bs); + + Py_DECREF(keys); + } else { + PyErr_Format( + BencodeTypeError, + "type %s is not Bencode serializable", + Py_TYPE(obj)->tp_name + ); + } + return 0; +} + + +static PyObject* dumps(PyObject* self, PyObject* args, PyObject* kwargs) { + static char *kwlist[] = {"obj", "cast", NULL}; + + PyObject* obj; + PyObject* result; + int cast = 0; + + struct benc_state bs; + benc_state_init(&bs); + + if (!PyArg_ParseTupleAndKeywords( + args, kwargs, "O|i", kwlist, + &obj, &cast + )) + { + return NULL; + } + + bs.cast = !!cast; + + do_dump(&bs, obj); + + if (PyErr_Occurred()) { + benc_state_free(&bs); + return NULL; + } else { + result = Py_BuildValue(PY_BUILD_VALUE_BYTES, bs.buffer, bs.offset); + benc_state_free(&bs); + return result; + } +} + + +static PyObject *do_load(struct benc_state *bs) { + PyObject *retval = NULL; + + int first = benc_state_read_char(bs); + + switch (first) { + case 'i': { + int sign = 1; + int read_cnt = 0; + long long value = 0; + int current = benc_state_read_char(bs); + if (current == '-') { + sign = -1; + current = benc_state_read_char(bs); + } + while (('0' <= current) && (current <= '9')) { + value = value * 10 + (current - '0'); + current = benc_state_read_char(bs); + read_cnt++; + } + + if ('e' == current) { + if (read_cnt > 0) { + value *= sign; + retval = PyLong_FromLongLong(value); + } else { + PyErr_Format( + BencodeValueError, + "unexpected end of data" + ); + retval = NULL; + } + } else if (-1 == current) { + PyErr_Format( + BencodeValueError, + "unexpected end of data" + ); + retval = NULL; + } else { + PyErr_Format( + BencodeValueError, + "unexpected byte 0x%.2x", + current + ); + retval = NULL; + } + + } break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + int size = first - '0'; + char current = benc_state_read_char(bs); + while (('0' <= current) && (current <= '9')) { + size = size * 10 + (current - '0'); + current = benc_state_read_char(bs); + } + if (':' == current) { + retval = benc_state_read_pystring(bs, size); + } else if (-1 == current) { + PyErr_Format( + BencodeValueError, + "unexpected end of data" + ); + retval = NULL; + } else { + PyErr_Format( + BencodeValueError, + "unexpected byte 0x%.2x", + current + ); + retval = NULL; + } + + } break; + case 'e': + Py_INCREF(PyExc_StopIteration); + retval = PyExc_StopIteration; + break; + case 'l': { + PyObject *v = PyList_New(0); + PyObject *item; + + while (1) { + item = do_load(bs); + + if (item == PyExc_StopIteration) { + Py_DECREF(PyExc_StopIteration); + break; + } + + if (item == NULL) { + if (!PyErr_Occurred()) { + PyErr_SetString( + BencodeTypeError, + "unexpected error in list" + ); + } + Py_DECREF(v); + v = NULL; + break; + } + + PyList_Append(v, item); + Py_DECREF(item); + } + + retval = v; + } break; + case 'd': { + PyObject *v = PyDict_New(); + + while (1) { + PyObject *key, *val; + key = val = NULL; + key = do_load(bs); + + if (key == PyExc_StopIteration) { + Py_DECREF(PyExc_StopIteration); + break; + } + + if (key == NULL) { + if (!PyErr_Occurred()) { + PyErr_SetString(BencodeTypeError, "unexpected error in dict"); + } + break; + } + + val = do_load(bs); + if (val != NULL) { + PyDict_SetItem(v, key, val); + } else { + if (!PyErr_Occurred()) { + PyErr_SetString(BencodeTypeError, "unexpected error in dict"); + } + break; + } + Py_DECREF(key); + Py_XDECREF(val); + } + if (PyErr_Occurred()) { + Py_DECREF(v); + v = NULL; + } + retval = v; + } break; + case -1: { + PyErr_Format( + BencodeValueError, + "unexpected end of data" + ); + retval = NULL; + } break; + default: + PyErr_Format( + BencodeValueError, + "unexpected byte 0x%.2x", + first + ); + retval = NULL; + break; + } + return retval; +} + + +static PyObject* loads(PyObject* self, PyObject* args) { + struct benc_state bs; + memset(&bs, 0, sizeof(struct benc_state)); + + if (!PyArg_ParseTuple(args, PY_BUILD_VALUE_BYTES, &(bs.buffer), &(bs.size))) + return NULL; + + PyObject* obj = do_load(&bs); + + return obj; +} + + +static PyObject* loads2(PyObject* self, PyObject* args) { + /* TODO: + * + * PyLong_FromLong and PyTuple_Pack might return NULL. How to handle these errors? + */ + struct benc_state bs; + memset(&bs, 0, sizeof(struct benc_state)); + + if (!PyArg_ParseTuple(args, PY_BUILD_VALUE_BYTES, &(bs.buffer), &(bs.size))) + return NULL; + + PyObject* obj = do_load(&bs); + PyObject* offset = PyLong_FromLong((long) bs.offset); + + return PyTuple_Pack(2, obj, offset); +} + + +static PyObject *add_errors(PyObject *module) { + BencodeValueError = PyErr_NewException( + "bencoder._fast.BencodeValueError", PyExc_ValueError, NULL + ); + Py_INCREF(BencodeValueError); + PyModule_AddObject(module, "BencodeValueError", BencodeValueError); + + BencodeTypeError = PyErr_NewException( + "bencoder._fast.BencodeTypeError", PyExc_TypeError, NULL + ); + Py_INCREF(BencodeTypeError); + PyModule_AddObject(module, "BencodeTypeError", BencodeTypeError); + + return module; +} + + +static PyMethodDef bencoder_fastMethods[] = { + {"loads", loads, METH_VARARGS, "Deserialize ``s`` to a Python object."}, + {"loads2", loads2, METH_VARARGS, "Deserialize ``s`` to a Python object and return end index."}, + {"dumps", dumps, METH_VARARGS|METH_KEYWORDS, "Serialize ``obj`` to a Bencode formatted ``str``."}, + {NULL, NULL, 0, NULL} +}; + + +static struct PyModuleDef bencoder_fast_module = { + PyModuleDef_HEAD_INIT, + "bencoder._fast", + NULL, + -1, + bencoder_fastMethods, + NULL, + NULL, + NULL, + NULL +}; + +PyMODINIT_FUNC +PyInit__fast(void) { + PyObject *module = PyModule_Create(&bencoder_fast_module); + return add_errors(module); + +} diff --git a/magneticod/magneticod/bencoder/_pure.py b/magneticod/magneticod/bencoder/_pure.py new file mode 100644 index 0000000..9ee204d --- /dev/null +++ b/magneticod/magneticod/bencoder/_pure.py @@ -0,0 +1,152 @@ +""" +Pure Python implementation of Bencode serialization format. +To be used when fast C Extension cannot be compiled. +""" +from io import BytesIO as StringIO + + +INTEGER_TYPES = (int,) +BINARY_TYPES = (bytes, ) +int_to_binary = lambda val: bytes(str(val), 'ascii') + + +class BencodeValueError(ValueError): + pass + + +class BencodeTypeError(TypeError): + pass + + +def _dump_implementation(obj, write, path, cast): + """ dump()/dumps() implementation """ + + t = type(obj) + + if id(obj) in path: + raise BencodeValueError('circular reference detected') + + if t in INTEGER_TYPES: + write(b'i') + write(int_to_binary(obj)) + write(b'e') + elif t in BINARY_TYPES: + write(int_to_binary(len(obj))) + write(b':') + write(obj) + elif t is list or (cast and issubclass(t, (list, tuple))): + write(b'l') + for item in obj: + _dump_implementation(item, write, path + [id(obj)], cast) + write(b'e') + elif t is dict: + write(b'd') + + data = sorted(obj.items()) + for key, val in data: + _dump_implementation(key, write, path + [id(obj)], cast) + _dump_implementation(val, write, path + [id(obj)], cast) + write(b'e') + elif cast and t is bool: + write(b'i') + write(int_to_binary(int(obj))) + write(b'e') + else: + raise BencodeTypeError( + 'type %s is not Bencode serializable' % type(obj).__name__ + ) + + +def dumps(obj, cast=False): + """Serialize ``obj`` to a Bencode formatted ``str``.""" + + fp = [] + _dump_implementation(obj, fp.append, [], cast) + return b''.join(fp) + + +def _read_until(delimiter, read): + """ Read char by char until ``delimiter`` occurs. """ + + result = b'' + ch = read(1) + if not ch: + raise BencodeValueError('unexpected end of data') + while ch != delimiter: + result += ch + ch = read(1) + if not ch: + raise BencodeValueError('unexpected end of data') + return result + + +def _load_implementation(read): + """ load()/loads() implementation """ + + first = read(1) + + if first == b'e': + return StopIteration + elif first == b'i': + value = b'' + ch = read(1) + while (b'0' <= ch <= b'9') or (ch == b'-'): + value += ch + ch = read(1) + if ch == b'' or (ch == b'e' and value in (b'', b'-')): + raise BencodeValueError('unexpected end of data') + if ch != b'e': + raise BencodeValueError('unexpected byte 0x%.2x' % ord(ch)) + return int(value) + elif b'0' <= first <= b'9': + size = 0 + while b'0' <= first <= b'9': + size = size * 10 + (ord(first) - ord('0')) + first = read(1) + if first == b'': + raise BencodeValueError('unexpected end of data') + if first != b':': + raise BencodeValueError('unexpected byte 0x%.2x' % ord(first)) + data = read(size) + if len(data) != size: + raise BencodeValueError('unexpected end of data') + return data + elif first == b'l': + result = [] + while True: + val = _load_implementation(read) + if val is StopIteration: + return result + result.append(val) + elif first == b'd': + result = {} + while True: + this = read(1) + if this == b'e': + return result + elif this == b'': + raise BencodeValueError('unexpected end of data') + elif not this.isdigit(): + raise BencodeValueError('unexpected byte 0x%.2x' % ord(this)) + size = int(this + _read_until(b':', read)) + key = read(size) + val = _load_implementation(read) + result[key] = val + elif first == b'': + raise BencodeValueError('unexpected end of data') + else: + raise BencodeValueError('unexpected byte 0x%.2x' % ord(first)) + + +def loads(data): + """Deserialize ``s`` to a Python object.""" + + fp = StringIO(data) + return _load_implementation(fp.read) + + +def loads2(data): + """Deserialize ``s`` to a Python object.""" + + fp = StringIO(data) + return _load_implementation(fp.read), fp.tell() diff --git a/magneticod/setup.py b/magneticod/setup.py index b71ad1a..4c90787 100644 --- a/magneticod/setup.py +++ b/magneticod/setup.py @@ -1,4 +1,4 @@ -from setuptools import setup +from setuptools import find_packages, setup, Extension def read_file(path): @@ -15,7 +15,7 @@ setup( author="Mert Bora ALPER", author_email="bora@boramalper.org", license="GNU Affero General Public License v3 or later (AGPLv3+)", - packages=["magneticod"], + packages=find_packages(), zip_safe=False, entry_points={ "console_scripts": ["magneticod=magneticod.__main__:main"] @@ -36,5 +36,12 @@ setup( "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: Implementation :: CPython", + ], + + ext_modules=[ + Extension( + "magneticod.bencoder._fast", + sources=["magneticod/bencoder/_fast.c"], + ), ] )