Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-mmh3 for openSUSE:Factory checked in at 2025-09-22 16:39:45 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-mmh3 (Old) and /work/SRC/openSUSE:Factory/.python-mmh3.new.27445 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-mmh3" Mon Sep 22 16:39:45 2025 rev:3 rq:1306364 version:5.2.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-mmh3/python-mmh3.changes 2025-07-06 17:19:09.894892688 +0200 +++ /work/SRC/openSUSE:Factory/.python-mmh3.new.27445/python-mmh3.changes 2025-09-22 16:40:35.979730462 +0200 @@ -1,0 +2,6 @@ +Sun Sep 21 19:36:43 UTC 2025 - Dirk Müller <[email protected]> + +- update to 5.2.0: + * Add support for Python 3.14, including 3.14t (no-GIL) wheels. + +------------------------------------------------------------------- Old: ---- mmh3-5.1.0.tar.gz New: ---- mmh3-5.2.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-mmh3.spec ++++++ --- /var/tmp/diff_new_pack.3l4Olj/_old 2025-09-22 16:40:36.579755672 +0200 +++ /var/tmp/diff_new_pack.3l4Olj/_new 2025-09-22 16:40:36.579755672 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-mmh3 # -# Copyright (c) 2025 SUSE LLC +# Copyright (c) 2025 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-mmh3 -Version: 5.1.0 +Version: 5.2.0 Release: 0 Summary: Python extension for MurmurHash (MurmurHash3) License: MIT ++++++ mmh3-5.1.0.tar.gz -> mmh3-5.2.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mmh3-5.1.0/.github/workflows/benchmark-base-hash.yml new/mmh3-5.2.0/.github/workflows/benchmark-base-hash.yml --- old/mmh3-5.1.0/.github/workflows/benchmark-base-hash.yml 2025-01-25 08:45:16.000000000 +0100 +++ new/mmh3-5.2.0/.github/workflows/benchmark-base-hash.yml 2025-07-29 08:53:09.000000000 +0200 @@ -11,7 +11,7 @@ permissions: contents: read packages: read - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 env: BENCHMARK_MAX_SIZE: 65536 steps: @@ -20,7 +20,7 @@ - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: "3.13" - name: Install dependencies run: | pip install --upgrade pip diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mmh3-5.1.0/.github/workflows/benchmark.yml new/mmh3-5.2.0/.github/workflows/benchmark.yml --- old/mmh3-5.1.0/.github/workflows/benchmark.yml 2025-01-25 08:45:16.000000000 +0100 +++ new/mmh3-5.2.0/.github/workflows/benchmark.yml 2025-07-29 08:53:09.000000000 +0200 @@ -11,7 +11,7 @@ permissions: contents: read packages: read - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 env: BENCHMARK_MAX_SIZE: 262144 steps: @@ -20,7 +20,7 @@ - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: "3.13" - name: Install dependencies run: | pip install --upgrade pip diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mmh3-5.1.0/.github/workflows/build.yml new/mmh3-5.2.0/.github/workflows/build.yml --- old/mmh3-5.1.0/.github/workflows/build.yml 2025-01-25 08:45:16.000000000 +0100 +++ new/mmh3-5.2.0/.github/workflows/build.yml 2025-07-29 08:53:09.000000000 +0200 @@ -24,8 +24,9 @@ strategy: matrix: - os: [macOS-14, windows-2022, ubuntu-22.04] - python-version: [3.9, "3.10", "3.11", "3.12"] + os: [macos-14, windows-2022, ubuntu-24.04] + python-version: + [3.9, "3.10", "3.11", "3.12", "3.13", "3.14-dev", "3.14t-dev"] runs-on: ${{ matrix.os }} steps: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mmh3-5.1.0/.github/workflows/superlinter.yml new/mmh3-5.2.0/.github/workflows/superlinter.yml --- old/mmh3-5.1.0/.github/workflows/superlinter.yml 2025-01-25 08:45:16.000000000 +0100 +++ new/mmh3-5.2.0/.github/workflows/superlinter.yml 2025-07-29 08:53:09.000000000 +0200 @@ -38,7 +38,7 @@ # Runs the Super-Linter action - name: Run Super-Linter - uses: super-linter/super-linter@v7 + uses: super-linter/super-linter@v8 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} LINTER_RULES_PATH: / diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mmh3-5.1.0/.github/workflows/wheels.yml new/mmh3-5.2.0/.github/workflows/wheels.yml --- old/mmh3-5.1.0/.github/workflows/wheels.yml 2025-01-25 08:45:16.000000000 +0100 +++ new/mmh3-5.2.0/.github/workflows/wheels.yml 2025-07-29 08:53:09.000000000 +0200 @@ -17,11 +17,11 @@ jobs: build_wheels: - name: Build wheels on ${{ matrix.os }}_${{ matrix.archs }} + name: Build wheel for ${{ matrix.platform }} ${{ matrix.archs }} ${{ matrix.build }} (runs on ${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-22.04] + os: [ubuntu-24.04] archs: [x86_64, i686, aarch64, ppc64le, s390x] build: [manylinux, musllinux] include: @@ -31,47 +31,76 @@ archs: x86 - os: windows-2022 archs: ARM64 - - os: macOS-13 + - os: macos-13 archs: x86_64 - - os: macOS-14 + - os: macos-14 archs: arm64 - - os: macOS-14 + - os: macos-14 archs: universal2 + - os: ubuntu-24.04 + platform: android + archs: x86_64 + build: android + - os: macos-14 + platform: android + archs: arm64_v8a + build: android + - os: macos-14 + platform: ios + archs: arm64_iphoneos + - os: macos-14 + platform: ios + archs: arm64_iphonesimulator + - os: macos-13 + platform: ios + archs: x86_64_iphonesimulator steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: "3.13" - name: Set up QEMU - if: runner.os == 'Linux' + if: runner.os == 'Linux' && matrix.platform != 'android' uses: docker/setup-qemu-action@v3 + # https://github.blog/changelog/2024-04-02-github-actions-hardware-accelerated-android-virtualization-now-available/ + - name: Set up KVM for Android emulation + if: runner.os == 'Linux' && matrix.platform == 'android' + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm - name: Build wheels - uses: pypa/[email protected] + uses: pypa/[email protected] with: output-dir: wheelhouse env: - CIBW_BUILD: "{cp39,cp310,cp311,cp312,cp313}-${{ matrix.build }}*" + CIBW_BUILD: "{cp39,cp310,cp311,cp312,cp313,cp314,cp314t}-${{ matrix.build }}*" + CIBW_PLATFORM: ${{ matrix.platform || 'auto' }} CIBW_ARCHS: ${{ matrix.archs }} CIBW_BUILD_FRONTEND: "build" CIBW_TEST_REQUIRES: "pytest" + CIBW_TEST_SOURCES_ANDROID: "./tests" + CIBW_TEST_SOURCES_IOS: "./tests" CIBW_TEST_COMMAND: "pytest {project}" - CIBW_TEST_SKIP: "*-win_arm64" + CIBW_TEST_COMMAND_ANDROID: "python -m pytest ./tests" + CIBW_TEST_COMMAND_IOS: "python -m pytest ./tests" + CIBW_TEST_SKIP: "*-win_arm64 *-android_arm64_v8a" - uses: actions/upload-artifact@v4 with: - name: Wheel-${{ matrix.os }}-${{ matrix.build }}${{ matrix.archs }} + name: Wheel-${{ matrix.os }}-${{ matrix.platform }}-${{ matrix.build }}${{ matrix.archs }} path: ./wheelhouse/*.whl build_sdist: name: Build a source distribution - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: "3.13" - name: Build sdist run: | python -m pip install --upgrade pip @@ -91,13 +120,13 @@ publish: if: ${{ inputs.pypi-repository == 'pypi' || inputs.pypi-repository == 'testpypi'}} name: "Upload to PyPI/Test PyPI" - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: [build_wheels, build_sdist] steps: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: "3.13" - name: Set up built items uses: actions/download-artifact@v4 with: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mmh3-5.1.0/CHANGELOG.md new/mmh3-5.2.0/CHANGELOG.md --- old/mmh3-5.1.0/CHANGELOG.md 2025-01-25 08:45:16.000000000 +0100 +++ new/mmh3-5.2.0/CHANGELOG.md 2025-07-29 08:53:09.000000000 +0200 @@ -10,6 +10,19 @@ [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html) since version 3.0.0. +## [5.2.0] - 2025-07-29 + +### Added + +- Add support for Python 3.14, including 3.14t (no-GIL) wheels. However, thread + safety for the no-GIL variant is not fully tested yet. Please report any + issues you encounter ([#134](https://github.com/hajimes/mmh3/pull/134), + [#136](https://github.com/hajimes/mmh3/pull/136)). +- Add support for Android (Python 3.13 only) and iOS (Python 3.13 and 3.14) wheels, + enabled by the major version update of + [cibuildwheel](https://github.com/pypa/cibuildwheel) + ([#135](https://github.com/hajimes/mmh3/pull/135)). + ## [5.1.0] - 2025-01-25 ### Added @@ -287,6 +300,7 @@ [Softpedia collected mmh3 1.0 on April 27, 2011](https://web.archive.org/web/20110430172027/https://linux.softpedia.com/get/Programming/Libraries/mmh3-68314.shtml), it must have been uploaded to PyPI on or slightly before this date. +[5.2.0]: https://github.com/hajimes/mmh3/compare/v5.1.0...v5.2.0 [5.1.0]: https://github.com/hajimes/mmh3/compare/v5.0.1...v5.1.0 [5.0.1]: https://github.com/hajimes/mmh3/compare/v5.0.0...v5.0.1 [5.0.0]: https://github.com/hajimes/mmh3/compare/v4.1.0...v5.0.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mmh3-5.1.0/README.md new/mmh3-5.2.0/README.md --- old/mmh3-5.1.0/README.md 2025-01-25 08:45:16.000000000 +0100 +++ new/mmh3-5.2.0/README.md 2025-07-29 08:53:09.000000000 +0200 @@ -78,8 +78,21 @@ ## Changelog -See [Changelog](https://mmh3.readthedocs.io/en/latest/changelog.html) -(latest version) for the complete changelog. +See [Changelog (latest version)](https://mmh3.readthedocs.io/en/latest/changelog.html) +for the complete changelog. + +### [5.2.0] - 2025-07-29 + +#### Added + +- Add support for Python 3.14, including 3.14t (no-GIL) wheels. However, thread + safety for the no-GIL variant is not fully tested yet. Please report any + issues you encounter ([#134](https://github.com/hajimes/mmh3/pull/134), + [#136](https://github.com/hajimes/mmh3/pull/136)). +- Add support for Android (Python 3.13 only) and iOS (Python 3.13 and 3.14) wheels, + enabled by the major version update of + [cibuildwheel](https://github.com/pypa/cibuildwheel) + ([#135](https://github.com/hajimes/mmh3/pull/135)). ### [5.1.0] - 2025-01-25 @@ -108,57 +121,6 @@ - Fix the issue that the package cannot be built from the source distribution ([#90](https://github.com/hajimes/mmh3/issues/90)). -### [5.0.0] - 2024-09-18 - -#### Added - -- Add support for Python 3.13. -- Improve the performance of the `hash()` function with - [METH_FASTCALL](https://docs.python.org/3/c-api/structures.html#c.METH_FASTCALL), - reducing the overhead of function calls. For data sizes between 1–2 KB - (e.g., 48x48 favicons), performance is 10%–20% faster. For smaller data - (~500 bytes, like 16x16 favicons), performance increases by approximately 30% - ([#87](https://github.com/hajimes/mmh3/pull/87)). -- Add `digest` functions that support the new buffer protocol - ([PEP 688](https://peps.python.org/pep-0688/)) as input - ([#75](https://github.com/hajimes/mmh3/pull/75)). - These functions are implemented with `METH_FASTCALL` too, offering improved - performance ([#84](https://github.com/hajimes/mmh3/pull/84)). -- Slightly improve the performance of the `hash_bytes()` function - ([#88](https://github.com/hajimes/mmh3/pull/88)) -- Add Read the Docs documentation - ([#54](https://github.com/hajimes/mmh3/issues/54)). -- Document benchmark results - ([#53](https://github.com/hajimes/mmh3/issues/53)). - -#### Changed - -- **Backward-incompatible**: The `seed` argument is now strictly validated to - ensure it falls within the range [0, 0xFFFFFFFF]. A `ValueError` is raised - if the seed is out of range ([#84](https://github.com/hajimes/mmh3/pull/84)). -- **Backward-incompatible**: Change the constructors of hasher classes to - accept a buffer as the first argument - ([#83](https://github.com/hajimes/mmh3/pull/83)). -- The type of flag argumens has been changed from `bool` to `Any` - ([#84](https://github.com/hajimes/mmh3/pull/84)). -- Change the format of CHANGELOG.md to conform to the - [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) standard - ([#63](https://github.com/hajimes/mmh3/pull/63)). - -#### Deprecated - -- Deprecate the `hash_from_buffer()` function. - Use `mmh3_32_sintdigest()` or `mmh3_32_uintdigest()` as alternatives - ([#84](https://github.com/hajimes/mmh3/pull/84)). - -#### Fixed - -- Fix a reference leak in the `hash_from_buffer()` function - ([#75](https://github.com/hajimes/mmh3/pull/75)). -- Fix type hints ([#76](https://github.com/hajimes/mmh3/pull/76), - [#77](https://github.com/hajimes/mmh3/pull/77), - [#84](https://github.com/hajimes/mmh3/pull/84)). - ## License [MIT](https://github.com/hajimes/mmh3/blob/master/LICENSE), unless otherwise @@ -266,8 +228,8 @@ ## How to Cite This Library -If you use this library in your research, it would be much appreciated it if -you would cite the following paper published in the +If you use this library in your research, it would be appreciated if you could +cite the following paper published in the [_Journal of Open Source Software_](https://joss.theoj.org): Hajime Senuma. 2025. @@ -305,6 +267,6 @@ - <https://github.com/ifduyue/python-xxhash>: Python bindings for xxHash (Yue Du) +[5.2.0]: https://github.com/hajimes/mmh3/compare/v5.1.0...v5.2.0 [5.1.0]: https://github.com/hajimes/mmh3/compare/v5.0.1...v5.1.0 [5.0.1]: https://github.com/hajimes/mmh3/compare/v5.0.0...v5.0.1 -[5.0.0]: https://github.com/hajimes/mmh3/compare/v4.1.0...v5.0.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mmh3-5.1.0/docs/CONTRIBUTING.md new/mmh3-5.2.0/docs/CONTRIBUTING.md --- old/mmh3-5.1.0/docs/CONTRIBUTING.md 2025-01-25 08:45:16.000000000 +0100 +++ new/mmh3-5.2.0/docs/CONTRIBUTING.md 2025-07-29 08:53:09.000000000 +0200 @@ -52,11 +52,12 @@ git clone https://github.com/hajimes/mmh3.git ``` -This project uses `tox` to automate testing and other tasks. You can install -`tox` by running: +This project uses `tox-uv` to automate testing and other tasks. You can install +`tox-uv` by running: ```shell -pipx install tox +pipx install uv +uv tool install tox --with tox-uv ``` In addition, `npx` (included with `npm` >= 5.2.0) is required within the `tox` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mmh3-5.1.0/docs/api.md new/mmh3-5.2.0/docs/api.md --- old/mmh3-5.1.0/docs/api.md 2025-01-25 08:45:16.000000000 +0100 +++ new/mmh3-5.2.0/docs/api.md 2025-07-29 08:53:09.000000000 +0200 @@ -27,11 +27,15 @@ Note that **`mmh3` is endian-neutral**, while the original C++ library is endian-sensitive (see also -[Known Issues](https://github.com/hajimes/mmh3#known-issues)). +[Frequently Asked Questions](https://github.com/hajimes/mmh3#frequently-asked-questions)). This feature of `mmh3` is essential when portability across different architectures is required, such as when calculating hash footprints for web services. +Support for no-GIL mode (officially introduced in Python 3.14) was added in +version 5.2.0. However, thread safety under the no-GIL variant has not been +fully tested. Please report any issues you encounter. + ## Basic Hash Functions The following functions are used to hash immutable types, specifically diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mmh3-5.1.0/docs/benchmark.md new/mmh3-5.2.0/docs/benchmark.md --- old/mmh3-5.1.0/docs/benchmark.md 2025-01-25 08:45:16.000000000 +0100 +++ new/mmh3-5.2.0/docs/benchmark.md 2025-07-29 08:53:09.000000000 +0200 @@ -50,7 +50,7 @@ derived from the Fibonacci sequence. - For each input size, the test generates a set of 10 `bytes` instances, where each instance's size is pseudo-randomly selected from the range - [ceil(input * 0.9), floor(input * 1.1)]. + `[ceil(input * 0.9), floor(input * 1.1)]`. - This randomization is crucial as it increases the difficulty of branch predictions, creating a more realistic scenario. For further details, see [xxHash: Performance comparison](https://github.com/Cyan4973/xxHash/wiki/Performance-comparison#throughput-on-small-data-of-random-length-1-n). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mmh3-5.1.0/pyproject.toml new/mmh3-5.2.0/pyproject.toml --- old/mmh3-5.1.0/pyproject.toml 2025-01-25 08:45:16.000000000 +0100 +++ new/mmh3-5.2.0/pyproject.toml 2025-07-29 08:53:09.000000000 +0200 @@ -5,7 +5,7 @@ [project] name = "mmh3" -version = "5.1.0" +version = "5.2.0" description = "Python extension for MurmurHash (MurmurHash3), a set of fast and robust hash functions." readme = "README.md" license = {file = "LICENSE"} @@ -24,38 +24,40 @@ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: Free Threading :: 2 - Beta", "Topic :: Software Development :: Libraries", "Topic :: Utilities" ] [project.optional-dependencies] test = [ - "pytest == 8.3.4", + "pytest == 8.4.1", "pytest-sugar == 1.0.0" ] lint = [ - "black == 24.10.0", - "clang-format == 19.1.7", - "isort == 5.13.2", - "pylint == 3.3.3" + "black == 25.1.0", + "clang-format == 20.1.8", + "isort == 6.0.1", + "pylint == 3.3.7" ] type = [ - "mypy == 1.14.1" + "mypy == 1.17.0" ] docs = [ - "myst-parser == 4.0.0", - "shibuya == 2024.12.21", - "sphinx == 8.1.3", + "myst-parser == 4.0.1", + "shibuya == 2025.7.24", + "sphinx == 8.2.3", "sphinx-copybutton == 0.5.2" ] benchmark = [ "pymmh3 == 0.0.5", - "pyperf == 2.8.1", + "pyperf == 2.9.0", "xxhash == 3.5.0" ] plot = [ - "matplotlib == 3.10.0", - "pandas == 2.2.3" + "matplotlib == 3.10.3", + "pandas == 2.3.1" ] [project.urls] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mmh3-5.1.0/src/mmh3/mmh3module.c new/mmh3-5.2.0/src/mmh3/mmh3module.c --- old/mmh3-5.1.0/src/mmh3/mmh3module.c 2025-01-25 08:45:16.000000000 +0100 +++ new/mmh3-5.2.0/src/mmh3/mmh3module.c 2025-07-29 08:53:09.000000000 +0200 @@ -132,6 +132,22 @@ } //----------------------------------------------------------------------------- +// Helpers for mutex manipulations for hashers + +#ifdef Py_GIL_DISABLED +#define MMH3_HASHER_LOCK(obj) PyMutex_Lock(&(obj->mutex)) +#define MMH3_HASHER_UNLOCK(obj) PyMutex_Unlock(&(obj->mutex)) +#define MMH3_HASHER_INIT_MUTEX(obj) \ + PyMutex t = {0}; \ + obj->mutex = t; + +#else +#define MMH3_HASHER_LOCK(obj) (void)0 +#define MMH3_HASHER_UNLOCK(obj) (void)0 +#define MMH3_HASHER_INIT_MUTEX(obj) (void)0 +#endif + +//----------------------------------------------------------------------------- // One shot functions PyDoc_STRVAR( @@ -263,7 +279,7 @@ "memory-views such as numpy arrays.\n" "\n" "Args:\n" - " key (Buffer | str): The bufer to hash. String inputs are also\n" + " key (Buffer | str): The buffer to hash. String inputs are also\n" " supported and are automatically converted to `bytes` using\n" " UTF-8 encoding before hashing.\n" " seed (int): The seed value. Must be an integer in the range\n" @@ -1283,6 +1299,9 @@ uint64_t buffer; uint8_t shift; Py_ssize_t length; +#ifdef Py_GIL_DISABLED + PyMutex mutex; +#endif } MMH3Hasher32; static PyTypeObject MMH3Hasher32Type; @@ -1291,11 +1310,14 @@ update32_impl(MMH3Hasher32 *self, Py_buffer *buf) { Py_ssize_t i = 0; - uint32_t h1 = self->h; + uint32_t h1 = 0; uint32_t k1 = 0; const uint32_t c1 = 0xe6546b64; const uint64_t mask = 0xffffffffUL; + MMH3_HASHER_LOCK(self); + h1 = self->h; + for (; i + 4 <= buf->len; i += 4) { k1 = getblock32(buf->buf, i / 4); self->buffer |= (k1 & mask) << self->shift; @@ -1320,10 +1342,12 @@ } } - PyBuffer_Release(buf); - self->h = h1; + MMH3_HASHER_UNLOCK(self); + + PyBuffer_Release(buf); + return; } @@ -1343,12 +1367,13 @@ self->buffer = 0; self->shift = 0; self->length = 0; + MMH3_HASHER_INIT_MUTEX(self); } return (PyObject *)self; } /* It is impossible to add docstring for __init__ in Python C extension. - Therefore, the contsructor docstring should be described in the class + Therefore, the constructor docstring should be described in the class docstring. See also https://stackoverflow.com/q/11913492 */ static int MMH3Hasher32_init(MMH3Hasher32 *self, PyObject *args, PyObject *kwds) @@ -1415,7 +1440,10 @@ static PyObject * MMH3Hasher32_digest(MMH3Hasher32 *self, PyObject *Py_UNUSED(ignored)) { + MMH3_HASHER_LOCK(self); uint32_t h = digest32_impl(self->h, self->buffer, self->length); + MMH3_HASHER_UNLOCK(self); + char out[MMH3_32_DIGESTSIZE]; #if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) @@ -1438,7 +1466,9 @@ static PyObject * MMH3Hasher32_sintdigest(MMH3Hasher32 *self, PyObject *Py_UNUSED(ignored)) { + MMH3_HASHER_LOCK(self); uint32_t h = digest32_impl(self->h, self->buffer, self->length); + MMH3_HASHER_UNLOCK(self); // Note that simple casting ("(int32_t) h") is an undefined behavior int32_t result = *(int32_t *)&h; @@ -1457,7 +1487,10 @@ static PyObject * MMH3Hasher32_uintdigest(MMH3Hasher32 *self, PyObject *Py_UNUSED(ignored)) { + MMH3_HASHER_LOCK(self); uint32_t h = digest32_impl(self->h, self->buffer, self->length); + MMH3_HASHER_UNLOCK(self); + return PyLong_FromUnsignedLong(h); } @@ -1478,10 +1511,13 @@ return NULL; } + MMH3_HASHER_LOCK(self); p->h = self->h; p->buffer = self->buffer; p->shift = self->shift; p->length = self->length; + MMH3_HASHER_INIT_MUTEX(p); + MMH3_HASHER_UNLOCK(self); return (PyObject *)p; } @@ -1543,6 +1579,9 @@ " seed (int): The seed value. Must be an integer in the range\n" " [0, 0xFFFFFFFF].\n" "\n" + ".. versionchanged:: 5.2.0\n" + " Experimental no-GIL support; thread safety not fully verified.\n" + "\n" ".. versionchanged:: 5.0.0\n" " Added the optional ``data`` parameter as the first argument.\n" " The ``seed`` argument is now strictly checked for valid range.\n"); @@ -1569,6 +1608,9 @@ uint64_t buffer2; uint8_t shift; Py_ssize_t length; +#ifdef Py_GIL_DISABLED + PyMutex mutex; +#endif } MMH3Hasher128x64; static PyTypeObject MMH3Hasher128x64Type; @@ -1577,11 +1619,15 @@ update_x64_128_impl(MMH3Hasher128x64 *self, Py_buffer *buf) { Py_ssize_t i = 0; - uint64_t h1 = self->h1; - uint64_t h2 = self->h2; + uint64_t h1 = 0; + uint64_t h2 = 0; uint64_t k1 = 0; uint64_t k2 = 0; + MMH3_HASHER_LOCK(self); + h1 = self->h1; + h2 = self->h2; + for (; i + 16 <= buf->len; i += 16) { k1 = getblock64(buf->buf, (i / 16) * 2); k2 = getblock64(buf->buf, (i / 16) * 2 + 1); @@ -1649,10 +1695,11 @@ } } - PyBuffer_Release(buf); - self->h1 = h1; self->h2 = h2; + MMH3_HASHER_UNLOCK(self); + + PyBuffer_Release(buf); } static void @@ -1673,6 +1720,7 @@ self->buffer2 = 0; self->shift = 0; self->length = 0; + MMH3_HASHER_INIT_MUTEX(self); } return (PyObject *)self; } @@ -1718,8 +1766,10 @@ MMH3Hasher128x64_digest(MMH3Hasher128x64 *self, PyObject *Py_UNUSED(ignored)) { const char out[MMH3_128_DIGESTSIZE]; + MMH3_HASHER_LOCK(self); digest_x64_128_impl(self->h1, self->h2, self->buffer1, self->buffer2, self->length, out); + MMH3_HASHER_UNLOCK(self); return PyBytes_FromStringAndSize(out, MMH3_128_DIGESTSIZE); } @@ -1728,8 +1778,10 @@ PyObject *Py_UNUSED(ignored)) { const char out[MMH3_128_DIGESTSIZE]; + MMH3_HASHER_LOCK(self); digest_x64_128_impl(self->h1, self->h2, self->buffer1, self->buffer2, self->length, out); + MMH3_HASHER_UNLOCK(self); const int little_endian = 1; const int is_signed = 1; @@ -1750,8 +1802,10 @@ PyObject *Py_UNUSED(ignored)) { const char out[MMH3_128_DIGESTSIZE]; + MMH3_HASHER_LOCK(self); digest_x64_128_impl(self->h1, self->h2, self->buffer1, self->buffer2, self->length, out); + MMH3_HASHER_UNLOCK(self); const int little_endian = 1; const int is_signed = 0; @@ -1781,8 +1835,10 @@ PyObject *Py_UNUSED(ignored)) { const char out[MMH3_128_DIGESTSIZE]; + MMH3_HASHER_LOCK(self); digest_x64_128_impl(self->h1, self->h2, self->buffer1, self->buffer2, self->length, out); + MMH3_HASHER_UNLOCK(self); const char *valflag = "LL"; uint64_t result1 = ((uint64_t *)out)[0]; @@ -1811,8 +1867,10 @@ PyObject *Py_UNUSED(ignored)) { const char out[MMH3_128_DIGESTSIZE]; + MMH3_HASHER_LOCK(self); digest_x64_128_impl(self->h1, self->h2, self->buffer1, self->buffer2, self->length, out); + MMH3_HASHER_UNLOCK(self); const char *valflag = "KK"; uint64_t result1 = ((uint64_t *)out)[0]; @@ -1843,12 +1901,15 @@ return NULL; } + MMH3_HASHER_LOCK(self); p->h1 = self->h1; p->h2 = self->h2; p->buffer1 = self->buffer1; p->buffer2 = self->buffer2; p->shift = self->shift; p->length = self->length; + MMH3_HASHER_INIT_MUTEX(p); + MMH3_HASHER_UNLOCK(self); return (PyObject *)p; } @@ -1910,6 +1971,9 @@ " seed (int): The seed value. Must be an integer in the range\n" " [0, 0xFFFFFFFF].\n" "\n" + ".. versionchanged:: 5.2.0\n" + " Experimental no-GIL support; thread safety not fully verified.\n" + "\n" ".. versionchanged:: 5.0.0\n" " Added the optional ``data`` parameter as the first argument.\n" " The ``seed`` argument is now strictly checked for valid range.\n"); @@ -1940,6 +2004,9 @@ uint32_t buffer4; uint8_t shift; Py_ssize_t length; +#ifdef Py_GIL_DISABLED + PyMutex mutex; +#endif } MMH3Hasher128x86; static PyTypeObject MMH3Hasher128x86Type; @@ -1948,12 +2015,18 @@ update_x86_128_impl(MMH3Hasher128x86 *self, Py_buffer *buf) { Py_ssize_t i = 0; - uint32_t h1 = self->h1; - uint32_t h2 = self->h2; - uint32_t h3 = self->h3; - uint32_t h4 = self->h4; + uint32_t h1 = 0; + uint32_t h2 = 0; + uint32_t h3 = 0; + uint32_t h4 = 0; uint32_t k1 = 0; + MMH3_HASHER_LOCK(self); + h1 = self->h1; + h2 = self->h2; + h3 = self->h3; + h4 = self->h4; + for (; i < buf->len; i++) { k1 = ((uint8_t *)buf->buf)[i]; if (self->shift < 32) { // TODO: use bit ops @@ -1997,12 +2070,13 @@ } } - PyBuffer_Release(buf); - self->h1 = h1; self->h2 = h2; self->h3 = h3; self->h4 = h4; + MMH3_HASHER_UNLOCK(self); + + PyBuffer_Release(buf); } static void @@ -2027,6 +2101,7 @@ self->buffer4 = 0; self->shift = 0; self->length = 0; + MMH3_HASHER_INIT_MUTEX(self); } return (PyObject *)self; } @@ -2073,9 +2148,11 @@ MMH3Hasher128x86_digest(MMH3Hasher128x86 *self, PyObject *Py_UNUSED(ignored)) { char out[MMH3_128_DIGESTSIZE]; + MMH3_HASHER_LOCK(self); digest_x86_128_impl(self->h1, self->h2, self->h3, self->h4, self->buffer1, self->buffer2, self->buffer3, self->buffer4, self->length, out); + MMH3_HASHER_UNLOCK(self); return PyBytes_FromStringAndSize(out, MMH3_128_DIGESTSIZE); } @@ -2084,9 +2161,11 @@ PyObject *Py_UNUSED(ignored)) { const char out[MMH3_128_DIGESTSIZE]; + MMH3_HASHER_LOCK(self); digest_x86_128_impl(self->h1, self->h2, self->h3, self->h4, self->buffer1, self->buffer2, self->buffer3, self->buffer4, self->length, out); + MMH3_HASHER_UNLOCK(self); const int little_endian = 1; const int is_signed = 1; @@ -2107,9 +2186,11 @@ PyObject *Py_UNUSED(ignored)) { const char out[MMH3_128_DIGESTSIZE]; + MMH3_HASHER_LOCK(self); digest_x86_128_impl(self->h1, self->h2, self->h3, self->h4, self->buffer1, self->buffer2, self->buffer3, self->buffer4, self->length, out); + MMH3_HASHER_UNLOCK(self); const int little_endian = 1; const int is_signed = 0; @@ -2130,9 +2211,11 @@ PyObject *Py_UNUSED(ignored)) { const char out[MMH3_128_DIGESTSIZE]; + MMH3_HASHER_LOCK(self); digest_x86_128_impl(self->h1, self->h2, self->h3, self->h4, self->buffer1, self->buffer2, self->buffer3, self->buffer4, self->length, out); + MMH3_HASHER_UNLOCK(self); const char *valflag = "LL"; uint64_t result1 = ((uint64_t *)out)[0]; @@ -2151,9 +2234,11 @@ PyObject *Py_UNUSED(ignored)) { const char out[MMH3_128_DIGESTSIZE]; + MMH3_HASHER_LOCK(self); digest_x86_128_impl(self->h1, self->h2, self->h3, self->h4, self->buffer1, self->buffer2, self->buffer3, self->buffer4, self->length, out); + MMH3_HASHER_UNLOCK(self); const char *valflag = "KK"; uint64_t result1 = ((uint64_t *)out)[0]; @@ -2184,6 +2269,7 @@ return NULL; } + MMH3_HASHER_LOCK(self); p->h1 = self->h1; p->h2 = self->h2; p->h3 = self->h3; @@ -2194,6 +2280,8 @@ p->buffer4 = self->buffer4; p->shift = self->shift; p->length = self->length; + MMH3_HASHER_INIT_MUTEX(p); + MMH3_HASHER_UNLOCK(self); return (PyObject *)p; } @@ -2255,6 +2343,9 @@ " seed (int): The seed value. Must be an integer in the range " "[0, 0xFFFFFFFF].\n" "\n" + ".. versionchanged:: 5.2.0\n" + " Experimental no-GIL support; thread safety not fully verified.\n" + "\n" ".. versionchanged:: 5.0.0\n" " Added the optional ``data`` parameter as the first argument.\n" " The ``seed`` argument is now strictly checked for valid range.\n"); @@ -2274,6 +2365,7 @@ //----------------------------------------------------------------------------- // Module + static struct PyModuleDef mmh3module = { PyModuleDef_HEAD_INIT, "mmh3", @@ -2314,6 +2406,10 @@ if (module == NULL) return NULL; +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED); +#endif + Py_INCREF(&MMH3Hasher32Type); if (PyModule_AddObject(module, "mmh3_32", (PyObject *)&MMH3Hasher32Type) < 0) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mmh3-5.1.0/tests/helper.py new/mmh3-5.2.0/tests/helper.py --- old/mmh3-5.1.0/tests/helper.py 2025-01-25 08:45:16.000000000 +0100 +++ new/mmh3-5.2.0/tests/helper.py 2025-07-29 08:53:09.000000000 +0200 @@ -1,4 +1,4 @@ -""" Helper functions for tests. """ +"""Helper functions for tests.""" # see also https://stackoverflow.com/a/1375939 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mmh3-5.1.0/tests/test_free_threading.py new/mmh3-5.2.0/tests/test_free_threading.py --- old/mmh3-5.1.0/tests/test_free_threading.py 1970-01-01 01:00:00.000000000 +0100 +++ new/mmh3-5.2.0/tests/test_free_threading.py 2025-07-29 08:53:09.000000000 +0200 @@ -0,0 +1,51 @@ +# pylint: disable=missing-module-docstring,missing-function-docstring +from collections.abc import Callable +from concurrent.futures import ThreadPoolExecutor +from typing import Any + +import mmh3 + + +def run_threaded(func: Callable[..., Any], num_threads: int = 8) -> None: + with ThreadPoolExecutor(max_workers=num_threads) as executor: + futures = [executor.submit(func) for _ in range(num_threads)] + for future in futures: + future.result() # wait for all threads to complete + + +def test_parallel_hasher_mmh3_32_update() -> None: + hasher = mmh3.mmh3_32() + + def closure() -> None: + for _ in range(1000): + hasher.update(b"foo") + + run_threaded(closure, num_threads=8) + + assert hasher.sintdigest() == mmh3.hash(b"foo" * 8000) + + +def test_parallel_hasher_mmh3_x64_128_update() -> None: + hasher = mmh3.mmh3_x64_128() + + def closure() -> None: + for _ in range(1000): + hasher.update(b"foo") + + run_threaded(closure, num_threads=8) + + assert hasher.sintdigest() == mmh3.hash128(b"foo" * 8000, x64arch=True, signed=True) + + +def test_parallel_hasher_mmh3_x86_128_update() -> None: + hasher = mmh3.mmh3_x86_128() + + def closure() -> None: + for _ in range(1000): + hasher.update(b"foo") + + run_threaded(closure, num_threads=8) + + assert hasher.sintdigest() == mmh3.hash128( + b"foo" * 8000, x64arch=False, signed=True + ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mmh3-5.1.0/tests/test_mmh3_hasher.py new/mmh3-5.2.0/tests/test_mmh3_hasher.py --- old/mmh3-5.1.0/tests/test_mmh3_hasher.py 2025-01-25 08:45:16.000000000 +0100 +++ new/mmh3-5.2.0/tests/test_mmh3_hasher.py 2025-07-29 08:53:09.000000000 +0200 @@ -13,25 +13,25 @@ # https://stackoverflow.com/a/31929528 hasher = mmh3.mmh3_32(seed=0x9747B28C) hasher.update(b"Hello, world!") - assert hasher.digest() == b"\xBA\x4C\x88\x24" + assert hasher.digest() == b"\xba\x4c\x88\x24" hasher = mmh3.mmh3_32(seed=0x9747B28C) hasher.update(b"Hello,") hasher.update(b" world!") - assert hasher.digest() == b"\xBA\x4C\x88\x24" + assert hasher.digest() == b"\xba\x4c\x88\x24" hasher = mmh3.mmh3_32(b"", 0x9747B28C) hasher.update(b"Hello,") hasher.update(b" world!") - assert hasher.digest() == b"\xBA\x4C\x88\x24" + assert hasher.digest() == b"\xba\x4c\x88\x24" hasher = mmh3.mmh3_32(b"Hello,", 0x9747B28C) hasher.update(b" world!") - assert hasher.digest() == b"\xBA\x4C\x88\x24" + assert hasher.digest() == b"\xba\x4c\x88\x24" hasher = mmh3.mmh3_32(b"Hello,", seed=0x9747B28C) hasher.update(b" world!") - assert hasher.digest() == b"\xBA\x4C\x88\x24" + assert hasher.digest() == b"\xba\x4c\x88\x24" def test_mmh3_32_sintdigest() -> None: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mmh3-5.1.0/tox.ini new/mmh3-5.2.0/tox.ini --- old/mmh3-5.1.0/tox.ini 2025-01-25 08:45:16.000000000 +0100 +++ new/mmh3-5.2.0/tox.ini 2025-07-29 08:53:09.000000000 +0200 @@ -1,12 +1,12 @@ [tox] requires = tox>=4 -envlist = lint, type, py{38,39,310,311,312} +envlist = lint, type, py{39,310,311,312,313,314,314t} [testenv] description = run unit tests commands_pre = - pip install ".[test]" + uv pip install ".[test]" commands = pytest {posargs} @@ -17,7 +17,7 @@ find npx commands_pre = - pip install ".[lint]" + uv pip install ".[lint]" commands = black . isort . @@ -30,7 +30,7 @@ [testenv:type] description = run type checks commands_pre = - pip install ".[test,type]" + uv pip install ".[test,type]" commands = mypy --strict tests @@ -39,7 +39,7 @@ allowlist_externals = make commands_pre = - pip install ".[docs]" + uv pip install ".[docs]" commands = make -C docs clean make -C docs html @@ -49,7 +49,7 @@ find git commands_pre = - pip install ".[lint]" + uv pip install ".[lint]" commands = git submodule update --init python util/refresh.py @@ -58,13 +58,13 @@ [testenv:benchmark] description = run benchmarks commands_pre = - pip install ".[benchmark]" + uv pip install ".[benchmark]" commands = python benchmark/benchmark.py {posargs} [testenv:plot] description = plot benchmark results commands_pre = - pip install ".[benchmark,plot]" + uv pip install ".[benchmark,plot]" commands = python benchmark/plot_graph.py {posargs}
