From ba18089d74f991b9b9e7ac9c17f85432f50d0048 Mon Sep 17 00:00:00 2001 From: Alexander Shadchin Date: Wed, 10 Jan 2024 12:34:10 +0300 Subject: [PATCH 01/18] Sync license in metadata with LICENSE file (#183) --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 7e0a33a..5f49b1d 100644 --- a/setup.py +++ b/setup.py @@ -60,7 +60,7 @@ def get_libraries(): author="Jan-Erik Rediger, Pieter Noordhuis", author_email="janerik@fnordig.de, pcnoordhuis@gmail.com", keywords=["Redis"], - license="BSD", + license="MIT", packages=["hiredis"], package_data={"hiredis": ["hiredis.pyi", "py.typed"]}, ext_modules=[ext], @@ -72,7 +72,7 @@ def get_libraries(): classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', + 'License :: OSI Approved :: MIT License', 'Operating System :: MacOS', 'Operating System :: POSIX', 'Programming Language :: C', From 64e3394548fe670e7853a2407799e13daa4bf2cb Mon Sep 17 00:00:00 2001 From: Chayim Date: Wed, 10 Jan 2024 11:37:56 +0200 Subject: [PATCH 02/18] Badge for latest released on Pypi (#182) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d1c0d9b..320aba1 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![Build Status](https://github.com/redis/hiredis-py/actions/workflows/integration.yaml/badge.svg)](https://github.com/redis/hiredis-py/actions/workflows/integration.yaml) [![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) +[![pypi](https://badge.fury.io/py/hiredis.svg)](https://pypi.org/project/hiredis/) Python extension that wraps protocol parsing code in [hiredis][hiredis]. It primarily speeds up parsing of multi bulk replies. From cc239705fb64f92c6ac3aff36679a300662e0ca7 Mon Sep 17 00:00:00 2001 From: Chayim Date: Wed, 3 Jul 2024 15:53:15 +0300 Subject: [PATCH 03/18] Removing Python 3.7 trove (#181) Removing support for Python 3.7. Take the opportunity to better support Python 3.12 by adding setuptools to dev_requirements. --- .github/workflows/integration.yaml | 2 +- README.md | 2 +- dev_requirements.txt | 5 +++-- setup.py | 3 +-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index f0a288a..46de073 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -25,7 +25,7 @@ jobs: strategy: max-parallel: 15 matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', 'pypy-3.7', 'pypy-3.8', 'pypy-3.9'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', 'pypy-3.8', 'pypy-3.9'] os: ['ubuntu-latest', 'windows-latest', 'macos-latest'] fail-fast: false env: diff --git a/README.md b/README.md index 320aba1..1832c19 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ python -m pytest ### Requirements -hiredis-py requires **Python 3.7+**. +hiredis-py requires **Python 3.8+**. Make sure Python development headers are available when installing hiredis-py. On Ubuntu/Debian systems, install them with `apt-get install python3-dev`. diff --git a/dev_requirements.txt b/dev_requirements.txt index 3ff7078..26a1513 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -1,7 +1,8 @@ black==22.3.0 flake8==4.0.1 isort==5.10.1 +pytest>=7.0.0 +setuptools tox==3.24.4 vulture>=2.3.0 -wheel>=0.30.0 -pytest>=7.0.0 +wheel>=0.30.0 \ No newline at end of file diff --git a/setup.py b/setup.py index 5f49b1d..77b60ac 100644 --- a/setup.py +++ b/setup.py @@ -64,7 +64,7 @@ def get_libraries(): packages=["hiredis"], package_data={"hiredis": ["hiredis.pyi", "py.typed"]}, ext_modules=[ext], - python_requires=">=3.7", + python_requires=">=3.8", project_urls={ "Changes": "https://github.com/redis/hiredis-py/releases", "Issue tracker": "https://github.com/redis/hiredis-py/issues", @@ -78,7 +78,6 @@ def get_libraries(): 'Programming Language :: C', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', From e70af5b94f0881f23f2fb35417bb2e0d0b792f53 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 16:02:07 +0300 Subject: [PATCH 04/18] Bump black from 22.3.0 to 24.3.0 (#185) Bumps [black](https://github.com/psf/black) from 22.3.0 to 24.3.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/22.3.0...24.3.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev_requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev_requirements.txt b/dev_requirements.txt index 26a1513..8e9189e 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -1,4 +1,4 @@ -black==22.3.0 +black==24.3.0 flake8==4.0.1 isort==5.10.1 pytest>=7.0.0 From f4dd0814c16dc9a8efa72434101d49c97778c830 Mon Sep 17 00:00:00 2001 From: Apteryks Date: Thu, 11 Jul 2024 12:13:04 +0000 Subject: [PATCH 05/18] pack: Replace sdsalloc.h with alloc.h (#159) Fixes #158. * src/pack.c: Replace sdsalloc.h with alloc.h. (pack_command): Replace s_malloc with hi_malloc. --- src/pack.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pack.c b/src/pack.c index 443e9d3..23e4004 100644 --- a/src/pack.c +++ b/src/pack.c @@ -16,7 +16,7 @@ extern sds sdscpylen(sds s, const char *t, size_t len); extern sds sdsnewlen(const void *init, size_t initlen); #endif -#include +#include PyObject * pack_command(PyObject *cmd) @@ -32,7 +32,7 @@ pack_command(PyObject *cmd) } Py_ssize_t tokens_number = PyTuple_Size(cmd); - sds *tokens = s_malloc(sizeof(sds) * tokens_number); + sds *tokens = hi_malloc(sizeof(sds) * tokens_number); if (tokens == NULL) { return PyErr_NoMemory(); @@ -118,4 +118,4 @@ pack_command(PyObject *cmd) sdsfreesplitres(tokens, tokens_number); hi_free(lengths); return result; -} \ No newline at end of file +} From 4c970a336567223573c700a44e405a0c263a85fa Mon Sep 17 00:00:00 2001 From: Gabriel Erzse Date: Fri, 12 Jul 2024 09:56:19 +0300 Subject: [PATCH 06/18] Fix building the wheel for windows (#190) The CI job that builds the wheel for Windows fails. Try to fix it. Also upgrade versions for GH actions, and use ubuntu-latest instead of a specific version. --- .github/workflows/REUSABLE-wheeler.yaml | 37 ++++++++++++------------- .gitignore | 1 + 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/REUSABLE-wheeler.yaml b/.github/workflows/REUSABLE-wheeler.yaml index f893427..c36d592 100644 --- a/.github/workflows/REUSABLE-wheeler.yaml +++ b/.github/workflows/REUSABLE-wheeler.yaml @@ -21,7 +21,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-20.04, windows-2019, macos-latest] + os: [ubuntu-latest, windows-latest, macos-latest] env: CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" MACOSX_DEPLOYMENT_TARGET: "10.15" @@ -32,36 +32,36 @@ jobs: - name: Set up QEMU if: runner.os == 'Linux' - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 with: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.16.2 + uses: pypa/cibuildwheel@v2.19.2 env: # configure cibuildwheel to build native archs ('auto'), and some # emulated ones CIBW_ARCHS_LINUX: auto aarch64 ppc64le s390x - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: ${{matrix.os}}-wheels path: ./wheelhouse/*.whl build_sdist: name: Build source dist - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: - python-version: '3.10' - - uses: actions/checkout@v3 + python-version: 3.10 + - uses: actions/checkout@v4 with: submodules: recursive - name: Build sdist run: | python3 setup.py sdist - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: source-dist path: ./dist/*.tar.gz @@ -69,33 +69,32 @@ jobs: publish: name: Pypi publish if: ${{inputs.release == true}} - # needs: ['build_wheels', 'build_sdist'] needs: ['build_wheels', 'build_sdist'] runs-on: ubuntu-latest steps: - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: 3.10 - name: Install tools run: | pip install twine wheel - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: - name: ubuntu-20.04-wheels + name: ubuntu-latest-wheels path: artifacts/linux - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: - name: windows-2019-wheels + name: windows-latest-wheels path: artifacts/windows - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: macos-latest-wheels path: artifacts/macos - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: source-dist path: artifacts/sdist - - name: unify wheel structure + - name: Unify wheel structure run: | mkdir dist cp -R artifacts/windows/* dist diff --git a/.gitignore b/.gitignore index 86cb2c8..de2d486 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ MANIFEST .venv **/*.so hiredis.egg-info +.idea From 01fa2fd6f123e3424ffa00a647f2bf83d48543be Mon Sep 17 00:00:00 2001 From: Gabriel Erzse Date: Fri, 12 Jul 2024 11:30:04 +0300 Subject: [PATCH 07/18] Quote version for Python setup action in CI (#191) --- .github/workflows/REUSABLE-wheeler.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/REUSABLE-wheeler.yaml b/.github/workflows/REUSABLE-wheeler.yaml index c36d592..6ef66be 100644 --- a/.github/workflows/REUSABLE-wheeler.yaml +++ b/.github/workflows/REUSABLE-wheeler.yaml @@ -54,7 +54,7 @@ jobs: steps: - uses: actions/setup-python@v5 with: - python-version: 3.10 + python-version: '3.10' - uses: actions/checkout@v4 with: submodules: recursive @@ -74,7 +74,7 @@ jobs: steps: - uses: actions/setup-python@v5 with: - python-version: 3.10 + python-version: '3.10' - name: Install tools run: | pip install twine wheel From 7792dd23384aa7b00761df51d8011fe24fa33143 Mon Sep 17 00:00:00 2001 From: ArtemIsmagilov <118372045+ArtemIsmagilov@users.noreply.github.com> Date: Mon, 15 Jul 2024 13:18:28 +0400 Subject: [PATCH 08/18] Fix a typo in the README file (#192) Fix a typo in the README file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1832c19..6729c1b 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ pip install hiredis Building this repository requires a recursive checkout of submodules, and building hiredis. The following example shows how to clone, compile, and run tests. Please note - you will need the gcc installed. ```bash -git clone --recursse-submodules https://github.com/redis/hiredis-py +git clone --recurse-submodules https://github.com/redis/hiredis-py python setup.py build_ext --inplace python -m pytest ``` From a94bb447173c60b90709a9ed117b3a5b699ff795 Mon Sep 17 00:00:00 2001 From: Gabriel Erzse Date: Fri, 19 Jul 2024 14:16:27 +0300 Subject: [PATCH 09/18] Version 2.4.0 (#193) --- hiredis/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hiredis/version.py b/hiredis/version.py index ef6497d..3d67cd6 100644 --- a/hiredis/version.py +++ b/hiredis/version.py @@ -1 +1 @@ -__version__ = "2.3.2" +__version__ = "2.4.0" From c1eefbdb76614435f7433207bf385ba8cb930b60 Mon Sep 17 00:00:00 2001 From: Gabriel Erzse Date: Fri, 19 Jul 2024 14:57:21 +0300 Subject: [PATCH 10/18] Return Redis sets as Python lists (#189) In some (rare) cases, the sets from a Redis response contain nested types that are not hashable in Python, for example maps. To handle these cases uniformly, always return Python lists for Redis sets. The elements will still be unique, relying on the correctness of data arriving from the server. This is a breaking change, although in reality it might not have a big impact. Therefore the major version gets bumped to 3. --- hiredis/version.py | 2 +- src/reader.c | 7 ------- tests/test_reader.py | 6 +++++- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/hiredis/version.py b/hiredis/version.py index 3d67cd6..528787c 100644 --- a/hiredis/version.py +++ b/hiredis/version.py @@ -1 +1 @@ -__version__ = "2.4.0" +__version__ = "3.0.0" diff --git a/src/reader.c b/src/reader.c index 77a7efe..5960995 100644 --- a/src/reader.c +++ b/src/reader.c @@ -82,10 +82,6 @@ static void *tryParentize(const redisReadTask *task, PyObject *obj) { PyDict_SetItem(parent, last_key, obj); } break; - case REDIS_REPLY_SET: - assert(PyAnySet_CheckExact(parent)); - PySet_Add(parent, obj); - break; default: assert(PyList_CheckExact(parent)); PyList_SET_ITEM(parent, task->idx, obj); @@ -162,9 +158,6 @@ static void *createArrayObject(const redisReadTask *task, size_t elements) { case REDIS_REPLY_MAP: obj = PyDict_New(); break; - case REDIS_REPLY_SET: - obj = PySet_New(NULL); - break; default: obj = PyList_New(elements); } diff --git a/tests/test_reader.py b/tests/test_reader.py index d78fb63..5b36a73 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -136,7 +136,11 @@ def test_none(reader): def test_set(reader): reader.feed(b"~3\r\n+tangerine\r\n_\r\n,10.5\r\n") - assert {b"tangerine", None, 10.5} == reader.gets() + assert [b"tangerine", None, 10.5] == reader.gets() + +def test_set_with_nested_dict(reader): + reader.feed(b"~2\r\n+tangerine\r\n%1\r\n+a\r\n:1\r\n") + assert [b"tangerine", {b"a": 1}] == reader.gets() def test_dict(reader): reader.feed(b"%2\r\n+radius\r\n,4.5\r\n+diameter\r\n:9\r\n") From 4113c717d17b7bd30243254d8b673fdcc2e270b3 Mon Sep 17 00:00:00 2001 From: Dirk Doesburg Date: Mon, 18 Nov 2024 10:05:02 +0100 Subject: [PATCH 11/18] Update Python 3.13 compatibility (#199) Fixes #186 Updates CI to build cp313 wheels. --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/redis/hiredis-py/issues/186?shareId=XXXX-XXXX-XXXX-XXXX). --- .github/workflows/REUSABLE-wheeler.yaml | 2 +- .github/workflows/integration.yaml | 2 +- CHANGELOG.md | 1 + setup.py | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/REUSABLE-wheeler.yaml b/.github/workflows/REUSABLE-wheeler.yaml index 6ef66be..10bd88e 100644 --- a/.github/workflows/REUSABLE-wheeler.yaml +++ b/.github/workflows/REUSABLE-wheeler.yaml @@ -37,7 +37,7 @@ jobs: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.19.2 + uses: pypa/cibuildwheel@v2.21.3 env: # configure cibuildwheel to build native archs ('auto'), and some # emulated ones diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 46de073..1e7ba2f 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -25,7 +25,7 @@ jobs: strategy: max-parallel: 15 matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', 'pypy-3.8', 'pypy-3.9'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', 'pypy-3.8', 'pypy-3.9', 'pypy-3.10'] os: ['ubuntu-latest', 'windows-latest', 'macos-latest'] fail-fast: false env: diff --git a/CHANGELOG.md b/CHANGELOG.md index f116f1b..c6e2ae7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ * Implement pack_command that serializes redis-py command to the RESP bytes object. * Implement garbage collection support in Reader (#162) * Python 3.12 +* Python 3.13 compatibility ### 2.1.1 (2023-10-01) diff --git a/setup.py b/setup.py index 77b60ac..90a29d8 100644 --- a/setup.py +++ b/setup.py @@ -83,6 +83,7 @@ def get_libraries(): 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Software Development', ], From 5b34a0e2aade3f5669e92e06b843858aaea12c30 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Thu, 24 Apr 2025 12:10:46 +0200 Subject: [PATCH 12/18] Use GitHub Releases to track changelogs (#202) --- CHANGELOG.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6e2ae7..6a12f11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ -* Implement pack_command that serializes redis-py command to the RESP bytes object. -* Implement garbage collection support in Reader (#162) -* Python 3.12 -* Python 3.13 compatibility +# Hiredis-py Changelog + +Since [v2.2.1](https://github.com/redis/hiredis-py/releases/tag/v2.2.1) we track changelog using [GitHub releases](https://github.com/redis/hiredis-py/releases). +Below you can find the changelog for all versions prior to that. + +----------------- ### 2.1.1 (2023-10-01) From 7e77f22963e8889dea6165bd6ac26203645cfb04 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 9 May 2025 10:51:06 +0200 Subject: [PATCH 13/18] Fix memory leak in RESP3 map parsing (#204) * Fix memory leak in RESP3 map parsing Fix #175 * Add memray deps installation * Install deps only on ubuntu * Disable memray installation on Windows * Another attempt * Fix memray on old macOS versions * Disable memray for PyPy * Exclude FreeBSD * Revert * Another attempt for freebsd fix * Another attempt for freebsd fix --- .github/workflows/freebsd.yaml | 7 +++--- .github/workflows/integration.yaml | 6 +++++ dev_requirements.txt | 4 ++- src/reader.c | 25 ++++++++++++++----- tests/test_reader.py | 39 ++++++++++++++++++++++++++++++ 5 files changed, 71 insertions(+), 10 deletions(-) diff --git a/.github/workflows/freebsd.yaml b/.github/workflows/freebsd.yaml index 0eb3739..b1bcdc3 100644 --- a/.github/workflows/freebsd.yaml +++ b/.github/workflows/freebsd.yaml @@ -41,7 +41,8 @@ jobs: curl -s https://bootstrap.pypa.io/get-pip.py -o get-pip.py python3.9 get-pip.py /usr/local/bin/pip install -U pip setuptools wheel - /usr/local/bin/pip install -r dev_requirements.txt + sed '5,6d' dev_requirements.txt > dev_requirements_without_memray.txt + /usr/local/bin/pip install -r dev_requirements_without_memray.txt /usr/local/bin/python3.9 setup.py build_ext --inplace - python -m pytest - python3.9 setup.py bdist_wheel + /usr/local/bin/python3.9 -m pytest + /usr/local/bin/python3.9 setup.py bdist_wheel diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 1e7ba2f..d6a2f7b 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -40,7 +40,13 @@ jobs: python-version: ${{ matrix.python-version }} cache: 'pip' cache-dependency-path: dev_requirements.txt + - name: Install memray deps + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + sudo apt-get install build-essential python3-dev libdebuginfod-dev libunwind-dev liblz4-dev -y -qq - name: run tests + env: + MACOSX_DEPLOYMENT_TARGET: 10.14 run: | pip install -U pip setuptools wheel pip install -r dev_requirements.txt diff --git a/dev_requirements.txt b/dev_requirements.txt index 8e9189e..a0b3dfe 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -1,7 +1,9 @@ black==24.3.0 flake8==4.0.1 isort==5.10.1 -pytest>=7.0.0 +pytest>=7.2.0 +memray==1.17.1 ; sys_platform != 'win32' and platform_python_implementation != 'PyPy' +pytest-memray==1.7.0 ; sys_platform != 'win32' and platform_python_implementation != 'PyPy' setuptools tox==3.24.4 vulture>=2.3.0 diff --git a/src/reader.c b/src/reader.c index 5960995..e0052f7 100644 --- a/src/reader.c +++ b/src/reader.c @@ -74,12 +74,19 @@ static void *tryParentize(const redisReadTask *task, PyObject *obj) { case REDIS_REPLY_MAP: if (task->idx % 2 == 0) { /* Set a temporary item to save the object as a key. */ - PyDict_SetItem(parent, obj, Py_None); + int res = PyDict_SetItem(parent, obj, Py_None); + Py_DECREF(obj); + + if (res == -1) { + return NULL; + } } else { /* Pop the temporary item and set proper key and value. */ PyObject *last_item = PyObject_CallMethod(parent, "popitem", NULL); PyObject *last_key = PyTuple_GetItem(last_item, 0); PyDict_SetItem(parent, last_key, obj); + Py_DECREF(last_item); + Py_DECREF(obj); } break; default: @@ -359,8 +366,6 @@ static PyObject *Reader_feed(hiredis_ReaderObject *self, PyObject *args) { static PyObject *Reader_gets(hiredis_ReaderObject *self, PyObject *args) { PyObject *obj; - PyObject *err; - char *errstr; self->shouldDecode = 1; if (!PyArg_ParseTuple(args, "|i", &self->shouldDecode)) { @@ -368,9 +373,17 @@ static PyObject *Reader_gets(hiredis_ReaderObject *self, PyObject *args) { } if (redisReaderGetReply(self->reader, (void**)&obj) == REDIS_ERR) { - errstr = redisReaderGetError(self->reader); - /* protocolErrorClass might be a callable. call it, then use it's type */ - err = createError(self->protocolErrorClass, errstr, strlen(errstr)); + PyObject *err = NULL; + char *errstr = NULL; + + // Checking if there is no error during the call to redisReaderGetReply + // to avoid getting a SystemError. + if (PyErr_Occurred() == NULL) { + errstr = redisReaderGetError(self->reader); + /* protocolErrorClass might be a callable. call it, then use it's type */ + err = createError(self->protocolErrorClass, errstr, strlen(errstr)); + } + if (err != NULL) { obj = PyObject_Type(err); PyErr_SetString(obj, errstr); diff --git a/tests/test_reader.py b/tests/test_reader.py index 5b36a73..3694084 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -146,6 +146,45 @@ def test_dict(reader): reader.feed(b"%2\r\n+radius\r\n,4.5\r\n+diameter\r\n:9\r\n") assert {b"radius": 4.5, b"diameter": 9} == reader.gets() +@pytest.mark.limit_memory("50 KB") +def test_dict_memory_leaks(reader): + data = ( + b"%5\r\n" + b"+radius\r\n,4.5\r\n" + b"+diameter\r\n:9\r\n" + b"+nested_map\r\n" + b"%2\r\n" + b"+key1\r\n+value1\r\n" + b"+key2\r\n:42\r\n" + b"+nested_array\r\n" + b"*2\r\n" + b"+item1\r\n" + b"+item2\r\n" + b"+nested_set\r\n" + b"~2\r\n" + b"+element1\r\n" + b"+element2\r\n" + ) + for i in range(10000): + reader.feed(data) + res = reader.gets() + assert { + b"radius": 4.5, + b"diameter": 9, + b"nested_map": {b"key1": b"value1", b"key2": 42}, + b"nested_array": [b"item1", b"item2"], + b"nested_set": [b"element1", b"element2"], + } == res + +def test_dict_with_unhashable_key(reader): + reader.feed( + b"%1\r\n" + b"%1\r\n+key1\r\n+value1\r\n" + b":9\r\n" + ) + with pytest.raises(TypeError): + reader.gets() + def test_vector(reader): reader.feed(b">4\r\n+pubsub\r\n+message\r\n+channel\r\n+message\r\n") assert [b"pubsub", b"message", b"channel", b"message"] == reader.gets() From ac31d58c5f78090fdfb165f6ecef4e1a539c20a4 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 9 May 2025 16:47:44 +0200 Subject: [PATCH 14/18] Bump version to 3.2.0-dev (#207) * Bump version to 3.1.1 * Oops, it should be 3.2.0-dev instead --- hiredis/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hiredis/version.py b/hiredis/version.py index 528787c..ff214f2 100644 --- a/hiredis/version.py +++ b/hiredis/version.py @@ -1 +1 @@ -__version__ = "3.0.0" +__version__ = "3.2.0-dev" From 86089654830a9c5f11d157173e84d7b5ba30eda4 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 9 May 2025 17:41:25 +0200 Subject: [PATCH 15/18] Update hiredis to 1.3.0 (#203) --- vendor/hiredis | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/hiredis b/vendor/hiredis index 60e5075..ccad7eb 160000 --- a/vendor/hiredis +++ b/vendor/hiredis @@ -1 +1 @@ -Subproject commit 60e5075d4ac77424809f855ba3e398df7aacefe8 +Subproject commit ccad7ebaf99310957004661d1c5f82d2a33ebd10 From 58fe9603cd7bcb921de7ba3f43e1556f1df81e02 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Fri, 9 May 2025 17:50:44 +0200 Subject: [PATCH 16/18] Run integration workflow on version branches (#210) --- .github/workflows/integration.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index d6a2f7b..aa33ab6 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -8,11 +8,11 @@ on: - '**/*.md' branches: - master - - '[0-9].[0-9]' + - 'v[0-9].[0-9]' pull_request: branches: - master - - '[0-9].[0-9]' + - 'v[0-9].[0-9]' permissions: contents: read # to fetch code (actions/checkout) From d5548270b6f2ac00be36d6c68403f1d94f81d086 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Thu, 22 May 2025 11:55:03 +0300 Subject: [PATCH 17/18] Introduce new type for RESP3 PUSH notifications (#208) * Introduce new type for RESP3 PUSH notifications Allow clients to distinguish between RESP3 arrays and PUSH types by introducing PushNotification type which subclasses list. Fix #128 * Use simpler solution to preallocate PushNotification list * Another attempt to make PushNotificationType compatible with PyPy --- hiredis/__init__.py | 3 ++- hiredis/hiredis.pyi | 4 ++++ src/hiredis.c | 8 +++++++ src/reader.c | 53 ++++++++++++++++++++++++++++++++++++++++++++ src/reader.h | 5 +++++ tests/test_reader.py | 6 +++-- 6 files changed, 76 insertions(+), 3 deletions(-) diff --git a/hiredis/__init__.py b/hiredis/__init__.py index 623ee6b..a0eff07 100644 --- a/hiredis/__init__.py +++ b/hiredis/__init__.py @@ -1,4 +1,4 @@ -from hiredis.hiredis import Reader, HiredisError, pack_command, ProtocolError, ReplyError +from hiredis.hiredis import Reader, HiredisError, pack_command, ProtocolError, ReplyError, PushNotification from hiredis.version import __version__ __all__ = [ @@ -6,5 +6,6 @@ "HiredisError", "pack_command", "ProtocolError", + "PushNotification", "ReplyError", "__version__"] diff --git a/hiredis/hiredis.pyi b/hiredis/hiredis.pyi index d75e88d..e7d852e 100644 --- a/hiredis/hiredis.pyi +++ b/hiredis/hiredis.pyi @@ -13,6 +13,10 @@ class ReplyError(HiredisError): ... +class PushNotification(list): + ... + + class Reader: def __init__( self, diff --git a/src/hiredis.c b/src/hiredis.c index c96097d..f858072 100644 --- a/src/hiredis.c +++ b/src/hiredis.c @@ -59,6 +59,11 @@ PyMODINIT_FUNC PyInit_hiredis(void) return NULL; } + PushNotificationType.tp_base = &PyList_Type; + if (PyType_Ready(&PushNotificationType) < 0) { + return NULL; + } + mod_hiredis = PyModule_Create(&hiredis_ModuleDef); /* Setup custom exceptions */ @@ -79,5 +84,8 @@ PyMODINIT_FUNC PyInit_hiredis(void) Py_INCREF(&hiredis_ReaderType); PyModule_AddObject(mod_hiredis, "Reader", (PyObject *)&hiredis_ReaderType); + Py_INCREF(&PushNotificationType); + PyModule_AddObject(mod_hiredis, "PushNotification", (PyObject *)&PushNotificationType); + return mod_hiredis; } diff --git a/src/reader.c b/src/reader.c index e0052f7..2e32617 100644 --- a/src/reader.c +++ b/src/reader.c @@ -1,6 +1,7 @@ #include "reader.h" #include +#include static void Reader_dealloc(hiredis_ReaderObject *self); static int Reader_traverse(hiredis_ReaderObject *self, visitproc visit, void *arg); @@ -14,6 +15,10 @@ static PyObject *Reader_len(hiredis_ReaderObject *self); static PyObject *Reader_has_data(hiredis_ReaderObject *self); static PyObject *Reader_set_encoding(hiredis_ReaderObject *self, PyObject *args, PyObject *kwds); +static int PushNotificationType_init(PushNotificationObject *self, PyObject *args, PyObject *kwds); +/* Create a new instance of PushNotificationType with preallocated number of elements */ +static PyObject* PushNotificationType_New(Py_ssize_t size); + static PyMethodDef hiredis_ReaderMethods[] = { {"feed", (PyCFunction)Reader_feed, METH_VARARGS, NULL }, {"gets", (PyCFunction)Reader_gets, METH_VARARGS, NULL }, @@ -66,6 +71,16 @@ PyTypeObject hiredis_ReaderType = { Reader_new, /*tp_new */ }; +PyTypeObject PushNotificationType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = MOD_HIREDIS ".PushNotification", + .tp_basicsize = sizeof(PushNotificationObject), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = "Redis PUSH notification type", + .tp_init = (initproc) PushNotificationType_init, +}; + static void *tryParentize(const redisReadTask *task, PyObject *obj) { PyObject *parent; if (task && task->parent) { @@ -165,6 +180,9 @@ static void *createArrayObject(const redisReadTask *task, size_t elements) { case REDIS_REPLY_MAP: obj = PyDict_New(); break; + case REDIS_REPLY_PUSH: + obj = PushNotificationType_New(elements); + break; default: obj = PyList_New(elements); } @@ -199,6 +217,41 @@ static void freeObject(void *obj) { Py_XDECREF(obj); } +static int PushNotificationType_init(PushNotificationObject *self, PyObject *args, PyObject *kwds) { + return PyList_Type.tp_init((PyObject *)self, args, kwds); +} + +static PyObject* PushNotificationType_New(Py_ssize_t size) { + /* Check for negative size */ + if (size < 0) { + PyErr_SetString(PyExc_SystemError, "negative list size"); + return NULL; + } + + /* Check for potential overflow */ + if ((size_t)size > PY_SSIZE_T_MAX / sizeof(PyObject*)) { + return PyErr_NoMemory(); + } + +#ifdef PYPY_VERSION + PyObject* obj = PyObject_CallObject((PyObject *) &PushNotificationType, NULL); +#else + PyObject* obj = PyType_GenericNew(&PushNotificationType, NULL, NULL); +#endif + if (obj == NULL) { + return NULL; + } + + int res = PyList_SetSlice(obj, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, PyList_New(size)); + + if (res == -1) { + Py_DECREF(obj); + return NULL; + } + + return obj; +} + redisReplyObjectFunctions hiredis_ObjectFunctions = { createStringObject, // void *(*createString)(const redisReadTask*, char*, size_t); createArrayObject, // void *(*createArray)(const redisReadTask*, size_t); diff --git a/src/reader.h b/src/reader.h index b09e7fa..c9359c1 100644 --- a/src/reader.h +++ b/src/reader.h @@ -23,7 +23,12 @@ typedef struct { } error; } hiredis_ReaderObject; +typedef struct { + PyListObject list; +} PushNotificationObject; + extern PyTypeObject hiredis_ReaderType; +extern PyTypeObject PushNotificationType; extern redisReplyObjectFunctions hiredis_ObjectFunctions; #endif diff --git a/tests/test_reader.py b/tests/test_reader.py index 3694084..5d6652e 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -185,9 +185,11 @@ def test_dict_with_unhashable_key(reader): with pytest.raises(TypeError): reader.gets() -def test_vector(reader): +def test_vector(reader): reader.feed(b">4\r\n+pubsub\r\n+message\r\n+channel\r\n+message\r\n") - assert [b"pubsub", b"message", b"channel", b"message"] == reader.gets() + result = reader.gets() + assert isinstance(result, hiredis.PushNotification) + assert [b"pubsub", b"message", b"channel", b"message"] == result def test_verbatim_string(reader): value = b"text" From f715de2e7c636fd1649e9e038aaba80b05379df2 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Thu, 22 May 2025 11:57:25 +0300 Subject: [PATCH 18/18] Version 3.2.0 --- hiredis/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hiredis/version.py b/hiredis/version.py index ff214f2..1173108 100644 --- a/hiredis/version.py +++ b/hiredis/version.py @@ -1 +1 @@ -__version__ = "3.2.0-dev" +__version__ = "3.2.0"