Drop Py 3.9- support PiperOrigin-RevId: 830342213
diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index 4533dde..fe24532 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml
@@ -336,38 +336,6 @@ pip install setuptools==51.3.3 python setup.py ${{ matrix.py_setuptools_cmd || 'test'}} - build_test_py27: - name: Build and test with Python 2.7 - runs-on: ubuntu-latest - container: - image: ubuntu:22.04 - steps: - - - name: Harden Runner - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 - with: - egress-policy: audit - - - name: Install deps - run: | - apt update - apt install -y curl gcc python2.7 python2.7-dev - curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py - python2.7 get-pip.py - python2.7 -m pip install distutils-pytest==0.1 - - - name: Checkout the source - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - submodules: false - fetch-depth: 1 - - - name: Build / Test - run: | - python2.7 -VV - python2.7 -c "import sys; sys.exit('Invalid python version') if '.'.join(map(str,sys.version_info[0:2])) != '2.7' else True" - python2.7 setup.py test - build_test_dotnet: name: Build and test with .NET runs-on: ubuntu-latest
diff --git a/python/_brotli.c b/python/_brotli.c index 0ecc7aa..e456672 100644 --- a/python/_brotli.c +++ b/python/_brotli.c
@@ -12,18 +12,18 @@ #include <brotli/decode.h> #include <brotli/encode.h> -#if PY_MAJOR_VERSION >= 3 -#define PY_GET_TYPE(Obj) (Py_TYPE(Obj)) -#else -#define PY_GET_TYPE(Obj) ((Obj)->ob_type) +// 3.9 end-of-life is 2025-10-31. +// 3.10 end-of-life is 2026-10. +// 3.11 end-of-life is 2027-10. +// 3.12 end-of-life is 2028-10. +// 3.13 end-of-life is 2029-10. +// 3.14 end-of-life is 2030-10. +#if PY_MAJOR_VERSION < 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 10) +#error "Only Python 3.10+ is supported" #endif static const char kErrorAttr[] = "error"; -#if PY_MAJOR_VERSION >= 3 static const char kModuleAttr[] = "_module"; -#else -static PyObject* BrotliError; -#endif static const char kInvalidBufferError[] = "brotli: data must be a C-contiguous buffer"; @@ -201,7 +201,6 @@ /* clang-format on */ static void set_brotli_exception(PyObject* t, const char* msg) { -#if PY_MAJOR_VERSION >= 3 PyObject* error = NULL; PyObject* module = NULL; assert(t != NULL); @@ -213,13 +212,9 @@ if (error == NULL) return; /* AttributeError raised. */ PyErr_SetString(error, msg); Py_DECREF(error); -#else - PyErr_SetString(BrotliError, msg); -#endif } static void set_brotli_exception_from_module(PyObject* m, const char* msg) { -#if PY_MAJOR_VERSION >= 3 PyObject* error = NULL; assert(m != NULL); assert(PyModule_Check(m)); @@ -227,9 +222,6 @@ if (error == NULL) return; /* AttributeError raised. */ PyErr_SetString(error, msg); Py_DECREF(error); -#else - PyErr_SetString(BrotliError, msg); -#endif } /* @@ -354,7 +346,7 @@ } if (len == 0) return result; - out = PyBytes_AS_STRING(result); + out = (uint8_t*)PyBytes_AS_STRING(result); block = buffer->head; while (block != buffer->tail) { memcpy(out + pos, block->payload, block->size); @@ -389,7 +381,7 @@ self->enc = BrotliEncoderCreateInstance(0, 0, 0); if (self->enc == NULL) { set_brotli_exception(self_type, kCompressCreateError); - PY_GET_TYPE(self)->tp_free((PyObject*)self); + Py_TYPE(self)->tp_free((PyObject*)self); return NULL; } self->healthy = 1; @@ -401,7 +393,7 @@ PyObject* keywds) { static const char* kwlist[] = {"mode", "quality", "lgwin", "lgblock", NULL}; - PyObject* self_type = (PyObject*)PY_GET_TYPE((PyObject*)self); + PyObject* self_type = (PyObject*)Py_TYPE((PyObject*)self); unsigned char mode = BROTLI_DEFAULT_MODE; unsigned char quality = BROTLI_DEFAULT_QUALITY; unsigned char lgwin = BROTLI_DEFAULT_WINDOW; @@ -454,7 +446,7 @@ static void brotli_Compressor_dealloc(PyBrotli_Compressor* self) { if (self->enc) BrotliEncoderDestroyInstance(self->enc); - PY_GET_TYPE(self)->tp_free((PyObject*)self); + Py_TYPE(self)->tp_free((PyObject*)self); } /* @@ -466,7 +458,7 @@ static PyObject* compress_stream(PyBrotli_Compressor* self, BrotliEncoderOperation op, uint8_t* input, size_t input_length) { - PyObject* self_type = (PyObject*)PY_GET_TYPE((PyObject*)self); + PyObject* self_type = (PyObject*)Py_TYPE((PyObject*)self); size_t available_in = input_length; const uint8_t* next_in = input; Buffer buffer; @@ -526,7 +518,7 @@ static PyObject* brotli_Compressor_process(PyBrotli_Compressor* self, PyObject* args) { - PyObject* self_type = (PyObject*)PY_GET_TYPE((PyObject*)self); + PyObject* self_type = (PyObject*)Py_TYPE((PyObject*)self); PyObject* ret = NULL; PyObject* input_object = NULL; Py_buffer input; @@ -556,7 +548,7 @@ } static PyObject* brotli_Compressor_flush(PyBrotli_Compressor* self) { - PyObject* self_type = (PyObject*)PY_GET_TYPE((PyObject*)self); + PyObject* self_type = (PyObject*)Py_TYPE((PyObject*)self); PyObject* ret = NULL; if (self->healthy == 0) { @@ -575,7 +567,7 @@ } static PyObject* brotli_Compressor_finish(PyBrotli_Compressor* self) { - PyObject* self_type = (PyObject*)PY_GET_TYPE((PyObject*)self); + PyObject* self_type = (PyObject*)Py_TYPE((PyObject*)self); PyObject* ret = NULL; if (self->healthy == 0) { @@ -619,7 +611,7 @@ self->dec = BrotliDecoderCreateInstance(0, 0, 0); if (self->dec == NULL) { set_brotli_exception(self_type, kDecompressCreateError); - PY_GET_TYPE(self)->tp_free((PyObject*)self); + Py_TYPE(self)->tp_free((PyObject*)self); return NULL; } @@ -653,14 +645,14 @@ free(self->unconsumed_data); self->unconsumed_data = NULL; } - PY_GET_TYPE(self)->tp_free((PyObject*)self); + Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject* brotli_Decompressor_process(PyBrotli_Decompressor* self, PyObject* args, PyObject* keywds) { static const char* kwlist[] = {"", "output_buffer_limit", NULL}; - PyObject* self_type = (PyObject*)PY_GET_TYPE((PyObject*)self); + PyObject* self_type = (PyObject*)Py_TYPE((PyObject*)self); PyObject* ret = NULL; PyObject* input_object = NULL; Py_buffer input; @@ -720,7 +712,7 @@ if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { assert(buffer.avail_out == 0); /* All allocated is used -> reached the output length limit. */ - if (buffer.total_allocated >= output_buffer_limit) break; + if (buffer.total_allocated >= (uint64_t)output_buffer_limit) break; if (Buffer_Grow(&buffer) < 0) { oom = 1; break; @@ -782,7 +774,7 @@ } static PyObject* brotli_Decompressor_is_finished(PyBrotli_Decompressor* self) { - PyObject* self_type = (PyObject*)PY_GET_TYPE((PyObject*)self); + PyObject* self_type = (PyObject*)Py_TYPE((PyObject*)self); if (self->healthy == 0) { set_brotli_exception(self_type, kDecompressUnhealthyError); return NULL; @@ -800,7 +792,7 @@ static PyObject* brotli_Decompressor_can_accept_more_data( PyBrotli_Decompressor* self) { - PyObject* self_type = (PyObject*)PY_GET_TYPE((PyObject*)self); + PyObject* self_type = (PyObject*)Py_TYPE((PyObject*)self); if (self->healthy == 0) { set_brotli_exception(self_type, kDecompressUnhealthyError); return NULL; @@ -892,13 +884,6 @@ /* Module definition */ -static int init_brotli_mod(PyObject* m); - -static PyMethodDef brotli_methods[] = { - {"decompress", (PyCFunction)brotli_decompress, METH_VARARGS | METH_KEYWORDS, - brotli_decompress__doc__}, - {NULL, NULL, 0, NULL}}; - static PyMethodDef brotli_Compressor_methods[] = { {"process", (PyCFunction)brotli_Compressor_process, METH_VARARGS, brotli_Compressor_process_doc}, @@ -909,44 +894,6 @@ {NULL} /* Sentinel */ }; -static PyMethodDef brotli_Decompressor_methods[] = { - {"process", (PyCFunction)brotli_Decompressor_process, - METH_VARARGS | METH_KEYWORDS, brotli_Decompressor_process_doc}, - {"is_finished", (PyCFunction)brotli_Decompressor_is_finished, METH_NOARGS, - brotli_Decompressor_is_finished_doc}, - {"can_accept_more_data", - (PyCFunction)brotli_Decompressor_can_accept_more_data, METH_NOARGS, - brotli_Decompressor_can_accept_more_data_doc}, - {NULL} /* Sentinel */ -}; - -#if PY_MAJOR_VERSION >= 3 - -#if PY_MINOR_VERSION >= 5 -static PyModuleDef_Slot brotli_mod_slots[] = { - {Py_mod_exec, init_brotli_mod}, -#if PY_MINOR_VERSION >= 12 - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, -#endif - {0, NULL}}; -#endif - -static struct PyModuleDef brotli_module = { - PyModuleDef_HEAD_INIT, - "_brotli", /* m_name */ - brotli_doc, /* m_doc */ - 0, /* m_size */ - brotli_methods, /* m_methods */ -#if PY_MINOR_VERSION >= 5 - brotli_mod_slots, /* m_slots */ -#else - NULL, /* m_reload */ -#endif - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL /* m_free */ -}; - static PyType_Slot brotli_Compressor_slots[] = { {Py_tp_dealloc, (destructor)brotli_Compressor_dealloc}, {Py_tp_doc, (void*)brotli_Compressor_doc}, @@ -960,6 +907,17 @@ "brotli.Compressor", sizeof(PyBrotli_Compressor), 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, brotli_Compressor_slots}; +static PyMethodDef brotli_Decompressor_methods[] = { + {"process", (PyCFunction)brotli_Decompressor_process, + METH_VARARGS | METH_KEYWORDS, brotli_Decompressor_process_doc}, + {"is_finished", (PyCFunction)brotli_Decompressor_is_finished, METH_NOARGS, + brotli_Decompressor_is_finished_doc}, + {"can_accept_more_data", + (PyCFunction)brotli_Decompressor_can_accept_more_data, METH_NOARGS, + brotli_Decompressor_can_accept_more_data_doc}, + {NULL} /* Sentinel */ +}; + static PyType_Slot brotli_Decompressor_slots[] = { {Py_tp_dealloc, (destructor)brotli_Decompressor_dealloc}, {Py_tp_doc, (void*)brotli_Decompressor_doc}, @@ -973,127 +931,17 @@ "brotli.Decompressor", sizeof(PyBrotli_Decompressor), 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, brotli_Decompressor_slots}; -PyMODINIT_FUNC PyInit__brotli(void) { -#if PY_MINOR_VERSION < 5 - PyObject* m = PyModule_Create(&brotli_module); - if (m == NULL) return NULL; - if (init_brotli_mod(m) < 0) { - Py_DECREF(m); - m = NULL; - } - return m; -#else - return PyModuleDef_Init(&brotli_module); -#endif -} - -#else - -static PyTypeObject brotli_CompressorType = { - PyObject_HEAD_INIT(NULL) 0, /* ob_size */ - "brotli.Compressor", /* tp_name */ - sizeof(PyBrotli_Compressor), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)brotli_Compressor_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ - brotli_Compressor_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - brotli_Compressor_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)brotli_Compressor_init, /* tp_init */ - 0, /* tp_alloc */ - brotli_Compressor_new, /* tp_new */ -}; - -static PyTypeObject brotli_DecompressorType = { - PyObject_HEAD_INIT(NULL) 0, /* ob_size */ - "brotli.Decompressor", /* tp_name */ - sizeof(PyBrotli_Decompressor), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)brotli_Decompressor_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ - brotli_Decompressor_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - brotli_Decompressor_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)brotli_Decompressor_init, /* tp_init */ - 0, /* tp_alloc */ - brotli_Decompressor_new, /* tp_new */ -}; - -PyMODINIT_FUNC init_brotli(void) { - PyObject* m = Py_InitModule3("_brotli", brotli_methods, brotli_doc); - if (m == NULL) return; - init_brotli_mod(m); -} - -#endif - /* Emulates PyModule_AddObject */ static int RegisterObject(PyObject* mod, const char* name, PyObject* value) { assert(value != NULL); -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 10 int ret = PyModule_AddObjectRef(mod, name, value); /* Emulates PyModule_AddObject, i.e. decrements the reference count on success. */ if (ret == 0) Py_DECREF(value); return ret; -#else - return PyModule_AddObject(mod, name, value); -#endif } -static int init_brotli_mod(PyObject* m) { +static int brotli_init_mod(PyObject* m) { PyObject* error_type = NULL; PyObject* compressor_type = NULL; PyObject* decompressor_type = NULL; @@ -1104,34 +952,19 @@ if (error_type == NULL) goto error; if (RegisterObject(m, kErrorAttr, error_type) < 0) goto error; -#if PY_MAJOR_VERSION < 3 - /* Assumption: pointer is used only while module is alive and well. */ - BrotliError = error_type; -#endif error_type = NULL; -#if PY_MAJOR_VERSION >= 3 compressor_type = PyType_FromSpec(&brotli_Compressor_spec); decompressor_type = PyType_FromSpec(&brotli_Decompressor_spec); -#else - compressor_type = (PyObject*)&brotli_CompressorType; - Py_INCREF(compressor_type); - decompressor_type = (PyObject*)&brotli_DecompressorType; - Py_INCREF(decompressor_type); -#endif if (compressor_type == NULL) goto error; if (PyType_Ready((PyTypeObject*)compressor_type) < 0) goto error; -#if PY_MAJOR_VERSION >= 3 if (PyObject_SetAttrString(compressor_type, kModuleAttr, m) < 0) goto error; -#endif if (RegisterObject(m, "Compressor", compressor_type) < 0) goto error; compressor_type = NULL; if (decompressor_type == NULL) goto error; if (PyType_Ready((PyTypeObject*)decompressor_type) < 0) goto error; -#if PY_MAJOR_VERSION >= 3 if (PyObject_SetAttrString(decompressor_type, kModuleAttr, m) < 0) goto error; -#endif if (RegisterObject(m, "Decompressor", decompressor_type) < 0) goto error; decompressor_type = NULL; @@ -1162,3 +995,29 @@ } return -1; } + +static PyMethodDef brotli_methods[] = { + {"decompress", (PyCFunction)brotli_decompress, METH_VARARGS | METH_KEYWORDS, + brotli_decompress__doc__}, + {NULL, NULL, 0, NULL}}; + +static PyModuleDef_Slot brotli_mod_slots[] = { + {Py_mod_exec, brotli_init_mod}, +#if (PY_MAJOR_VERSION > 3) || (PY_MINOR_VERSION >= 12) + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, +#endif + {0, NULL}}; + +static struct PyModuleDef brotli_module = { + PyModuleDef_HEAD_INIT, + "_brotli", /* m_name */ + brotli_doc, /* m_doc */ + 0, /* m_size */ + brotli_methods, /* m_methods */ + brotli_mod_slots, /* m_slots */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL /* m_free */ +}; + +PyMODINIT_FUNC PyInit__brotli(void) { return PyModuleDef_Init(&brotli_module); }
diff --git a/python/tests/_test_utils.py b/python/tests/_test_utils.py index 36102b8..c451968 100644 --- a/python/tests/_test_utils.py +++ b/python/tests/_test_utils.py
@@ -5,13 +5,13 @@ import glob import itertools import os +import pathlib import sys import sysconfig import tempfile import unittest -# TODO(eustas): use str(pathlib.PurePath(file).parent.parent) for Python 3.4+ -project_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) +project_dir = str(pathlib.PurePath(__file__).parent.parent.parent) test_dir = os.getenv('BROTLI_TESTS_PATH') BRO_ARGS = [os.getenv('BROTLI_WRAPPER')]
diff --git a/python/tests/compressor_test.py b/python/tests/compressor_test.py index 8f92405..2c08f8f 100644 --- a/python/tests/compressor_test.py +++ b/python/tests/compressor_test.py
@@ -20,7 +20,7 @@ def tearDown(self): self.compressor = None - # super().tearDown() # Requires Py3+ + super().tearDown() def _check_decompression(self, test_data): # Write decompression to temp file and verify it matches the original. @@ -71,28 +71,28 @@ class TestCompressorQuality1(_TestCompressor, _test_utils.TestCase): def setUp(self): - # super().setUp() # Requires Py3+ + super().setUp() self.compressor = brotli.Compressor(quality=1) class TestCompressorQuality6(_TestCompressor, _test_utils.TestCase): def setUp(self): - # super().setUp() # Requires Py3+ + super().setUp() self.compressor = brotli.Compressor(quality=6) class TestCompressorQuality9(_TestCompressor, _test_utils.TestCase): def setUp(self): - # super().setUp() # Requires Py3+ + super().setUp() self.compressor = brotli.Compressor(quality=9) class TestCompressorQuality11(_TestCompressor, _test_utils.TestCase): def setUp(self): - # super().setUp() # Requires Py3+ + super().setUp() self.compressor = brotli.Compressor(quality=11)
diff --git a/python/tests/decompressor_test.py b/python/tests/decompressor_test.py index 67844b3..1aa9ecb 100644 --- a/python/tests/decompressor_test.py +++ b/python/tests/decompressor_test.py
@@ -22,12 +22,12 @@ MIN_OUTPUT_BUFFER_SIZE = 32768 # Actually, several bytes less. def setUp(self): - # super().setUp() # Requires Py3+ + super().setUp() self.decompressor = brotli.Decompressor() def tearDown(self): self.decompressor = None - # super().tearDown() # Requires Py3+ + super().tearDown() def _check_decompression(self, test_data): # Verify decompression matches the original.
diff --git a/setup.py b/setup.py index 5c82320..60cee8a 100644 --- a/setup.py +++ b/setup.py
@@ -4,16 +4,11 @@ # See file LICENSE for detail or copy at https://opensource.org/licenses/MIT import os -import platform import re import unittest -try: - from setuptools import Extension - from setuptools import setup -except: - from distutils.core import Extension - from distutils.core import setup +from setuptools import Extension +from setuptools import setup from distutils.command.build_ext import build_ext from distutils import errors from distutils import dep_util @@ -166,12 +161,12 @@ "Programming Language :: C", "Programming Language :: C++", "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Unix Shell", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules",