From b5627aa0184111eee53c86ad631ae54d92c5cb28 Mon Sep 17 00:00:00 2001 From: Rudolf Kolbe Date: Sat, 5 Oct 2024 21:44:04 +0200 Subject: [PATCH 1/8] _brotli.c - add Py_LIMITED_API compatible code --- python/_brotli.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/python/_brotli.c b/python/_brotli.c index c6a0da03d..148fa39c8 100644 --- a/python/_brotli.c +++ b/python/_brotli.c @@ -9,6 +9,26 @@ #if PY_MAJOR_VERSION >= 3 #define PyInt_Check PyLong_Check #define PyInt_AsLong PyLong_AsLong + +#ifdef Py_LIMITED_API +// macro functions are version specific and therefore not compatible with the stable api +// therefore we map them here to the functions +#define PyList_GET_ITEM PyList_GetItem +#define PyList_SET_ITEM PyList_SetItem +#define PyBytes_AS_STRING PyBytes_AsString +// Py_buffer is only available in stable api 3.10+ +// as we want to support 3.6+ we need to define a dummy struct +// to not break compatibility with Python 2 +// len is of type int, as Py_ssize_t clean is 3.7+ +typedef struct { + void *buf; + int len; +} Py_buffer; +static void PyBuffer_Release(Py_buffer *view) { + view->buf = NULL; + view->len = -1; +} +#endif #else #define Py_ARRAY_LENGTH(array) (sizeof(array) / sizeof((array)[0])) #endif @@ -356,12 +376,17 @@ typedef struct { static void brotli_Compressor_dealloc(brotli_Compressor* self) { BrotliEncoderDestroyInstance(self->enc); #if PY_MAJOR_VERSION >= 3 + #ifdef Py_LIMITED_API + PyObject_Del((PyObject*)self); + #else Py_TYPE(self)->tp_free((PyObject*)self); + #endif #else self->ob_type->tp_free((PyObject*)self); #endif } +#ifndef Py_LIMITED_API static PyObject* brotli_Compressor_new(PyTypeObject *type, PyObject *args, PyObject *keywds) { brotli_Compressor *self; self = (brotli_Compressor *)type->tp_alloc(type, 0); @@ -372,8 +397,12 @@ static PyObject* brotli_Compressor_new(PyTypeObject *type, PyObject *args, PyObj return (PyObject *)self; } +#endif static int brotli_Compressor_init(brotli_Compressor *self, PyObject *args, PyObject *keywds) { + #ifdef Py_LIMITED_API + self->enc = BrotliEncoderCreateInstance(0, 0, 0); + #endif BrotliEncoderMode mode = (BrotliEncoderMode) -1; int quality = -1; int lgwin = -1; @@ -431,7 +460,11 @@ static PyObject* brotli_Compressor_process(brotli_Compressor *self, PyObject *ar int ok; #if PY_MAJOR_VERSION >= 3 +#ifdef Py_LIMITED_API + ok = PyArg_ParseTuple(args, "y*:process", &input.buf, &input.len); +#else ok = PyArg_ParseTuple(args, "y*:process", &input); +#endif #else ok = PyArg_ParseTuple(args, "s*:process", &input); #endif @@ -546,6 +579,25 @@ static PyMethodDef brotli_Compressor_methods[] = { {NULL} /* Sentinel */ }; +#ifdef Py_LIMITED_API +static PyType_Slot brotli_Compressor_slots[] = { + {Py_tp_dealloc, (void *)brotli_Compressor_dealloc}, + {Py_tp_doc, (void *)brotli_Compressor_doc}, + {Py_tp_members, brotli_Compressor_members}, + {Py_tp_methods, brotli_Compressor_methods}, + {Py_tp_new, (void *)PyType_GenericNew}, + {Py_tp_init, (void *)brotli_Compressor_init}, + {0, NULL} +}; + +static PyType_Spec brotli_Compressor_Spec = { + "brotli.Compressor", // const char* name; + sizeof(brotli_Compressor), // int basicsize; + 0, // int itemsize; + Py_TPFLAGS_DEFAULT, // unsigned int flags; + brotli_Compressor_slots, // PyType_Slot *slots; +}; +#else static PyTypeObject brotli_CompressorType = { #if PY_MAJOR_VERSION >= 3 PyVarObject_HEAD_INIT(NULL, 0) @@ -591,6 +643,7 @@ static PyTypeObject brotli_CompressorType = { 0, /* tp_alloc */ brotli_Compressor_new, /* tp_new */ }; +#endif static PyObject* decompress_stream(BrotliDecoderState* dec, uint8_t* input, size_t input_length) { @@ -660,12 +713,17 @@ typedef struct { static void brotli_Decompressor_dealloc(brotli_Decompressor* self) { BrotliDecoderDestroyInstance(self->dec); #if PY_MAJOR_VERSION >= 3 + #ifdef Py_LIMITED_API + PyObject_Del((PyObject*)self); + #else Py_TYPE(self)->tp_free((PyObject*)self); + #endif #else self->ob_type->tp_free((PyObject*)self); #endif } +#ifndef Py_LIMITED_API static PyObject* brotli_Decompressor_new(PyTypeObject *type, PyObject *args, PyObject *keywds) { brotli_Decompressor *self; self = (brotli_Decompressor *)type->tp_alloc(type, 0); @@ -676,8 +734,12 @@ static PyObject* brotli_Decompressor_new(PyTypeObject *type, PyObject *args, PyO return (PyObject *)self; } +#endif static int brotli_Decompressor_init(brotli_Decompressor *self, PyObject *args, PyObject *keywds) { + #ifdef Py_LIMITED_API + self->dec = BrotliDecoderCreateInstance(0, 0, 0); + #endif int ok; static const char *kwlist[] = {NULL}; @@ -718,7 +780,11 @@ static PyObject* brotli_Decompressor_process(brotli_Decompressor *self, PyObject int ok; #if PY_MAJOR_VERSION >= 3 +#ifdef Py_LIMITED_API + ok = PyArg_ParseTuple(args, "y*:process", &input.buf, &input.len); +#else ok = PyArg_ParseTuple(args, "y*:process", &input); +#endif #else ok = PyArg_ParseTuple(args, "s*:process", &input); #endif @@ -783,6 +849,25 @@ static PyMethodDef brotli_Decompressor_methods[] = { {NULL} /* Sentinel */ }; +#ifdef Py_LIMITED_API +static PyType_Slot brotli_Decompressor_slots[] = { + {Py_tp_dealloc, (void *)brotli_Decompressor_dealloc}, + {Py_tp_doc, (void *)brotli_Decompressor_doc}, + {Py_tp_members, brotli_Decompressor_members}, + {Py_tp_methods, brotli_Decompressor_methods}, + {Py_tp_new, (void *)PyType_GenericNew}, + {Py_tp_init, (void *)brotli_Decompressor_init}, + {0, NULL} +}; + +static PyType_Spec brotli_Decompressor_Spec = { + "brotli.Decompressor", // const char* name; + sizeof(brotli_Decompressor), // int basicsize; + 0, // int itemsize; + Py_TPFLAGS_DEFAULT, // unsigned int flags; + brotli_Decompressor_slots, // PyType_Slot *slots; +}; +#else static PyTypeObject brotli_DecompressorType = { #if PY_MAJOR_VERSION >= 3 PyVarObject_HEAD_INIT(NULL, 0) @@ -828,6 +913,7 @@ static PyTypeObject brotli_DecompressorType = { 0, /* tp_alloc */ brotli_Decompressor_new, /* tp_new */ }; +#endif PyDoc_STRVAR(brotli_decompress__doc__, "Decompress a compressed byte string.\n" @@ -861,8 +947,13 @@ static PyObject* brotli_decompress(PyObject *self, PyObject *args, PyObject *key int ok; #if PY_MAJOR_VERSION >= 3 +#ifdef Py_LIMITED_API + ok = PyArg_ParseTupleAndKeywords(args, keywds, "y#|:decompress", + (char**) kwlist, &input.buf, &input.len); +#else ok = PyArg_ParseTupleAndKeywords(args, keywds, "y*|:decompress", (char**) kwlist, &input); +#endif #else ok = PyArg_ParseTupleAndKeywords(args, keywds, "s*|:decompress", (char**) kwlist, &input); @@ -959,12 +1050,18 @@ PyMODINIT_FUNC INIT_BROTLI(void) { PyModule_AddObject(m, "error", BrotliError); } + #ifdef Py_LIMITED_API + PyTypeObject* brotli_CompressorType = (PyTypeObject *) PyType_FromSpec(&brotli_Compressor_Spec); + #endif if (PyType_Ready(&brotli_CompressorType) < 0) { RETURN_NULL; } Py_INCREF(&brotli_CompressorType); PyModule_AddObject(m, "Compressor", (PyObject *)&brotli_CompressorType); + #ifdef Py_LIMITED_API + PyTypeObject* brotli_DecompressorType = (PyTypeObject *) PyType_FromSpec(&brotli_Decompressor_Spec); + #endif if (PyType_Ready(&brotli_DecompressorType) < 0) { RETURN_NULL; } From 5d46bf74d9385d0e89da1c22d704087f0f67eac6 Mon Sep 17 00:00:00 2001 From: Rudolf Kolbe Date: Sat, 5 Oct 2024 22:14:25 +0200 Subject: [PATCH 2/8] brotli.c - fix pytypeobject* handling --- python/_brotli.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/python/_brotli.c b/python/_brotli.c index 148fa39c8..f801db309 100644 --- a/python/_brotli.c +++ b/python/_brotli.c @@ -1051,22 +1051,26 @@ PyMODINIT_FUNC INIT_BROTLI(void) { } #ifdef Py_LIMITED_API - PyTypeObject* brotli_CompressorType = (PyTypeObject *) PyType_FromSpec(&brotli_Compressor_Spec); + PyTypeObject* brotli_CompressorType_ref = (PyTypeObject *) PyType_FromSpec(&brotli_Compressor_Spec); + #else + PyTypeObject* brotli_CompressorType_ref = &brotli_CompressorType; #endif - if (PyType_Ready(&brotli_CompressorType) < 0) { + if (PyType_Ready(brotli_CompressorType_ref) < 0) { RETURN_NULL; } - Py_INCREF(&brotli_CompressorType); - PyModule_AddObject(m, "Compressor", (PyObject *)&brotli_CompressorType); + Py_INCREF(brotli_CompressorType_ref); + PyModule_AddObject(m, "Compressor", (PyObject *)brotli_CompressorType_ref); #ifdef Py_LIMITED_API - PyTypeObject* brotli_DecompressorType = (PyTypeObject *) PyType_FromSpec(&brotli_Decompressor_Spec); + PyTypeObject* brotli_DecompressorType_ref = (PyTypeObject *) PyType_FromSpec(&brotli_Decompressor_Spec); + #else + PyTypeObject* brotli_DecompressorType_ref = &brotli_DecompressorType; #endif - if (PyType_Ready(&brotli_DecompressorType) < 0) { + if (PyType_Ready(brotli_DecompressorType_ref) < 0) { RETURN_NULL; } - Py_INCREF(&brotli_DecompressorType); - PyModule_AddObject(m, "Decompressor", (PyObject *)&brotli_DecompressorType); + Py_INCREF(brotli_DecompressorType_ref); + PyModule_AddObject(m, "Decompressor", (PyObject *)brotli_DecompressorType_ref); PyModule_AddIntConstant(m, "MODE_GENERIC", (int) BROTLI_MODE_GENERIC); PyModule_AddIntConstant(m, "MODE_TEXT", (int) BROTLI_MODE_TEXT); From f594f8ff586359b5b9efda50b1727eeb95acff40 Mon Sep 17 00:00:00 2001 From: Rudolf Kolbe Date: Sat, 5 Oct 2024 22:20:06 +0200 Subject: [PATCH 3/8] setup.py - simplify and add abi3 build for cibuildwheel env --- setup.py | 112 ++++++++++++++++++++++--------------------------------- 1 file changed, 45 insertions(+), 67 deletions(-) diff --git a/setup.py b/setup.py index 6cd325d8c..8bb0779ec 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,7 @@ import platform import re import unittest +import sys try: from setuptools import Extension @@ -15,11 +16,13 @@ from distutils.core import Extension from distutils.core import setup from distutils.command.build_ext import build_ext -from distutils import errors from distutils import dep_util from distutils import log +IS_PYTHON3 = sys.version_info[0] == 3 +CIBUILDWHEEL = os.environ.get('CIBUILDWHEEL', '0') == '1' + CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) @@ -27,7 +30,7 @@ def read_define(path, macro): """ Return macro value from the given file. """ with open(path, 'r') as f: for line in f: - m = re.match(rf'#define\s{macro}\s+(.+)', line) + m = re.match('#define\\s{macro}\\s+(.+)'.format(macro=macro), line) if m: return m.group(1) return '' @@ -41,7 +44,7 @@ def get_version(): patch = read_define(version_file_path, 'BROTLI_VERSION_PATCH') if not major or not minor or not patch: return '' - return f'{major}.{minor}.{patch}' + return '{major}.{minor}.{patch}'.format(major=major, minor=minor, patch=patch) def get_test_suite(): @@ -51,20 +54,7 @@ def get_test_suite(): class BuildExt(build_ext): - - def get_source_files(self): - filenames = build_ext.get_source_files(self) - for ext in self.extensions: - filenames.extend(ext.depends) - return filenames - def build_extension(self, ext): - if ext.sources is None or not isinstance(ext.sources, (list, tuple)): - raise errors.DistutilsSetupError( - "in 'ext_modules' option (extension '%s'), " - "'sources' must be present and must be " - "a list of source filenames" % ext.name) - ext_path = self.get_ext_fullpath(ext.name) depends = ext.sources + ext.depends if not (self.force or dep_util.newer_group(depends, ext_path, 'newer')): @@ -73,59 +63,13 @@ def build_extension(self, ext): else: log.info("building '%s' extension", ext.name) - c_sources = [] - for source in ext.sources: - if source.endswith('.c'): - c_sources.append(source) - extra_args = ext.extra_compile_args or [] - - objects = [] - - macros = ext.define_macros[:] - if platform.system() == 'Darwin': - macros.append(('OS_MACOSX', '1')) - elif self.compiler.compiler_type == 'mingw32': + if self.compiler.compiler_type == 'mingw32': # On Windows Python 2.7, pyconfig.h defines "hypot" as "_hypot", # This clashes with GCC's cmath, and causes compilation errors when # building under MinGW: http://bugs.python.org/issue11566 - macros.append(('_hypot', 'hypot')) - for undef in ext.undef_macros: - macros.append((undef,)) - - objs = self.compiler.compile( - c_sources, - output_dir=self.build_temp, - macros=macros, - include_dirs=ext.include_dirs, - debug=self.debug, - extra_postargs=extra_args, - depends=ext.depends) - objects.extend(objs) - - self._built_objects = objects[:] - if ext.extra_objects: - objects.extend(ext.extra_objects) - extra_args = ext.extra_link_args or [] - # when using GCC on Windows, we statically link libgcc and libstdc++, - # so that we don't need to package extra DLLs - if self.compiler.compiler_type == 'mingw32': - extra_args.extend(['-static-libgcc', '-static-libstdc++']) + ext.define_macros.append(('_hypot', 'hypot')) - ext_path = self.get_ext_fullpath(ext.name) - # Detect target language, if not provided - language = ext.language or self.compiler.detect_language(c_sources) - - self.compiler.link_shared_object( - objects, - ext_path, - libraries=self.get_libraries(ext), - library_dirs=ext.library_dirs, - runtime_library_dirs=ext.runtime_library_dirs, - extra_postargs=extra_args, - export_symbols=self.get_export_symbols(ext), - debug=self.debug, - build_temp=self.build_temp, - target_lang=language) + build_ext.build_extension(self, ext) NAME = 'Brotli' @@ -172,9 +116,25 @@ def build_extension(self, ext): PY_MODULES = ['brotli'] +class VersionedExtension(Extension): + def __init__(self, *args, **kwargs): + define_macros = [] + + if IS_PYTHON3 and CIBUILDWHEEL: + kwargs['py_limited_api'] = True + define_macros.append(('Py_LIMITED_API', '0x03060000')) + + if platform.system() == 'Darwin': + define_macros.append(('OS_MACOSX', '1')) + + kwargs['define_macros'] = kwargs.get('define_macros', []) + define_macros + + Extension.__init__(self, *args, **kwargs) + + EXT_MODULES = [ - Extension( - '_brotli', + VersionedExtension( + name='_brotli', sources=[ 'python/_brotli.c', 'c/common/constants.c', @@ -276,6 +236,24 @@ def build_extension(self, ext): 'build_ext': BuildExt, } +if IS_PYTHON3 and CIBUILDWHEEL: + from wheel.bdist_wheel import bdist_wheel + class bdist_wheel_abi3(bdist_wheel): + # adopted from: + # https://github.com/joerick/python-abi3-package-sample/blob/7f05b22b9e0cfb4e60293bc85252e95278a80720/setup.py + class bdist_wheel_abi3(bdist_wheel): + def get_tag(self): + python, abi, plat = super().get_tag() + + if python.startswith("cp"): + # on CPython, our wheels are abi3 and compatible back to 3.6 + return "cp36", "abi3", plat + + return python, abi, plat + + CMD_CLASS["bdist_wheel"] = bdist_wheel_abi3 + + with open("README.md", "r") as f: README = f.read() From 4fb7a4408e92cff378b3b0755207e7ee70a4ccf3 Mon Sep 17 00:00:00 2001 From: Rudolf Kolbe Date: Sat, 5 Oct 2024 22:27:43 +0200 Subject: [PATCH 4/8] setup.py - abi3 cp36 to cp32 (base stable api) --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 8bb0779ec..05b478ee1 100644 --- a/setup.py +++ b/setup.py @@ -122,7 +122,7 @@ def __init__(self, *args, **kwargs): if IS_PYTHON3 and CIBUILDWHEEL: kwargs['py_limited_api'] = True - define_macros.append(('Py_LIMITED_API', '0x03060000')) + define_macros.append(('Py_LIMITED_API', '3')) if platform.system() == 'Darwin': define_macros.append(('OS_MACOSX', '1')) @@ -247,7 +247,7 @@ def get_tag(self): if python.startswith("cp"): # on CPython, our wheels are abi3 and compatible back to 3.6 - return "cp36", "abi3", plat + return "cp32", "abi3", plat return python, abi, plat From 336a70acf951e291575789a4d3ac6fd9deb5fd0d Mon Sep 17 00:00:00 2001 From: Rudolf Kolbe Date: Sat, 5 Oct 2024 23:04:58 +0200 Subject: [PATCH 5/8] add type stubs --- MANIFEST.in | 4 +- python/brotli.pyi | 182 ++++++++++++++++++++++++++++++++++++++++++++++ python/py.typed | 0 3 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 python/brotli.pyi create mode 100644 python/py.typed diff --git a/MANIFEST.in b/MANIFEST.in index ff8d60065..279ce6f72 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -8,9 +8,11 @@ include c/enc/*.h include c/include/brotli/*.h include LICENSE include MANIFEST.in -include python/_brotli.cc +include python/_brotli.c include python/bro.py include python/brotli.py +include python/brotli.pyi +include python/py.typed include python/README.md include python/tests/* include README.md diff --git a/python/brotli.pyi b/python/brotli.pyi new file mode 100644 index 000000000..44a68719c --- /dev/null +++ b/python/brotli.pyi @@ -0,0 +1,182 @@ +# Copyright 2016 The Brotli Authors. All rights reserved. +# +# Distributed under MIT license. +# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT + +""" +Functions to compress and decompress data using the Brotli library. +""" + +from typing import Union + +ByteString = Union[bytes, bytearray, memoryview] + +version: str +__version__: str + +MODE_GENERIC: int = 0 +MODE_TEXT: int = 1 +MODE_FONT: int = 2 + +class Compressor: + """An object to compress a byte string.""" + + def __init__( + self, mode=MODE_GENERIC, quality: int = 11, lgwin: int = 22, lgblock: int = 0 + ): + """ + Args: + mode (int, optional): The compression mode can be + - MODE_GENERIC (default), + - MODE_TEXT (for UTF-8 format text input) + - MODE_FONT (for WOFF 2.0) + quality (int, optional): Controls the compression-speed vs compression-density tradeoff. + The higher the quality, the slower the compression. + Range is 0 to 11. + Defaults to 11. + lgwin (int, optional): Base 2 logarithm of the sliding window size. + Range is 10 to 24. + Defaults to 22. + lgblock (int, optional): Base 2 logarithm of the maximum input block size. + Range is 16 to 24. + If set to 0, the value will be set based on the quality. + Defaults to 0. + + Raises: + brotli.error: If arguments are invalid. + """ + + def process(self, string: ByteString) -> bytes: + """Process "string" for compression, returning a string that contains + compressed output data. This data should be concatenated to the output + produced by any preceding calls to the "process()" or "flush()" methods. + Some or all of the input may be kept in internal buffers for later + processing, and the compressed output data may be empty until enough input + has been accumulated. + + Args: + string (bytes): The input data + + Returns: + The compressed output data (bytes) + + Raises: + brotli.error: If compression fails + """ + ... + + def flush(self) -> bytes: + """Process all pending input, returning a string containing the remaining + compressed data. This data should be concatenated to the output produced by + any preceding calls to the "process()" or "flush()" methods. + + Returns: + The compressed output data (bytes) + + Raises: + brotli.error: If compression fails + """ + ... + + def finish(self) -> bytes: + """Process all pending input and complete all compression, returning a string + containing the remaining compressed data. This data should be concatenated + to the output produced by any preceding calls to the "process()" or "flush()" methods. + After calling "finish()", the "process()" and "flush()" methods + cannot be called again, and a new "Compressor" object should be created. + + Returns: + The compressed output data (bytes) + + Raises: + brotli.error: If compression fails + """ + ... + +class Decompressor: + """An object to decompress a byte string.""" + + def __init__(self): ... + def is_finished(self) -> bool: + """Checks if decoder instance reached the final state. + + Returns: + True if the decoder is in a state where it reached the end of the input + and produced all of the output + False otherwise + + Raises: + brotli.error: If decompression fails + """ + ... + + def process(self, string: ByteString) -> bytes: + """Process "string" for decompression, returning a string that contains + decompressed output data. This data should be concatenated to the output + produced by any preceding calls to the "process()" method. + Some or all of the input may be kept in internal buffers for later + processing, and the decompressed output data may be empty until enough input + has been accumulated. + + Args: + string (bytes): The input data + + Returns: + The decompressed output data (bytes) + + Raises: + brotli.error: If decompression fails + """ + ... + +def compress( + string: ByteString, + mode: int = MODE_GENERIC, + quality: int = 11, + lgwin: int = 22, + lgblock: int = 0, +): + """Compress a byte string. + + Args: + string (bytes): The input data. + mode (int, optional): The compression mode can be + - MODE_GENERIC (default), + - MODE_TEXT (for UTF-8 format text input) + - MODE_FONT (for WOFF 2.0) + quality (int, optional): Controls the compression-speed vs compression-density tradeoff. + The higher the quality, the slower the compression. + Range is 0 to 11. + Defaults to 11. + lgwin (int, optional): Base 2 logarithm of the sliding window size. + Range is 10 to 24. + Defaults to 22. + lgblock (int, optional): Base 2 logarithm of the maximum input block size. + Range is 16 to 24. + If set to 0, the value will be set based on the quality. + Defaults to 0. + + Returns: + The compressed byte string. + + Raises: + brotli.error: If arguments are invalid, or compressor fails. + """ + ... + +def decompress(string: bytes) -> bytes: + """ + Decompress a compressed byte string. + + Args: + string (bytes): The compressed input data. + + Returns: + The decompressed byte string. + + Raises: + brotli.error: If decompressor fails. + """ + ... + +class error(Exception): ... diff --git a/python/py.typed b/python/py.typed new file mode 100644 index 000000000..e69de29bb From 022aaf8e47d70293b27d902a0db8e057476aab91 Mon Sep 17 00:00:00 2001 From: Rudolf Kolbe Date: Sun, 6 Oct 2024 00:47:27 +0200 Subject: [PATCH 6/8] setup.py - fix build py36-abi3 wheel --- setup.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/setup.py b/setup.py index 05b478ee1..f7309fcd7 100644 --- a/setup.py +++ b/setup.py @@ -122,7 +122,7 @@ def __init__(self, *args, **kwargs): if IS_PYTHON3 and CIBUILDWHEEL: kwargs['py_limited_api'] = True - define_macros.append(('Py_LIMITED_API', '3')) + define_macros.append(('Py_LIMITED_API', '0x03060000')) if platform.system() == 'Darwin': define_macros.append(('OS_MACOSX', '1')) @@ -238,18 +238,17 @@ def __init__(self, *args, **kwargs): if IS_PYTHON3 and CIBUILDWHEEL: from wheel.bdist_wheel import bdist_wheel + # adopted from: + # https://github.com/joerick/python-abi3-package-sample/blob/7f05b22b9e0cfb4e60293bc85252e95278a80720/setup.py class bdist_wheel_abi3(bdist_wheel): - # adopted from: - # https://github.com/joerick/python-abi3-package-sample/blob/7f05b22b9e0cfb4e60293bc85252e95278a80720/setup.py - class bdist_wheel_abi3(bdist_wheel): - def get_tag(self): - python, abi, plat = super().get_tag() + def get_tag(self): + python, abi, plat = super().get_tag() - if python.startswith("cp"): - # on CPython, our wheels are abi3 and compatible back to 3.6 - return "cp32", "abi3", plat + if python.startswith("cp"): + # on CPython, our wheels are abi3 and compatible back to 3.6 + return "cp36", "abi3", plat - return python, abi, plat + return python, abi, plat CMD_CLASS["bdist_wheel"] = bdist_wheel_abi3 From 93f436dc857e097c23fdeba282a1a6460fe6dbe9 Mon Sep 17 00:00:00 2001 From: Rudolf Kolbe Date: Sun, 6 Oct 2024 01:31:58 +0200 Subject: [PATCH 7/8] setup.py - include type stubs --- python/__init__.py | 1 + setup.py | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 python/__init__.py diff --git a/python/__init__.py b/python/__init__.py new file mode 100644 index 000000000..ea85450bf --- /dev/null +++ b/python/__init__.py @@ -0,0 +1 @@ +from .brotli import * diff --git a/setup.py b/setup.py index f7309fcd7..1fc38f272 100644 --- a/setup.py +++ b/setup.py @@ -114,8 +114,6 @@ def build_extension(self, ext): PACKAGE_DIR = {'': 'python'} -PY_MODULES = ['brotli'] - class VersionedExtension(Extension): def __init__(self, *args, **kwargs): define_macros = [] @@ -268,7 +266,8 @@ def get_tag(self): platforms=PLATFORMS, classifiers=CLASSIFIERS, package_dir=PACKAGE_DIR, - py_modules=PY_MODULES, + packages=[''], + package_data={'': ['__init__.py', 'brotli.py', 'brotli.pyi', 'py.typed']}, ext_modules=EXT_MODULES, test_suite=TEST_SUITE, cmdclass=CMD_CLASS) From 5cee949c59d417950186f877a3c813367ef40325 Mon Sep 17 00:00:00 2001 From: Rudolf Kolbe Date: Tue, 26 Nov 2024 12:21:31 +0100 Subject: [PATCH 8/8] move python files from python/* to python/brotli/* --- python/__init__.py | 1 - python/{brotli.py => brotli/__init__.py} | 42 +++++++++++----------- python/{brotli.pyi => brotli/__init__.pyi} | 0 python/{bro.py => brotli/__main__.py} | 0 python/{ => brotli}/py.typed | 0 python/tests/_test_utils.py | 2 +- setup.py | 6 ++-- 7 files changed, 24 insertions(+), 27 deletions(-) delete mode 100644 python/__init__.py rename python/{brotli.py => brotli/__init__.py} (73%) rename python/{brotli.pyi => brotli/__init__.pyi} (100%) rename python/{bro.py => brotli/__main__.py} (100%) mode change 100755 => 100644 rename python/{ => brotli}/py.typed (100%) diff --git a/python/__init__.py b/python/__init__.py deleted file mode 100644 index ea85450bf..000000000 --- a/python/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .brotli import * diff --git a/python/brotli.py b/python/brotli/__init__.py similarity index 73% rename from python/brotli.py rename to python/brotli/__init__.py index 4787d63b2..cfa57e2f0 100644 --- a/python/brotli.py +++ b/python/brotli/__init__.py @@ -5,21 +5,26 @@ """Functions to compress and decompress data using the Brotli library.""" -import _brotli +from ._brotli import ( + # The library version. + __version__, + # The compression mode. + MODE_GENERIC, + MODE_TEXT, + MODE_FONT, + # The Compressor object. + Compressor, + # The Decompressor object. + Decompressor, + # Decompress a compressed byte string. + decompress, + # Raised if compression or decompression fails. + error, +) + + +version = __version__ -# The library version. -version = __version__ = _brotli.__version__ - -# The compression mode. -MODE_GENERIC = _brotli.MODE_GENERIC -MODE_TEXT = _brotli.MODE_TEXT -MODE_FONT = _brotli.MODE_FONT - -# The Compressor object. -Compressor = _brotli.Compressor - -# The Decompressor object. -Decompressor = _brotli.Decompressor # Compress a byte string. def compress(string, mode=MODE_GENERIC, quality=11, lgwin=22, lgblock=0): @@ -46,12 +51,5 @@ def compress(string, mode=MODE_GENERIC, quality=11, lgwin=22, lgblock=0): Raises: brotli.error: If arguments are invalid, or compressor fails. """ - compressor = Compressor(mode=mode, quality=quality, lgwin=lgwin, - lgblock=lgblock) + compressor = Compressor(mode=mode, quality=quality, lgwin=lgwin, lgblock=lgblock) return compressor.process(string) + compressor.finish() - -# Decompress a compressed byte string. -decompress = _brotli.decompress - -# Raised if compression or decompression fails. -error = _brotli.error diff --git a/python/brotli.pyi b/python/brotli/__init__.pyi similarity index 100% rename from python/brotli.pyi rename to python/brotli/__init__.pyi diff --git a/python/bro.py b/python/brotli/__main__.py old mode 100755 new mode 100644 similarity index 100% rename from python/bro.py rename to python/brotli/__main__.py diff --git a/python/py.typed b/python/brotli/py.typed similarity index 100% rename from python/py.typed rename to python/brotli/py.typed diff --git a/python/tests/_test_utils.py b/python/tests/_test_utils.py index 38afec396..b85c7505d 100644 --- a/python/tests/_test_utils.py +++ b/python/tests/_test_utils.py @@ -18,7 +18,7 @@ test_dir = os.path.join(project_dir, 'tests') if BRO_ARGS[0] is None: python_exe = sys.executable or 'python' - bro_path = os.path.join(project_dir, 'python', 'bro.py') + bro_path = os.path.join(project_dir, 'python', 'brotli', '__main__.py') BRO_ARGS = [python_exe, bro_path] # Get the platform/version-specific build folder. diff --git a/setup.py b/setup.py index 1fc38f272..a8b920477 100644 --- a/setup.py +++ b/setup.py @@ -132,7 +132,7 @@ def __init__(self, *args, **kwargs): EXT_MODULES = [ VersionedExtension( - name='_brotli', + name='brotli._brotli', sources=[ 'python/_brotli.c', 'c/common/constants.c', @@ -266,8 +266,8 @@ def get_tag(self): platforms=PLATFORMS, classifiers=CLASSIFIERS, package_dir=PACKAGE_DIR, - packages=[''], - package_data={'': ['__init__.py', 'brotli.py', 'brotli.pyi', 'py.typed']}, + packages=["brotli"], + package_data={"brotli": ["*.py", "*.pyi", "py.typed"]}, ext_modules=EXT_MODULES, test_suite=TEST_SUITE, cmdclass=CMD_CLASS)