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 2026-03-12 22:21:48
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-mmh3 (Old)
 and      /work/SRC/openSUSE:Factory/.python-mmh3.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-mmh3"

Thu Mar 12 22:21:48 2026 rev:4 rq:1338414 version:5.2.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-mmh3/python-mmh3.changes  2025-09-22 
16:40:35.979730462 +0200
+++ /work/SRC/openSUSE:Factory/.python-mmh3.new.8177/python-mmh3.changes        
2026-03-12 22:26:30.836722954 +0100
@@ -1,0 +2,6 @@
+Thu Mar 12 07:51:07 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 5.2.1:
+  * Add support for the Android wheel for Python 3.14.
+
+-------------------------------------------------------------------

Old:
----
  mmh3-5.2.0.tar.gz

New:
----
  mmh3-5.2.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-mmh3.spec ++++++
--- /var/tmp/diff_new_pack.3s00M9/_old  2026-03-12 22:26:31.484750120 +0100
+++ /var/tmp/diff_new_pack.3s00M9/_new  2026-03-12 22:26:31.492750455 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-mmh3
 #
-# Copyright (c) 2025 SUSE LLC and contributors
+# Copyright (c) 2026 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.2.0
+Version:        5.2.1
 Release:        0
 Summary:        Python extension for MurmurHash (MurmurHash3)
 License:        MIT

++++++ mmh3-5.2.0.tar.gz -> mmh3-5.2.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/.github/actionlint.yml 
new/mmh3-5.2.1/.github/actionlint.yml
--- old/mmh3-5.2.0/.github/actionlint.yml       1970-01-01 01:00:00.000000000 
+0100
+++ new/mmh3-5.2.1/.github/actionlint.yml       2026-03-05 16:11:36.000000000 
+0100
@@ -0,0 +1,6 @@
+# As of March 5, 2026, actionlint via super-linter 8.5.0 does not support 
macOS 26, so we ignore the runner-label warning for now.
+paths:
+  .github/workflows/**/*.{yml,yaml}:
+    ignore:
+      - 'label "macos-26" is unknown.+'
+      - 'label "macos-26-intel" is unknown.+'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/.github/dependabot.yml 
new/mmh3-5.2.1/.github/dependabot.yml
--- old/mmh3-5.2.0/.github/dependabot.yml       2025-07-29 08:53:09.000000000 
+0200
+++ new/mmh3-5.2.1/.github/dependabot.yml       2026-03-05 16:11:36.000000000 
+0100
@@ -4,15 +4,23 @@
     directory: "/"
     schedule:
       interval: "weekly"
+      day: "monday"
+    open-pull-requests-limit: 5
     groups:
       dependencies:
         patterns:
           - "*"
+    cooldown:
+      default-days: 7
   - package-ecosystem: "pip"
     directory: "/"
     schedule:
       interval: "weekly"
+      day: "monday"
+    open-pull-requests-limit: 5
     groups:
       dependencies:
         patterns:
           - "*"
+    cooldown:
+      default-days: 7
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/.github/workflows/benchmark-base-hash.yml 
new/mmh3-5.2.1/.github/workflows/benchmark-base-hash.yml
--- old/mmh3-5.2.0/.github/workflows/benchmark-base-hash.yml    2025-07-29 
08:53:09.000000000 +0200
+++ new/mmh3-5.2.1/.github/workflows/benchmark-base-hash.yml    2026-03-05 
16:11:36.000000000 +0100
@@ -16,11 +16,13 @@
       BENCHMARK_MAX_SIZE: 65536
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 
v6.0.2
+        with:
+          persist-credentials: false
       - name: Set up Python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # 
v6.2.0
         with:
-          python-version: "3.13"
+          python-version: "3.14"
       - name: Install dependencies
         run: |
           pip install --upgrade pip
@@ -85,7 +87,7 @@
           sudo systemctl restart irqbalance
           systemctl status irqbalance
       - name: Upload artifacts
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f 
# v7.0.0
         with:
           name: benchmark-results
           path: var
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/.github/workflows/benchmark.yml 
new/mmh3-5.2.1/.github/workflows/benchmark.yml
--- old/mmh3-5.2.0/.github/workflows/benchmark.yml      2025-07-29 
08:53:09.000000000 +0200
+++ new/mmh3-5.2.1/.github/workflows/benchmark.yml      2026-03-05 
16:11:36.000000000 +0100
@@ -16,11 +16,13 @@
       BENCHMARK_MAX_SIZE: 262144
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 
v6.0.2
+        with:
+          persist-credentials: false
       - name: Set up Python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # 
v6.2.0
         with:
-          python-version: "3.13"
+          python-version: "3.14"
       - name: Install dependencies
         run: |
           pip install --upgrade pip
@@ -80,7 +82,7 @@
           sudo systemctl restart irqbalance
           systemctl status irqbalance
       - name: Upload artifacts
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f 
# v7.0.0
         with:
           name: benchmark-results
           path: var
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/.github/workflows/build.yml 
new/mmh3-5.2.1/.github/workflows/build.yml
--- old/mmh3-5.2.0/.github/workflows/build.yml  2025-07-29 08:53:09.000000000 
+0200
+++ new/mmh3-5.2.1/.github/workflows/build.yml  2026-03-05 16:11:36.000000000 
+0100
@@ -5,9 +5,7 @@
 
 on: # yamllint disable-line rule:truthy
   push:
-    branches:
-      - master
-      - feature/**
+    branches: "**"
   pull_request:
     types:
       - opened
@@ -24,15 +22,17 @@
 
     strategy:
       matrix:
-        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"]
+        os: [macos-26, windows-2025, ubuntu-24.04]
+        python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"]
 
     runs-on: ${{ matrix.os }}
     steps:
-      - uses: actions/checkout@v4
+      - name: Checkout
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 
v6.0.2
+        with:
+          persist-credentials: false
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # 
v6.2.0
         with:
           python-version: ${{ matrix.python-version }}
       - name: Install dependencies
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/.github/workflows/draft-pdf.yml 
new/mmh3-5.2.1/.github/workflows/draft-pdf.yml
--- old/mmh3-5.2.0/.github/workflows/draft-pdf.yml      2025-07-29 
08:53:09.000000000 +0200
+++ new/mmh3-5.2.1/.github/workflows/draft-pdf.yml      2026-03-05 
16:11:36.000000000 +0100
@@ -4,8 +4,8 @@
 on:
   push:
     branches:
-      - master
       - paper
+  workflow_dispatch:
 
 permissions: {}
 
@@ -18,17 +18,20 @@
     runs-on: ubuntu-latest
 
     name: Paper Draft
+    if: github.event_name == 'workflow_dispatch' && github.ref == 
'refs/heads/master'
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 
v6.0.2
+        with:
+          persist-credentials: false
       - name: Build draft PDF
-        uses: openjournals/openjournals-draft-action@master
+        uses: 
openjournals/openjournals-draft-action@85a18372e48f551d8af9ddb7a747de685fbbb01c 
# v1.0
         with:
           journal: joss
           # This should be the path to the paper within your repo.
           paper-path: paper/paper.md
       - name: Upload
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f 
# v7.0.0
         with:
           name: paper
           # This is the output path where Pandoc will write the compiled
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/.github/workflows/superlinter.yml 
new/mmh3-5.2.1/.github/workflows/superlinter.yml
--- old/mmh3-5.2.0/.github/workflows/superlinter.yml    2025-07-29 
08:53:09.000000000 +0200
+++ new/mmh3-5.2.1/.github/workflows/superlinter.yml    2026-03-05 
16:11:36.000000000 +0100
@@ -30,24 +30,31 @@
     steps:
       # Checks out a copy of your repository on the ubuntu-latest machine
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 
v6.0.2
         with:
+          persist-credentials: false
           # super-linter needs the full git history to get the
           # list of files that changed across commits
           fetch-depth: 0
 
       # Runs the Super-Linter action
       - name: Run Super-Linter
-        uses: super-linter/super-linter@v8
+        uses: 
super-linter/super-linter@61abc07d755095a68f4987d1c2c3d1d64408f1f9 # v8.5.0
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           LINTER_RULES_PATH: /
-          PYTHON_ISORT_CONFIG_FILE: pyproject.toml
+          GITHUB_ACTIONS_CONFIG_FILE: .github/actionlint.yml
           PYTHON_PYLINT_CONFIG_FILE: pyproject.toml
+          PYTHON_RUFF_CONFIG_FILE: pyproject.toml
+          PYTHON_RUFF_FORMAT_CONFIG_FILE: pyproject.toml
           # Suppressed because it conflicts with clang-format in some cases
           VALIDATE_CPP: false
           # Suppressed because copy/paste is sometimes required at low level
           VALIDATE_JSCPD: false
+          # Suppressed in favor of Ruff
+          VALIDATE_PYTHON_BLACK: false
+          VALIDATE_PYTHON_FLAKE8: false
+          VALIDATE_PYTHON_ISORT: false
           # Suppressed because it even accuses book titles
           VALIDATE_NATURAL_LANGUAGE: false
           # Suppressed because it does not honor the ignore-paths option
@@ -55,9 +62,9 @@
       # super-linter 7 does not honor the ignore-paths option of pylint
       # so we run pylint separately
       - name: Set up Python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # 
v6.2.0
         with:
-          python-version: 3.12
+          python-version: "3.14"
       - name: Run pylint
         run: |
           pip install pylint
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/.github/workflows/test-wheels.yml 
new/mmh3-5.2.1/.github/workflows/test-wheels.yml
--- old/mmh3-5.2.0/.github/workflows/test-wheels.yml    2025-07-29 
08:53:09.000000000 +0200
+++ new/mmh3-5.2.1/.github/workflows/test-wheels.yml    1970-01-01 
01:00:00.000000000 +0100
@@ -1,14 +0,0 @@
----
-name: Build and Test Wheels
-
-on:
-  workflow_dispatch:
-
-permissions: {}
-
-jobs:
-  publish:
-    permissions:
-      contents: read
-      packages: read
-    uses: ./.github/workflows/wheels.yml
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/.github/workflows/upload-pypi.yml 
new/mmh3-5.2.1/.github/workflows/upload-pypi.yml
--- old/mmh3-5.2.0/.github/workflows/upload-pypi.yml    2025-07-29 
08:53:09.000000000 +0200
+++ new/mmh3-5.2.1/.github/workflows/upload-pypi.yml    1970-01-01 
01:00:00.000000000 +0100
@@ -1,20 +0,0 @@
----
-name: Build and Upload Wheels to PyPI
-
-on:
-  workflow_dispatch:
-
-permissions: {}
-
-jobs:
-  publish:
-    if: github.ref_type == 'tag'
-    permissions:
-      contents: read
-      packages: read
-    uses: ./.github/workflows/wheels.yml
-    with:
-      pypi-repository: "pypi"
-    secrets:
-      pypi-username: ${{ secrets.PYPI_USERNAME }}
-      pypi-password: ${{ secrets.PYPI_PASSWORD }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/.github/workflows/upload-testpypi.yml 
new/mmh3-5.2.1/.github/workflows/upload-testpypi.yml
--- old/mmh3-5.2.0/.github/workflows/upload-testpypi.yml        2025-07-29 
08:53:09.000000000 +0200
+++ new/mmh3-5.2.1/.github/workflows/upload-testpypi.yml        1970-01-01 
01:00:00.000000000 +0100
@@ -1,19 +0,0 @@
----
-name: Build and Upload Wheels to TestPyPI
-
-on:
-  workflow_dispatch:
-
-permissions: {}
-
-jobs:
-  publish:
-    permissions:
-      contents: read
-      packages: read
-    uses: ./.github/workflows/wheels.yml
-    with:
-      pypi-repository: "testpypi"
-    secrets:
-      pypi-username: ${{ secrets.TEST_PYPI_USERNAME }}
-      pypi-password: ${{ secrets.TEST_PYPI_PASSWORD }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/.github/workflows/wheels.yml 
new/mmh3-5.2.1/.github/workflows/wheels.yml
--- old/mmh3-5.2.0/.github/workflows/wheels.yml 2025-07-29 08:53:09.000000000 
+0200
+++ new/mmh3-5.2.1/.github/workflows/wheels.yml 2026-03-05 16:11:36.000000000 
+0100
@@ -2,16 +2,10 @@
 name: Wheel-Builder
 
 on:
-  workflow_call:
-    inputs:
-      pypi-repository:
-        required: false
-        type: string
-    secrets:
-      pypi-username:
-        required: false
-      pypi-password:
-        required: false
+  push:
+    tags:
+      - "v*.*.*"
+  workflow_dispatch:
 
 permissions: {}
 
@@ -25,45 +19,66 @@
         archs: [x86_64, i686, aarch64, ppc64le, s390x]
         build: [manylinux, musllinux]
         include:
-          - os: windows-2022
+          - os: windows-2025
             archs: AMD64
-          - os: windows-2022
+          - os: windows-2025
             archs: x86
-          - os: windows-2022
+          - os: windows-2025
             archs: ARM64
-          - os: macos-13
+          - os: macos-26-intel
             archs: x86_64
-          - os: macos-14
+          - os: macos-26
             archs: arm64
-          - os: macos-14
+          - os: macos-26
             archs: universal2
           - os: ubuntu-24.04
             platform: android
             archs: x86_64
             build: android
-          - os: macos-14
+          - os: macos-26
             platform: android
             archs: arm64_v8a
             build: android
-          - os: macos-14
+          - os: macos-26
             platform: ios
             archs: arm64_iphoneos
-          - os: macos-14
+          - os: macos-26
             platform: ios
             archs: arm64_iphonesimulator
-          - os: macos-13
+          - os: macos-26-intel
             platform: ios
             archs: x86_64_iphonesimulator
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 
v6.0.2
+        with:
+          persist-credentials: false
       - name: Set up Python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # 
v6.2.0
         with:
-          python-version: "3.13"
+          python-version: "3.14"
+      - name: Set dev version for TestPyPI
+        if: github.event_name == 'workflow_dispatch'
+        shell: python
+        run: |
+          import re, datetime
+          timestamp = 
datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%d%H%M")
+          text = open("pyproject.toml", encoding="utf-8").read()
+          m = re.search(r'version\s*=\s*"(.+?)"', text)
+          if not m:
+            raise RuntimeError("version field not found in pyproject.toml")
+          version = m.group(1)
+          base_version = version.split("-")[0]
+          new_text = re.sub(
+            r'version\s*=\s*".*?"',
+            f'version = "{base_version}.dev{timestamp}"',
+            text,
+            count=1
+          )
+          open("pyproject.toml", "w", encoding="utf-8").write(new_text)
       - name: Set up QEMU
         if: runner.os == 'Linux' && matrix.platform != 'android'
-        uses: docker/setup-qemu-action@v3
+        uses: 
docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
       # 
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'
@@ -72,11 +87,11 @@
           sudo udevadm control --reload-rules
           sudo udevadm trigger --name-match=kvm
       - name: Build wheels
-        uses: pypa/[email protected]
+        uses: pypa/cibuildwheel@ee02a1537ce3071a004a6b08c41e72f0fdc42d9a # 
v3.4.0
         with:
           output-dir: wheelhouse
         env:
-          CIBW_BUILD: "{cp39,cp310,cp311,cp312,cp313,cp314,cp314t}-${{ 
matrix.build }}*"
+          CIBW_BUILD: "{cp310,cp311,cp312,cp313,cp314,cp314t}-${{ matrix.build 
}}*"
           CIBW_PLATFORM: ${{ matrix.platform || 'auto' }}
           CIBW_ARCHS: ${{ matrix.archs }}
           CIBW_BUILD_FRONTEND: "build"
@@ -87,7 +102,7 @@
           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
+      - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f 
# v7.0.0
         with:
           name: Wheel-${{ matrix.os }}-${{ matrix.platform }}-${{ matrix.build 
}}${{ matrix.archs }}
           path: ./wheelhouse/*.whl
@@ -96,11 +111,32 @@
     runs-on: ubuntu-24.04
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 
v6.0.2
+        with:
+          persist-credentials: false
       - name: Set up Python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # 
v6.2.0
         with:
-          python-version: "3.13"
+          python-version: "3.14"
+      - name: Set dev version for TestPyPI
+        if: github.event_name == 'workflow_dispatch'
+        shell: python
+        run: |
+          import re, datetime
+          timestamp = 
datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%d%H%M")
+          text = open("pyproject.toml", encoding="utf-8").read()
+          m = re.search(r'version\s*=\s*"(.+?)"', text)
+          if not m:
+            raise RuntimeError("version field not found in pyproject.toml")
+          version = m.group(1)
+          base_version = version.split("-")[0]
+          new_text = re.sub(
+            r'version\s*=\s*".*?"',
+            f'version = "{base_version}.dev{timestamp}"',
+            text,
+            count=1
+          )
+          open("pyproject.toml", "w", encoding="utf-8").write(new_text)
       - name: Build sdist
         run: |
           python -m pip install --upgrade pip
@@ -114,31 +150,44 @@
           python -m pip install dist/*.tar.gz
           python -m pytest
           mypy --strict tests
-      - uses: actions/upload-artifact@v4
+      - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f 
# v7.0.0
         with:
           path: dist/*.tar.gz
-  publish:
-    if: ${{ inputs.pypi-repository == 'pypi' || inputs.pypi-repository == 
'testpypi'}}
-    name: "Upload to PyPI/Test PyPI"
-    runs-on: ubuntu-24.04
+  publish-to-pypi:
+    name: "Publish artifacts to PyPI"
+    if: startsWith(github.ref, 'refs/tags/')
     needs: [build_wheels, build_sdist]
+    runs-on: ubuntu-24.04
+    environment:
+      name: pypi
+      url: https://pypi.org/p/mmh3
+    permissions:
+      id-token: write # IMPORTANT: this permission is mandatory for trusted 
publishing
     steps:
-      - name: Set up Python
-        uses: actions/setup-python@v5
+      - name: Set up built items
+        uses: 
actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
         with:
-          python-version: "3.13"
+          path: dist
+          merge-multiple: true
+      - name: Publish package distributions to PyPI
+        uses: 
pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
+  publish-to-testpypi:
+    name: "Publish artifacts to TestPyPI"
+    if: github.event_name == 'workflow_dispatch' && github.ref == 
'refs/heads/master'
+    needs: [build_wheels, build_sdist]
+    runs-on: ubuntu-24.04
+    environment:
+      name: testpypi
+      url: https://test.pypi.org/p/mmh3
+    permissions:
+      id-token: write # IMPORTANT: this permission is mandatory for trusted 
publishing
+    steps:
       - name: Set up built items
-        uses: actions/download-artifact@v4
+        uses: 
actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
         with:
           path: dist
           merge-multiple: true
-      - name: Install dependencies
-        run: |
-          python -m pip install --upgrade pip
-          pip install setuptools wheel twine
-      - name: "Publish"
-        env:
-          TWINE_USERNAME: ${{ secrets.pypi-username }}
-          TWINE_PASSWORD: ${{ secrets.pypi-password }}
-        run: |
-          twine upload --repository ${{ inputs.pypi-repository }} dist/*
+      - name: Publish package distributions to TestPyPI
+        uses: 
pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
+        with:
+          repository-url: https://test.pypi.org/legacy/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/CHANGELOG.md new/mmh3-5.2.1/CHANGELOG.md
--- old/mmh3-5.2.0/CHANGELOG.md 2025-07-29 08:53:09.000000000 +0200
+++ new/mmh3-5.2.1/CHANGELOG.md 2026-03-05 16:11:36.000000000 +0100
@@ -10,6 +10,16 @@
 [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html)
 since version 3.0.0.
 
+## [5.2.1] - 2026-03-06
+
+### Added
+
+- Add support for the Android wheel for Python 3.14.
+
+### Removed
+
+- Drop support for Python 3.9, as it has reached the end of life on 2025-10-31.
+
 ## [5.2.0] - 2025-07-29
 
 ### Added
@@ -81,7 +91,7 @@
 - **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`
+- The type of flag arguments 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
@@ -300,6 +310,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.1]: https://github.com/hajimes/mmh3/compare/v5.2.0...v5.2.1
 [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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/LICENSE new/mmh3-5.2.1/LICENSE
--- old/mmh3-5.2.0/LICENSE      2025-07-29 08:53:09.000000000 +0200
+++ new/mmh3-5.2.1/LICENSE      2026-03-05 16:11:36.000000000 +0100
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2011-2025 Hajime Senuma
+Copyright (c) 2011-2026 Hajime Senuma
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/README.md new/mmh3-5.2.1/README.md
--- old/mmh3-5.2.0/README.md    2025-07-29 08:53:09.000000000 +0200
+++ new/mmh3-5.2.1/README.md    2026-03-05 16:11:36.000000000 +0100
@@ -81,6 +81,16 @@
 See [Changelog (latest 
version)](https://mmh3.readthedocs.io/en/latest/changelog.html)
 for the complete changelog.
 
+### [5.2.1] - 2026-03-06
+
+#### Added
+
+- Add support for the Android wheel for Python 3.14.
+
+#### Removed
+
+- Drop support for Python 3.9, as it has reached the end of life on 2025-10-31.
+
 ### [5.2.0] - 2025-07-29
 
 #### Added
@@ -114,13 +124,6 @@
 - Drop support for Python 3.8, as it has reached the end of life on 2024-10-07
   ([#117](https://github.com/hajimes/mmh3/pull/117)).
 
-### [5.0.1] - 2024-09-22
-
-#### Fixed
-
-- Fix the issue that the package cannot be built from the source distribution
-  ([#90](https://github.com/hajimes/mmh3/issues/90)).
-
 ## License
 
 [MIT](https://github.com/hajimes/mmh3/blob/master/LICENSE), unless otherwise
@@ -193,8 +196,8 @@
 - Chapter 11: _Using Less Ram_ in Micha Gorelick and Ian Ozsvald. 2014. _High
   Performance Python: Practical Performant Programming for Humans_. O'Reilly
   Media. [ISBN: 978-1-4493-6159-4](https://www.amazon.com/dp/1449361595).
-  - 2nd edition of the above (2020).
-    [ISBN: 978-1492055020](https://www.amazon.com/dp/1492055026).
+  - 3rd edition of the above (2025).
+    [ISBN: 978-1098165963](https://www.amazon.com/dp/1098165969/).
 - Max Burstein. February 2, 2013.
   _[Creating a Simple Bloom 
Filter](http://www.maxburstein.com/blog/creating-a-simple-bloom-filter/)_.
 - Duke University. April 14, 2016.
@@ -267,6 +270,6 @@
 - <https://github.com/ifduyue/python-xxhash>: Python bindings for xxHash (Yue
   Du)
 
+[5.2.1]: https://github.com/hajimes/mmh3/compare/v5.2.0...v5.2.1
 [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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/benchmark/benchmark.py 
new/mmh3-5.2.1/benchmark/benchmark.py
--- old/mmh3-5.2.0/benchmark/benchmark.py       2025-07-29 08:53:09.000000000 
+0200
+++ new/mmh3-5.2.1/benchmark/benchmark.py       2026-03-05 16:11:36.000000000 
+0100
@@ -8,11 +8,12 @@
 from collections.abc import Callable
 from typing import Final
 
-import mmh3
 import pymmh3
 import pyperf
 import xxhash
 
+import mmh3
+
 K1: Final[int] = 
0b1001111000110111011110011011000110000101111010111100101010000111
 K2: Final[int] = 
0b1100001010110010101011100011110100100111110101001110101101001111
 MASK: Final[int] = 0xFFFFFFFFFFFFFFFF
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/benchmark/generate_table.py 
new/mmh3-5.2.1/benchmark/generate_table.py
--- old/mmh3-5.2.0/benchmark/generate_table.py  2025-07-29 08:53:09.000000000 
+0200
+++ new/mmh3-5.2.1/benchmark/generate_table.py  2026-03-05 16:11:36.000000000 
+0100
@@ -9,11 +9,12 @@
 import os
 from typing import TypeVar
 
-import mmh3
 import pandas as pd
 import pyperf
 import xxhash
 
+import mmh3
+
 T = TypeVar("T")
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/benchmark/plot_graph.py 
new/mmh3-5.2.1/benchmark/plot_graph.py
--- old/mmh3-5.2.0/benchmark/plot_graph.py      2025-07-29 08:53:09.000000000 
+0200
+++ new/mmh3-5.2.1/benchmark/plot_graph.py      2026-03-05 16:11:36.000000000 
+0100
@@ -6,11 +6,12 @@
 from typing import TypeVar
 
 import matplotlib.pyplot as plt
-import mmh3
 import pandas as pd
 import pyperf
 import xxhash
 
+import mmh3
+
 T = TypeVar("T")
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/benchmark/plot_graph_base_hash.py 
new/mmh3-5.2.1/benchmark/plot_graph_base_hash.py
--- old/mmh3-5.2.0/benchmark/plot_graph_base_hash.py    2025-07-29 
08:53:09.000000000 +0200
+++ new/mmh3-5.2.1/benchmark/plot_graph_base_hash.py    2026-03-05 
16:11:36.000000000 +0100
@@ -9,10 +9,11 @@
 from typing import TypeVar
 
 import matplotlib.pyplot as plt
-import mmh3
 import pandas as pd
 import pyperf
 
+import mmh3
+
 T = TypeVar("T")
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/docs/api.md new/mmh3-5.2.1/docs/api.md
--- old/mmh3-5.2.0/docs/api.md  2025-07-29 08:53:09.000000000 +0200
+++ new/mmh3-5.2.1/docs/api.md  2026-03-05 16:11:36.000000000 +0100
@@ -32,9 +32,25 @@
 architectures is required, such as when calculating hash footprints for web
 services.
 
+```{caution}
+[Buffer-accepting hash functions](#buffer-accepting-hash-functions) (except the
+deprecated `hash_from_buffer`) accept positional-arguments only. Using keyword
+arguments will raise a `TypeError`.
+```
+
+```{note}
 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.
+version 5.2.0.
+- Basic hash functions are inherently thread-safe by design.
+- Buffer-accepting hash functions are thread-safe,
+  **provided the input buffer is thread-safe**.
+- Hasher classes are thread-safe,
+  again **assuming the input buffer is thread-safe**.
+
+However, thread safety under the no-GIL variant has not yet been
+fully tested as of 5.2.0. If you encounter any issues, please report them via
+the [issue tracker](https://github.com/hajimes/mmh3/issues).
+```
 
 ## Basic Hash Functions
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/pyproject.toml 
new/mmh3-5.2.1/pyproject.toml
--- old/mmh3-5.2.0/pyproject.toml       2025-07-29 08:53:09.000000000 +0200
+++ new/mmh3-5.2.1/pyproject.toml       2026-03-05 16:11:36.000000000 +0100
@@ -5,12 +5,12 @@
 
 [project]
 name = "mmh3"
-version = "5.2.0"
+version = "5.2.1"
 description = "Python extension for MurmurHash (MurmurHash3), a set of fast 
and robust hash functions."
 readme = "README.md"
 license = {file = "LICENSE"}
 keywords = ["utility", "hash", "MurmurHash"]
-requires-python = ">=3.9"
+requires-python = ">=3.10"
 authors = [
   {name = "Hajime Senuma", email="[email protected]"}
 ]
@@ -19,7 +19,6 @@
   "Intended Audience :: Developers",
   "License :: OSI Approved :: MIT License",
   "Programming Language :: Python :: 3",
-  "Programming Language :: Python :: 3.9",
   "Programming Language :: Python :: 3.10",
   "Programming Language :: Python :: 3.11",
   "Programming Language :: Python :: 3.12",
@@ -32,43 +31,58 @@
 
 [project.optional-dependencies]
 test = [
-  "pytest == 8.4.1",
-  "pytest-sugar == 1.0.0"
+  "pytest == 9.0.2",
+  "pytest-sugar == 1.1.1"
 ]
 lint = [
-  "black == 25.1.0",
-  "clang-format == 20.1.8",
-  "isort == 6.0.1",
-  "pylint == 3.3.7"
+  "actionlint-py == 1.7.11.24",
+  "clang-format == 22.1.0",
+  "codespell == 2.4.1",
+  "pylint == 4.0.5",
+  "ruff == 0.15.4"
 ]
 type = [
-  "mypy == 1.17.0"
+  "mypy == 1.19.1"
 ]
 docs = [
-  "myst-parser == 4.0.1",
-  "shibuya == 2025.7.24",
+  "myst-parser == 5.0.0",
+  "shibuya == 2026.1.9",
   "sphinx == 8.2.3",
   "sphinx-copybutton == 0.5.2"
 ]
 benchmark = [
   "pymmh3 == 0.0.5",
-  "pyperf == 2.9.0",
-  "xxhash == 3.5.0"
+  "pyperf == 2.10.0",
+  "xxhash == 3.6.0"
 ]
 plot = [
-  "matplotlib == 3.10.3",
-  "pandas == 2.3.1"
+  "matplotlib == 3.10.8",
+  "pandas == 3.0.1"
 ]
 
 [project.urls]
 Homepage = "https://pypi.org/project/mmh3/";
+Documentation = "https://mmh3.readthedocs.io/";
 Repository = "https://github.com/hajimes/mmh3";
 Changelog = "https://github.com/hajimes/mmh3/blob/master/CHANGELOG.md";
 "Bug Tracker" = "https://github.com/hajimes/mmh3/issues";
 
-[tool.isort]
-profile = "black"
-src_paths = ["src/mmh3/__init__.pyi", "util", "tests", "benchmark", "docs"]
+[tool.codespell]
+# As of 2026-03-02, skip has an issue on super-linter
+# https://github.com/super-linter/super-linter/issues/7466
+skip = "*/paper.bib,./build"
+# Collet is a surname, Commun is an abbr for a journal name,
+# fo is used in several test strings, and Ines is also a surname.
+ignore-words-list = "Collet,Commun,fo,Ines"
+
+[tool.ruff]
+src = ["src/mmh3/__init__.pyi", "util", "tests", "benchmark", "docs"]
+
+[tool.ruff.lint]
+select = ["E", "W", "F", "I", "UP", "B", "SIM", "C4", "ISC", "NPY"]
+
+[tool.ruff.lint.isort]
+known-first-party = ["mmh3"]
 
 [tool.setuptools]
 include-package-data = true
@@ -92,7 +106,7 @@
 # This setting can be found in the template file of super-linter 7.0.0.
 jobs = 0
 # import-error: An error tricky to resolve, especially on super-linter.
-# wrong-import-order: Respect isort's import order.
+# wrong-import-order: Respect Ruff's import order.
 disable = [
   "import-error",
   "wrong-import-order"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/src/mmh3/__init__.pyi 
new/mmh3-5.2.1/src/mmh3/__init__.pyi
--- old/mmh3-5.2.0/src/mmh3/__init__.pyi        2025-07-29 08:53:09.000000000 
+0200
+++ new/mmh3-5.2.1/src/mmh3/__init__.pyi        2026-03-05 16:11:36.000000000 
+0100
@@ -1,46 +1,36 @@
 import sys
-from typing import Any, Union, final
+from typing import Any, final
 
 if sys.version_info >= (3, 12):
     from collections.abc import Buffer
 else:
     from _typeshed import ReadableBuffer as Buffer
 
-def hash(key: Union[bytes, str], seed: int = 0, signed: Any = True) -> int: ...
-def hash_from_buffer(
-    key: Union[Buffer, str], seed: int = 0, signed: Any = True
-) -> int: ...
+def hash(key: bytes | str, seed: int = 0, signed: Any = True) -> int: ...
+def hash_from_buffer(key: Buffer | str, seed: int = 0, signed: Any = True) -> 
int: ...
 def hash64(
-    key: Union[bytes, str], seed: int = 0, x64arch: Any = True, signed: Any = 
True
+    key: bytes | str, seed: int = 0, x64arch: Any = True, signed: Any = True
 ) -> tuple[int, int]: ...
 def hash128(
-    key: Union[bytes, str], seed: int = 0, x64arch: Any = True, signed: Any = 
False
+    key: bytes | str, seed: int = 0, x64arch: Any = True, signed: Any = False
 ) -> int: ...
-def hash_bytes(key: Union[bytes, str], seed: int = 0, x64arch: Any = True) -> 
bytes: ...
-def mmh3_32_digest(key: Union[Buffer, str], seed: int = 0) -> bytes: ...
-def mmh3_32_sintdigest(key: Union[Buffer, str], seed: int = 0) -> int: ...
-def mmh3_32_uintdigest(key: Union[Buffer, str], seed: int = 0) -> int: ...
-def mmh3_x64_128_digest(key: Union[Buffer, str], seed: int = 0) -> bytes: ...
-def mmh3_x64_128_sintdigest(key: Union[Buffer, str], seed: int = 0) -> int: ...
-def mmh3_x64_128_uintdigest(key: Union[Buffer, str], seed: int = 0) -> int: ...
-def mmh3_x64_128_stupledigest(
-    key: Union[Buffer, str], seed: int = 0
-) -> tuple[int, int]: ...
-def mmh3_x64_128_utupledigest(
-    key: Union[Buffer, str], seed: int = 0
-) -> tuple[int, int]: ...
-def mmh3_x86_128_digest(key: Union[Buffer, str], seed: int = 0) -> bytes: ...
-def mmh3_x86_128_sintdigest(key: Union[Buffer, str], seed: int = 0) -> int: ...
-def mmh3_x86_128_uintdigest(key: Union[Buffer, str], seed: int = 0) -> int: ...
-def mmh3_x86_128_stupledigest(
-    key: Union[Buffer, str], seed: int = 0
-) -> tuple[int, int]: ...
-def mmh3_x86_128_utupledigest(
-    key: Union[Buffer, str], seed: int = 0
-) -> tuple[int, int]: ...
+def hash_bytes(key: bytes | str, seed: int = 0, x64arch: Any = True) -> bytes: 
...
+def mmh3_32_digest(key: Buffer | str, seed: int = 0) -> bytes: ...
+def mmh3_32_sintdigest(key: Buffer | str, seed: int = 0) -> int: ...
+def mmh3_32_uintdigest(key: Buffer | str, seed: int = 0) -> int: ...
+def mmh3_x64_128_digest(key: Buffer | str, seed: int = 0) -> bytes: ...
+def mmh3_x64_128_sintdigest(key: Buffer | str, seed: int = 0) -> int: ...
+def mmh3_x64_128_uintdigest(key: Buffer | str, seed: int = 0) -> int: ...
+def mmh3_x64_128_stupledigest(key: Buffer | str, seed: int = 0) -> tuple[int, 
int]: ...
+def mmh3_x64_128_utupledigest(key: Buffer | str, seed: int = 0) -> tuple[int, 
int]: ...
+def mmh3_x86_128_digest(key: Buffer | str, seed: int = 0) -> bytes: ...
+def mmh3_x86_128_sintdigest(key: Buffer | str, seed: int = 0) -> int: ...
+def mmh3_x86_128_uintdigest(key: Buffer | str, seed: int = 0) -> int: ...
+def mmh3_x86_128_stupledigest(key: Buffer | str, seed: int = 0) -> tuple[int, 
int]: ...
+def mmh3_x86_128_utupledigest(key: Buffer | str, seed: int = 0) -> tuple[int, 
int]: ...
 
 class Hasher:
-    def __init__(self, data: Union[Buffer, None] = None, seed: int = 0) -> 
None: ...
+    def __init__(self, data: Buffer | None = None, seed: int = 0) -> None: ...
     def update(self, data: Buffer) -> None: ...
     def digest(self) -> bytes: ...
     def sintdigest(self) -> int: ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/src/mmh3/mmh3module.c 
new/mmh3-5.2.1/src/mmh3/mmh3module.c
--- old/mmh3-5.2.0/src/mmh3/mmh3module.c        2025-07-29 08:53:09.000000000 
+0200
+++ new/mmh3-5.2.1/src/mmh3/mmh3module.c        2026-03-05 16:11:36.000000000 
+0100
@@ -2328,7 +2328,7 @@
     {"block_size", (getter)MMH3Hasher128x86_get_block_size, NULL,
      "int: Number of bytes of the internal block of this algorithm", NULL},
     {"name", (getter)MMH3Hasher128x86_get_name, NULL,
-     "str: Te hash algorithm being used by this object", NULL},
+     "str: The hash algorithm being used by this object", NULL},
     {NULL} /* Sentinel */
 };
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/tests/test_invalid_inputs.py 
new/mmh3-5.2.1/tests/test_invalid_inputs.py
--- old/mmh3-5.2.0/tests/test_invalid_inputs.py 2025-07-29 08:53:09.000000000 
+0200
+++ new/mmh3-5.2.1/tests/test_invalid_inputs.py 2026-03-05 16:11:36.000000000 
+0100
@@ -2,9 +2,10 @@
 # pylint: disable=no-value-for-parameter, too-many-function-args
 from typing import no_type_check
 
-import mmh3
 import pytest
 
+import mmh3
+
 
 @no_type_check
 def test_hash_raises_typeerror() -> None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/tests/test_mmh3.py 
new/mmh3-5.2.1/tests/test_mmh3.py
--- old/mmh3-5.2.0/tests/test_mmh3.py   2025-07-29 08:53:09.000000000 +0200
+++ new/mmh3-5.2.1/tests/test_mmh3.py   2026-03-05 16:11:36.000000000 +0100
@@ -2,7 +2,6 @@
 import sys
 
 import mmh3
-
 from helper import u32_to_s32
 
 
@@ -37,7 +36,7 @@
 
     assert mmh3.hash("Hello, world!", 0x9747B28C) == u32_to_s32(0x24884CBA)
 
-    assert mmh3.hash("ππππππππ".encode("utf-8"), 0x9747B28C) == 
u32_to_s32(0xD58063C1)
+    assert mmh3.hash("ππππππππ".encode(), 0x9747B28C) == u32_to_s32(0xD58063C1)
 
     assert mmh3.hash("a" * 256, 0x9747B28C) == u32_to_s32(0x37405BDC)
 
@@ -82,7 +81,7 @@
 
     assert mmh3.hash("Hello, world!", 0x9747B28C, signed=False) == 0x24884CBA
 
-    assert mmh3.hash("ππππππππ".encode("utf-8"), 0x9747B28C, signed=False) == 
0xD58063C1
+    assert mmh3.hash("ππππππππ".encode(), 0x9747B28C, signed=False) == 
0xD58063C1
 
     assert mmh3.hash("a" * 256, 0x9747B28C, signed=False) == 0x37405BDC
 
@@ -140,7 +139,7 @@
 
     assert mmh3.hash("Hello, world!", 0x9747B28C) == u32_to_s32(0x24884CBA)
 
-    assert mmh3.hash("ππππππππ".encode("utf-8"), 0x9747B28C) == 
u32_to_s32(0xD58063C1)
+    assert mmh3.hash("ππππππππ".encode(), 0x9747B28C) == u32_to_s32(0xD58063C1)
 
     assert mmh3.hash("a" * 256, 0x9747B28C) == u32_to_s32(0x37405BDC)
 
@@ -155,7 +154,7 @@
 
 
 def test_hash_from_buffer() -> None:
-    mview = memoryview("foo".encode("utf8"))
+    mview = memoryview(b"foo")
     assert mmh3.hash_from_buffer(mview) == -156908512
     assert mmh3.hash_from_buffer(mview, signed=False) == 4138058784
 
@@ -267,7 +266,7 @@
         4, "little"
     )
 
-    assert mmh3.mmh3_32_digest("ππππππππ".encode("utf-8"), 0x9747B28C) == (
+    assert mmh3.mmh3_32_digest("ππππππππ".encode(), 0x9747B28C) == (
         0xD58063C1
     ).to_bytes(4, "little")
 
@@ -329,9 +328,9 @@
         0x24884CBA
     )
 
-    assert mmh3.mmh3_32_sintdigest(
-        "ππππππππ".encode("utf-8"), 0x9747B28C
-    ) == u32_to_s32(0xD58063C1)
+    assert mmh3.mmh3_32_sintdigest("ππππππππ".encode(), 0x9747B28C) == 
u32_to_s32(
+        0xD58063C1
+    )
 
     assert mmh3.mmh3_32_sintdigest(b"a" * 256, 0x9747B28C) == 
u32_to_s32(0x37405BDC)
 
@@ -379,7 +378,7 @@
 
     assert mmh3.mmh3_32_uintdigest(b"Hello, world!", 0x9747B28C) == 0x24884CBA
 
-    assert mmh3.mmh3_32_uintdigest("ππππππππ".encode("utf-8"), 0x9747B28C) == 
0xD58063C1
+    assert mmh3.mmh3_32_uintdigest("ππππππππ".encode(), 0x9747B28C) == 
0xD58063C1
 
     assert mmh3.mmh3_32_uintdigest(b"a" * 256, 0x9747B28C) == 0x37405BDC
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/tests/test_mmh3_hasher.py 
new/mmh3-5.2.1/tests/test_mmh3_hasher.py
--- old/mmh3-5.2.0/tests/test_mmh3_hasher.py    2025-07-29 08:53:09.000000000 
+0200
+++ new/mmh3-5.2.1/tests/test_mmh3_hasher.py    2026-03-05 16:11:36.000000000 
+0100
@@ -1,6 +1,5 @@
 # pylint: disable=missing-module-docstring,missing-function-docstring
 import mmh3
-
 from helper import u32_to_s32
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/tox.ini new/mmh3-5.2.1/tox.ini
--- old/mmh3-5.2.0/tox.ini      2025-07-29 08:53:09.000000000 +0200
+++ new/mmh3-5.2.1/tox.ini      2026-03-05 16:11:36.000000000 +0100
@@ -1,7 +1,7 @@
 [tox]
 requires =
     tox>=4
-envlist = lint, type, py{39,310,311,312,313,314,314t}
+envlist = lint, type, py{310,311,312,313,314,314t}
 
 [testenv]
 description = run unit tests
@@ -19,13 +19,15 @@
 commands_pre =
     uv pip install ".[lint]"
 commands =
-    black .
-    isort .
+    ruff format .
+    ruff check --fix .
     find ./src/mmh3 -name '*.[ch]' -exec clang-format -i {} +
     npx prettier --write .
     pylint --recursive=y .
     npx markdownlint --config .markdown-lint.yml \
       --ignore-path .gitignore **/*.md
+    codespell
+    actionlint
 
 [testenv:type]
 description = run type checks
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mmh3-5.2.0/util/refresh.py 
new/mmh3-5.2.1/util/refresh.py
--- old/mmh3-5.2.0/util/refresh.py      2025-07-29 08:53:09.000000000 +0200
+++ new/mmh3-5.2.1/util/refresh.py      2026-03-05 16:11:36.000000000 +0100
@@ -132,15 +132,13 @@
     """
     subcode += "\n\n"
 
-    subcode += textwrap.dedent(
-        """\
+    subcode += textwrap.dedent("""\
         // To handle 64-bit data; see https://docs.python.org/3/c-api/arg.html
         #ifndef PY_SSIZE_T_CLEAN
         #define PY_SSIZE_T_CLEAN
         #endif
         #include <Python.h>
-        """
-    )
+        """)
 
     return subcode
 
@@ -156,13 +154,11 @@
     """
     subcode += "\n"
 
-    subcode += textwrap.dedent(
-        """\
+    subcode += textwrap.dedent("""\
         #if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
         #include <byteswap.h>
         #endif
-        """
-    )
+        """)
 
     return subcode
 
@@ -171,7 +167,7 @@
     """Use Py_ssize_t instead of int as the index type.
 
     Py_ssize_t is the type used by Python to represent the size of objects.
-    It is required to handle 64-bit data in Python extentions.
+    It is required to handle 64-bit data in Python extensions.
 
     See https://docs.python.org/3/c-api/intro.html#c.Py_ssize_t
     and
@@ -214,15 +210,13 @@
     for tr in transformations:
         subcode = subcode.replace(tr[0], tr[1])
 
-    BYTE_SWAP_IF_BIG_ENDIAN = textwrap.dedent(
-        """\
+    BYTE_SWAP_IF_BIG_ENDIAN = textwrap.dedent("""\
         #if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
             return bswap_\\1(p[i]);
         #else
             return p[i];
         #endif
-        """
-    )
+        """)
 
     subcode = re.sub(
         r"getblock(.*?)(\s\(.*?\{\n).*?\}",
@@ -266,8 +260,7 @@
     """
     # pylint: disable=invalid-name
 
-    BYTE_SWAP_IF_BIG_ENDIAN = textwrap.dedent(
-        """\
+    BYTE_SWAP_IF_BIG_ENDIAN = textwrap.dedent("""\
         #if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
                 ((uint32_t *)out)[0] = h2;
                 ((uint32_t *)out)[1] = h1;
@@ -276,8 +269,7 @@
         #else
             \\1
         #endif
-        """
-    )
+        """)
 
     subcode = re.sub(
         r"(\(\(uint32_t\*\)out\)\[0\] = h1;[\s\S]*\(\(uint32_t\*\)out\)\[3\] = 
h4;)",
@@ -290,7 +282,7 @@
 
 
 def expand_win_stdint_typedefs(subcode: str) -> str:
-    """Delineate int type defitions for the older versions of the VS compiler.
+    """Delineate int type definitions for the older versions of the VS 
compiler.
 
     Args:
         subcode (str): The code to be transformed.
@@ -300,16 +292,14 @@
     """
     # pylint: disable=invalid-name
 
-    MSC_STDINT_TYPEDEFS = textwrap.dedent(
-        """\
+    MSC_STDINT_TYPEDEFS = textwrap.dedent("""\
         typedef signed __int8 int8_t;
         typedef signed __int32 int32_t;
         typedef signed __int64 int64_t;
         typedef unsigned __int8 uint8_t;
         typedef unsigned __int32 uint32_t;
         typedef unsigned __int64 uint64_t;
-        """
-    )
+        """)
 
     return re.sub(
         r"typedef unsigned char(.*)uint64_t;",
@@ -335,18 +325,15 @@
     """
     subcode += "\n\n"
 
-    subcode += textwrap.dedent(
-        """\
+    subcode += textwrap.dedent("""\
         
//-----------------------------------------------------------------------------
         // Building blocks for multiply and rotate (MUR) operations.
         // Names are taken from Google Guava's implementation
-        """
-    )
+        """)
 
     subcode += "\n"
 
-    subcode += textwrap.dedent(
-        """\
+    subcode += textwrap.dedent("""\
         static FORCE_INLINE uint32_t
         mixK1(uint32_t k1)
         {
@@ -359,11 +346,9 @@
 
             return k1;
         }
-        """
-    )
+        """)
 
-    subcode += textwrap.dedent(
-        """\
+    subcode += textwrap.dedent("""\
         static FORCE_INLINE uint32_t
         mixH1(uint32_t h1, const uint32_t h2, const uint8_t shift, const 
uint32_t c1)
         {
@@ -373,11 +358,9 @@
 
             return h1;
         }
-        """
-    )
+        """)
 
-    subcode += textwrap.dedent(
-        """\
+    subcode += textwrap.dedent("""\
         static FORCE_INLINE uint64_t
         mixK_x64_128(uint64_t k1, const uint8_t shift,
                     const uint64_t c1, const uint64_t c2)
@@ -388,11 +371,9 @@
 
             return k1;
         }
-        """
-    )
+        """)
 
-    subcode += textwrap.dedent(
-        """\
+    subcode += textwrap.dedent("""\
         static FORCE_INLINE uint64_t
         mixK1_x64_128(uint64_t k1)
         {
@@ -405,11 +386,9 @@
 
             return k1;
         }
-        """
-    )
+        """)
 
-    subcode += textwrap.dedent(
-        """\
+    subcode += textwrap.dedent("""\
         static FORCE_INLINE uint64_t
         mixK2_x64_128(uint64_t k2)
         {
@@ -422,11 +401,9 @@
 
             return k2;
         }
-        """
-    )
+        """)
 
-    subcode += textwrap.dedent(
-        """\
+    subcode += textwrap.dedent("""\
         static FORCE_INLINE uint64_t
         mixH_x64_128(uint64_t h1, uint64_t h2, const uint8_t shift, const 
uint32_t c)
         {
@@ -436,11 +413,9 @@
 
             return h1;
         }
-        """
-    )
+        """)
 
-    subcode += textwrap.dedent(
-        """\
+    subcode += textwrap.dedent("""\
         static FORCE_INLINE uint64_t
         mixK_x86_128(uint32_t k, const uint8_t shift, const uint32_t c1,
                     const uint32_t c2)
@@ -451,8 +426,7 @@
 
             return k;
         }
-        """
-    )
+        """)
 
     return subcode
 
@@ -468,15 +442,13 @@
     """
     hasher_digests = "\n\n"
 
-    hasher_digests += textwrap.dedent(
-        """\
+    hasher_digests += textwrap.dedent("""\
         static FORCE_INLINE void
         digest_x86_128_impl(uint32_t h1, uint32_t h2, uint32_t h3, uint32_t h4,
             const uint32_t k1, const uint32_t k2, const uint32_t k3,
             const uint32_t k4, const Py_ssize_t len, const char *out)
         {
-        """
-    )
+        """)
 
     hasher_digests += subcode + "\n"
 
@@ -494,18 +466,15 @@
     """
     hasher_digests = ""
 
-    hasher_digests += textwrap.dedent(
-        """\
+    hasher_digests += textwrap.dedent("""\
         h1 ^= mixK_x86_128(k1, 15, c1, c2);
         h2 ^= mixK_x86_128(k2, 16, c2, c3);
         h3 ^= mixK_x86_128(k3, 17, c3, c4);
         h4 ^= mixK_x86_128(k4, 18, c4, c1);
-        """
-    )
+        """)
 
     hasher_digests += subcode + "\n"
-    hasher_digests += textwrap.dedent(
-        """\
+    hasher_digests += textwrap.dedent("""\
         #if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
             ((uint32_t *)out)[0] = bswap_32(h1);
             ((uint32_t *)out)[1] = bswap_32(h2);
@@ -517,8 +486,7 @@
             ((uint32_t *)out)[2] = h3;
             ((uint32_t *)out)[3] = h4;
         #endif
-        """
-    )
+        """)
     hasher_digests += "\n}"
 
     return hasher_digests
@@ -535,32 +503,25 @@
     """
     hasher_digests = "\n\n"
 
-    hasher_digests += textwrap.dedent(
-        """\
+    hasher_digests += textwrap.dedent("""\
         
//-----------------------------------------------------------------------------
         // Finalization function
-        """
-    )
+        """)
 
     hasher_digests += "\n"
 
-    hasher_digests += textwrap.dedent(
-        """\
+    hasher_digests += textwrap.dedent("""\
         static FORCE_INLINE void
         digest_x64_128_impl(uint64_t h1, uint64_t h2, const uint64_t k1,
             const uint64_t k2, const Py_ssize_t len, const char *out)
         {
-        """
-    )
-    hasher_digests += textwrap.dedent(
-        """\
+        """)
+    hasher_digests += textwrap.dedent("""\
         h1 ^= mixK1_x64_128(k1);
         h2 ^= mixK2_x64_128(k2);
-        """
-    )
+        """)
     hasher_digests += subcode + "\n"
-    hasher_digests += textwrap.dedent(
-        """\
+    hasher_digests += textwrap.dedent("""\
         #if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
             ((uint64_t *)out)[0] = bswap_64(h1);
             ((uint64_t *)out)[1] = bswap_64(h2);
@@ -568,8 +529,7 @@
             ((uint64_t *)out)[0] = h1;
             ((uint64_t *)out)[1] = h2;
         #endif
-        """
-    )
+        """)
     hasher_digests += "\n}"
 
     return hasher_digests
@@ -593,8 +553,7 @@
         "#define       FORCE_INLINE inline __attribute__((always_inline))"
     )
 
-    NON_WIN_FORCE_INLINE_REVISED = textwrap.dedent(
-        """\
+    NON_WIN_FORCE_INLINE_REVISED = textwrap.dedent("""\
         #if ((__GNUC__ > 4) || (__GNUC__ == 4 && GNUC_MINOR >= 4))
         /* gcc version >= 4.4 4.1 = RHEL 5, 4.4 = RHEL 6. Don't inline for 
RHEL 5 gcc
         * which is 4.1*/
@@ -602,8 +561,7 @@
         #else
         #define FORCE_INLINE
         #endif
-        """
-    )
+        """)
 
     return subcode.replace(NON_WIN_FORCE_INLINE_ORIGINAL, 
NON_WIN_FORCE_INLINE_REVISED)
 
@@ -658,9 +616,9 @@
     file_header_path = os.path.join(dir_path, FILE_HEADER_NAME)
 
     with (
-        open(original_source_path, "r", encoding="utf-8") as source_file,
-        open(original_header_path, "r", encoding="utf-8") as header_file,
-        open(file_header_path, "r", encoding="utf-8") as file_header_file,
+        open(original_source_path, encoding="utf-8") as source_file,
+        open(original_header_path, encoding="utf-8") as header_file,
+        open(file_header_path, encoding="utf-8") as file_header_file,
     ):
         source = MMH3Source(source_file.read())
         header = MMH3Header(header_file.read())

Reply via email to