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}

Reply via email to