hashcodecs 1.2.0__tar.gz → 1.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/CHANGELOG.md +12 -1
  2. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/CITATION.cff +3 -3
  3. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/Cargo.lock +1 -1
  4. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/Cargo.toml +11 -11
  5. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/PKG-INFO +1 -1
  6. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/base64.md +3 -1
  7. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/hashcodecs/_hashcodecs.pyi +22 -17
  8. hashcodecs-1.2.1/hatch_build.py +86 -0
  9. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/pyproject.toml +4 -6
  10. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/backend.rs +16 -11
  11. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/decode/avx512.rs +2 -2
  12. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/decode.rs +27 -8
  13. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/encode/avx512.rs +2 -2
  14. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/tests/aarch64.rs +2 -2
  15. hashcodecs-1.2.1/src/base64/tests.rs +1110 -0
  16. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64.rs +3 -0
  17. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/base64/decode.rs +318 -26
  18. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/base64/methods.rs +4 -4
  19. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/base64/mod.rs +10 -5
  20. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/buffer.rs +36 -6
  21. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/murmur3/incremental.rs +9 -9
  22. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/xxhash/methods.rs +4 -4
  23. hashcodecs-1.2.1/src/murmur3/block_buffer.rs +73 -0
  24. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/murmur3/dispatch.rs +2 -2
  25. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/murmur3/proofs.rs +4 -3
  26. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/murmur3/tests.rs +58 -16
  27. hashcodecs-1.2.1/src/murmur3/x64_128/x86.rs +190 -0
  28. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/murmur3/x64_128.rs +33 -17
  29. hashcodecs-1.2.1/src/murmur3/x86_128/x86.rs +206 -0
  30. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/murmur3/x86_128.rs +29 -11
  31. hashcodecs-1.2.1/src/murmur3/x86_32/x86.rs +139 -0
  32. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/murmur3/x86_32.rs +32 -16
  33. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/murmur3.rs +1 -3
  34. hashcodecs-1.2.1/src/xxhash/batch.rs +282 -0
  35. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/xxhash/hash.rs +3 -36
  36. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/xxhash/long/aarch64.rs +11 -9
  37. hashcodecs-1.2.1/src/xxhash/long/scalar.rs +53 -0
  38. {hashcodecs-1.2.0/src/xxhash/long → hashcodecs-1.2.1/src/xxhash/long/x86}/avx2.rs +28 -98
  39. hashcodecs-1.2.1/src/xxhash/long/x86/avx2_batch.rs +89 -0
  40. {hashcodecs-1.2.0/src/xxhash/long → hashcodecs-1.2.1/src/xxhash/long/x86}/avx512.rs +13 -8
  41. hashcodecs-1.2.1/src/xxhash/long/x86/mod.rs +4 -0
  42. {hashcodecs-1.2.0/src/xxhash/long → hashcodecs-1.2.1/src/xxhash/long/x86}/ssse3.rs +14 -10
  43. hashcodecs-1.2.1/src/xxhash/long.rs +485 -0
  44. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/xxhash/proofs.rs +10 -8
  45. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/xxhash/tests.rs +34 -12
  46. hashcodecs-1.2.0/hatch_build.py +0 -60
  47. hashcodecs-1.2.0/src/murmur3/incremental.rs +0 -42
  48. hashcodecs-1.2.0/src/murmur3/x86.rs +0 -515
  49. hashcodecs-1.2.0/src/xxhash/batch.rs +0 -191
  50. hashcodecs-1.2.0/src/xxhash/long.rs +0 -203
  51. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/.gitignore +0 -0
  52. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/BENCHMARK.md +0 -0
  53. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/LICENSE +0 -0
  54. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/LICENSE-MIT +0 -0
  55. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/README.md +0 -0
  56. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/SAFETY.md +0 -0
  57. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/SECURITY.md +0 -0
  58. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/benches/base64.rs +0 -0
  59. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/benches/murmur3.rs +0 -0
  60. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/benches/support/mod.rs +0 -0
  61. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/benches/xxhash.rs +0 -0
  62. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/build.rs +0 -0
  63. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/ARCHITECTURE.md +0 -0
  64. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/api/base64.md +0 -0
  65. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/api/murmur3.md +0 -0
  66. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/api/xxh3.md +0 -0
  67. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/base64-python-batch-large.svg +0 -0
  68. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/base64-python-batch-reusable.svg +0 -0
  69. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/base64-python-batch.svg +0 -0
  70. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/base64-python-lenient.svg +0 -0
  71. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/base64-python-memoryview.svg +0 -0
  72. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/base64-python-mutable.svg +0 -0
  73. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/base64-python-reusable.svg +0 -0
  74. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/base64-python.svg +0 -0
  75. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/base64-rust.svg +0 -0
  76. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/murmur3-python-mutable.svg +0 -0
  77. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/murmur3-python.svg +0 -0
  78. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/murmur3-rust.svg +0 -0
  79. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/performance-at-a-glance.svg +0 -0
  80. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/results.csv +0 -0
  81. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/xxh3-python.svg +0 -0
  82. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/xxh3-rust-batch-remainders.svg +0 -0
  83. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/benchmarks/xxh3-rust.svg +0 -0
  84. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/compatibility.md +0 -0
  85. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/index.md +0 -0
  86. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/murmur3.md +0 -0
  87. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/performance.md +0 -0
  88. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/requirements.txt +0 -0
  89. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/docs/xxh3.md +0 -0
  90. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/hashcodecs/__init__.py +0 -0
  91. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/hashcodecs/__init__.pyi +0 -0
  92. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/hashcodecs/base64.py +0 -0
  93. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/hashcodecs/base64.pyi +0 -0
  94. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/hashcodecs/murmur3.py +0 -0
  95. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/hashcodecs/murmur3.pyi +0 -0
  96. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/hashcodecs/py.typed +0 -0
  97. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/hashcodecs/xxhash.py +0 -0
  98. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/hashcodecs/xxhash.pyi +0 -0
  99. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/alphabet.rs +0 -0
  100. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/backend.rs +0 -0
  101. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/decode/aarch64.rs +0 -0
  102. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/decode/avx2.rs +0 -0
  103. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/decode/sse41.rs +0 -0
  104. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/decode/ssse3.rs +0 -0
  105. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/decode/x86_contracts.rs +0 -0
  106. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/dispatch.rs +0 -0
  107. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/encode/aarch64.rs +0 -0
  108. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/encode/avx2.rs +0 -0
  109. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/encode/cache.rs +0 -0
  110. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/encode/ssse3.rs +0 -0
  111. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/encode.rs +0 -0
  112. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/error.rs +0 -0
  113. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/miri_tests.rs +0 -0
  114. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/output.rs +0 -0
  115. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/base64/proofs.rs +0 -0
  116. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/arguments.rs +0 -0
  117. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/base64/callbacks.rs +0 -0
  118. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/base64/decode/plan.rs +0 -0
  119. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/base64/encode.rs +0 -0
  120. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/mod.rs +0 -0
  121. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/murmur3/digest.rs +0 -0
  122. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/murmur3/methods.rs +0 -0
  123. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/murmur3/mod.rs +0 -0
  124. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/murmur3/one_shot.rs +0 -0
  125. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/objects.rs +0 -0
  126. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/runtime.rs +0 -0
  127. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/xxhash/batch.rs +0 -0
  128. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/bindings/xxhash/mod.rs +0 -0
  129. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/lib.rs +0 -0
  130. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/murmur3/miri_tests.rs +0 -0
  131. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/murmur3/primitives.rs +0 -0
  132. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/xxhash/miri_tests.rs +0 -0
  133. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/xxhash/primitives.rs +0 -0
  134. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/xxhash/short.rs +0 -0
  135. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/src/xxhash.rs +0 -0
  136. {hashcodecs-1.2.0 → hashcodecs-1.2.1}/tools/generate_api_metadata.py +0 -0
@@ -4,6 +4,16 @@ This file records notable user-facing changes to `hashcodecs`. Version 1.0.0 sta
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.2.1] - 2026-08-26
8
+
9
+ ### What's Changed
10
+ * fix: harden buffer and lenient decode paths by @kozistr in https://github.com/kozistr/hashcodecs-rs/pull/58
11
+ * Refactor Murmur SIMD and centralize XXH3 long dispatch by @kozistr in https://github.com/kozistr/hashcodecs-rs/pull/59
12
+ * Repair sdist verification and roll back unpublished 1.2.1 by @kozistr in https://github.com/kozistr/hashcodecs-rs/pull/60
13
+
14
+
15
+ **Full Changelog**: https://github.com/kozistr/hashcodecs-rs/compare/v1.2.0...v1.2.1
16
+
7
17
  ## [1.2.0] - 2026-08-25
8
18
 
9
19
  ### What's Changed
@@ -117,7 +127,8 @@ This file records notable user-facing changes to `hashcodecs`. Version 1.0.0 sta
117
127
  - Initial Python and Rust APIs for Base64 and MurmurHash3.
118
128
  - Runtime SIMD dispatch and platform-specific CPython wheels.
119
129
 
120
- [Unreleased]: https://github.com/kozistr/hashcodecs-rs/compare/v1.2.0...HEAD
130
+ [Unreleased]: https://github.com/kozistr/hashcodecs-rs/compare/v1.2.1...HEAD
131
+ [1.2.1]: https://github.com/kozistr/hashcodecs-rs/compare/v1.2.0...v1.2.1
121
132
  [1.2.0]: https://github.com/kozistr/hashcodecs-rs/compare/v1.1.0...v1.2.0
122
133
  [1.1.0]: https://github.com/kozistr/hashcodecs-rs/compare/v1.0.0...v1.1.0
123
134
  [1.0.0]: https://github.com/kozistr/hashcodecs-rs/compare/v0.6.1...v1.0.0
@@ -5,8 +5,8 @@ authors:
5
5
  given-names: Hyeongchan
6
6
  orcid: https://orcid.org/0000-0002-1729-0580
7
7
  title: "hashcodecs: SIMD-accelerated Base64, MurmurHash3, and XXH3 for Python and Rust"
8
- version: 1.2.0
9
- date-released: 2026-08-25
8
+ version: 1.2.1
9
+ date-released: 2026-08-26
10
10
  license: "MIT OR Apache-2.0"
11
11
  repository-code: "https://github.com/kozistr/hashcodecs-rs"
12
- url: "https://github.com/kozistr/hashcodecs-rs"
12
+ url: "https://github.com/kozistr/hashcodecs-rs"
@@ -194,7 +194,7 @@ dependencies = [
194
194
 
195
195
  [[package]]
196
196
  name = "hashcodecs"
197
- version = "1.2.0"
197
+ version = "1.2.1"
198
198
  dependencies = [
199
199
  "base64",
200
200
  "base64-turbo",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "hashcodecs"
3
- version = "1.2.0"
3
+ version = "1.2.1"
4
4
  edition = "2024"
5
5
  rust-version = "1.89"
6
6
  description = "SIMD-accelerated Base64 codecs and fast MurmurHash3 and xxHash implementations"
@@ -36,16 +36,16 @@ pyo3 = { version = "0.29.2", optional = true }
36
36
  pyo3-build-config = { version = "0.29.2", optional = true }
37
37
 
38
38
  [dev-dependencies]
39
- base64 = "0.23.1"
40
- base64-turbo = "0.3.0"
41
- criterion = { version = "0.8.2", default-features = false, features = ["cargo_bench_support"] }
42
- fastmurmur3 = "0.2.0"
43
- mimalloc = "0.1.52"
44
- mm3h = "0.1.3"
45
- murmur3 = "0.5"
46
- murmurs = "1.0.5"
47
- xxhash-c-sys = "0.8.7"
48
- xxhash-rust = { version = "0.8.18", features = ["xxh3"] }
39
+ base64 = "=0.23.1"
40
+ base64-turbo = "=0.3.0"
41
+ criterion = { version = "=0.8.2", default-features = false, features = ["cargo_bench_support"] }
42
+ fastmurmur3 = "=0.2.0"
43
+ mimalloc = "=0.1.52"
44
+ mm3h = "=0.1.3"
45
+ murmur3 = "=0.5.2"
46
+ murmurs = "=1.0.5"
47
+ xxhash-c-sys = "=0.8.7"
48
+ xxhash-rust = { version = "=0.8.18", features = ["xxh3"] }
49
49
 
50
50
  [[bench]]
51
51
  name = "base64"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hashcodecs
3
- Version: 1.2.0
3
+ Version: 1.2.1
4
4
  Summary: SIMD-accelerated Base64, MurmurHash3, and xxHash codecs
5
5
  Project-URL: Documentation, https://hashcodecs-rs.readthedocs.io/
6
6
  Project-URL: Repository, https://github.com/kozistr/hashcodecs-rs
@@ -30,10 +30,12 @@ The `standard_b64encode` and `urlsafe_b64encode` helpers select the respective a
30
30
 
31
31
  ## Decoding and Validation
32
32
 
33
- `b64decode(s, altchars=None, validate=False, *, padded=True, ignorechars=..., canonical=False)` decodes an ASCII
33
+ `b64decode(s, altchars=None, validate=..., *, padded=True, ignorechars=..., canonical=False)` decodes an ASCII
34
34
  string or bytes-like value.
35
35
 
36
36
  - `validate=True` rejects characters outside the chosen alphabet instead of discarding them.
37
+ - If you supply `ignorechars` without `validate`, hashcodecs uses strict mode. Pass `validate=False` to keep lenient
38
+ mode; hashcodecs then limits discarded bytes to `ignorechars`.
37
39
  - `padded=False` accepts an unpadded final Base64 quantum.
38
40
  - `ignorechars` specifies which non-alphabet bytes lenient decoding may ignore. Its default preserves the standard
39
41
  library's lenient behavior.
@@ -1,3 +1,5 @@
1
+ from typing import final
2
+
1
3
  from _typeshed import ReadableBuffer
2
4
 
3
5
  def b64encode(
@@ -146,7 +148,7 @@ def b64decode(
146
148
  bit validation are available for protocols with tighter requirements.
147
149
 
148
150
  Args:
149
- s: ASCII text or contiguous bytes-like Base64 data.
151
+ s: ASCII text or bytes-like Base64 data.
150
152
  altchars: Two characters replacing "+" and "/", or None for the
151
153
  standard alphabet.
152
154
  validate: Reject non-alphabet bytes when true. The default is lenient
@@ -256,7 +258,7 @@ def b64decode_into(
256
258
  destination prefix may already have been modified.
257
259
 
258
260
  Args:
259
- s: ASCII text or contiguous bytes-like Base64 data.
261
+ s: ASCII text or bytes-like Base64 data.
260
262
  output: Destination bytearray with room for the complete result.
261
263
  altchars: Two characters replacing "+" and "/", or None for the
262
264
  standard alphabet.
@@ -348,7 +350,7 @@ def standard_b64decode(s: str | ReadableBuffer) -> bytes:
348
350
  Python's base64.standard_b64decode function.
349
351
 
350
352
  Args:
351
- s: ASCII text or contiguous bytes-like Base64 data.
353
+ s: ASCII text or bytes-like Base64 data.
352
354
 
353
355
  Returns:
354
356
  Newly allocated decoded bytes.
@@ -368,7 +370,7 @@ def standard_b64decode_into(s: str | ReadableBuffer, output: bytearray) -> int:
368
370
  """Decode standard Base64 into a reusable bytearray.
369
371
 
370
372
  Args:
371
- s: ASCII text or contiguous bytes-like Base64 data.
373
+ s: ASCII text or bytes-like Base64 data.
372
374
  output: Destination bytearray with room for the decoded result.
373
375
 
374
376
  Returns:
@@ -615,7 +617,7 @@ def xxh3_64_batch(items: list[ReadableBuffer], seed: int = 0) -> list[int]:
615
617
  """Compute canonical XXH3 64-bit hashes for a list of inputs.
616
618
 
617
619
  Args:
618
- items: A list of contiguous bytes-like objects to hash.
620
+ items: A list of bytes-like objects to hash.
619
621
  seed: Initial unsigned 64-bit seed shared by every item.
620
622
 
621
623
  Returns:
@@ -638,7 +640,7 @@ def xxh3_64_batch_into(items: list[ReadableBuffer], output: bytearray, seed: int
638
640
  written prefix remain unchanged.
639
641
 
640
642
  Args:
641
- items: A list of contiguous bytes-like objects to hash.
643
+ items: A list of bytes-like objects to hash.
642
644
  output: Destination with at least 8 * len(items) bytes.
643
645
  seed: Initial unsigned 64-bit seed shared by every item.
644
646
 
@@ -663,7 +665,7 @@ def xxh3_128_batch(items: list[ReadableBuffer], seed: int = 0) -> list[int]:
663
665
  """Compute canonical XXH3 128-bit hashes for a list of inputs.
664
666
 
665
667
  Args:
666
- items: A list of contiguous bytes-like objects to hash.
668
+ items: A list of bytes-like objects to hash.
667
669
  seed: Initial unsigned 64-bit seed shared by every item.
668
670
 
669
671
  Returns:
@@ -686,7 +688,7 @@ def xxh3_128_batch_into(items: list[ReadableBuffer], output: bytearray, seed: in
686
688
  written prefix remain unchanged.
687
689
 
688
690
  Args:
689
- items: A list of contiguous bytes-like objects to hash.
691
+ items: A list of bytes-like objects to hash.
690
692
  output: Destination with at least 16 * len(items) bytes.
691
693
  seed: Initial unsigned 64-bit seed shared by every item.
692
694
 
@@ -707,11 +709,12 @@ def xxh3_128_batch_into(items: list[ReadableBuffer], output: bytearray, seed: in
707
709
  """
708
710
  ...
709
711
 
712
+ @final
710
713
  class murmur3_x86_32:
711
714
  """Incremental MurmurHash3 x86 32-bit hasher.
712
715
 
713
716
  Args:
714
- data: Optional initial contiguous bytes-like data.
717
+ data: Optional initial bytes-like data.
715
718
  seed: Initial unsigned 32-bit seed.
716
719
 
717
720
  Examples:
@@ -725,7 +728,7 @@ class murmur3_x86_32:
725
728
  """Initialize an incremental x86 32-bit hash state.
726
729
 
727
730
  Args:
728
- data: Optional initial contiguous bytes-like data.
731
+ data: Optional initial bytes-like data.
729
732
  seed: Initial unsigned 32-bit seed.
730
733
 
731
734
  Raises:
@@ -738,7 +741,7 @@ class murmur3_x86_32:
738
741
  """Append bytes to the hash state.
739
742
 
740
743
  Args:
741
- data: Contiguous bytes-like data to append.
744
+ data: Bytes-like data to append.
742
745
 
743
746
  Returns:
744
747
  None.
@@ -804,11 +807,12 @@ class murmur3_x86_32:
804
807
  """Return the algorithm name (murmur3_x86_32)."""
805
808
  ...
806
809
 
810
+ @final
807
811
  class murmur3_x86_128:
808
812
  """Incremental MurmurHash3 x86 128-bit hasher.
809
813
 
810
814
  Args:
811
- data: Optional initial contiguous bytes-like data.
815
+ data: Optional initial bytes-like data.
812
816
  seed: Initial unsigned 32-bit seed.
813
817
 
814
818
  Examples:
@@ -822,7 +826,7 @@ class murmur3_x86_128:
822
826
  """Initialize an incremental x86 128-bit hash state.
823
827
 
824
828
  Args:
825
- data: Optional initial contiguous bytes-like data.
829
+ data: Optional initial bytes-like data.
826
830
  seed: Initial unsigned 32-bit seed.
827
831
 
828
832
  Raises:
@@ -835,7 +839,7 @@ class murmur3_x86_128:
835
839
  """Append bytes to the hash state.
836
840
 
837
841
  Args:
838
- data: Contiguous bytes-like data to append.
842
+ data: Bytes-like data to append.
839
843
 
840
844
  Returns:
841
845
  None.
@@ -901,11 +905,12 @@ class murmur3_x86_128:
901
905
  """Return the algorithm name (murmur3_x86_128)."""
902
906
  ...
903
907
 
908
+ @final
904
909
  class murmur3_x64_128:
905
910
  """Incremental MurmurHash3 x64 128-bit hasher.
906
911
 
907
912
  Args:
908
- data: Optional initial contiguous bytes-like data.
913
+ data: Optional initial bytes-like data.
909
914
  seed: Initial unsigned 32-bit seed.
910
915
 
911
916
  Examples:
@@ -920,7 +925,7 @@ class murmur3_x64_128:
920
925
  """Initialize an incremental x64 128-bit hash state.
921
926
 
922
927
  Args:
923
- data: Optional initial contiguous bytes-like data.
928
+ data: Optional initial bytes-like data.
924
929
  seed: Initial unsigned 32-bit seed.
925
930
 
926
931
  Raises:
@@ -933,7 +938,7 @@ class murmur3_x64_128:
933
938
  """Append bytes to the hash state.
934
939
 
935
940
  Args:
936
- data: Contiguous bytes-like data to append.
941
+ data: Bytes-like data to append.
937
942
 
938
943
  Returns:
939
944
  None.
@@ -0,0 +1,86 @@
1
+ """Hatchling hook that packages the in-tree PyO3 extension."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import platform
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
14
+ from packaging.tags import sys_tags
15
+
16
+
17
+ class CustomBuildHook(BuildHookInterface[Any]):
18
+ """Build the CPython extension wheel."""
19
+
20
+ def initialize(self, version: str, build_data: dict[str, Any]) -> None:
21
+ if self.target_name != 'wheel':
22
+ return
23
+
24
+ root = Path(self.root)
25
+ wheel_tag = self._wheel_tag()
26
+ env = os.environ.copy()
27
+ configured_target_dir = env.get('CARGO_TARGET_DIR') or env.get('CARGO_LLVM_COV_TARGET_DIR')
28
+ if configured_target_dir:
29
+ target_dir = Path(configured_target_dir)
30
+ if not target_dir.is_absolute():
31
+ target_dir = root / target_dir
32
+ else:
33
+ target_dir = root / 'target' / 'hatch' / wheel_tag
34
+ env['CARGO_TARGET_DIR'] = str(target_dir)
35
+ env['PYO3_PYTHON'] = sys.executable
36
+ result = subprocess.run(
37
+ [
38
+ 'cargo',
39
+ 'build',
40
+ '--locked',
41
+ '--manifest-path',
42
+ str(root / 'Cargo.toml'),
43
+ '--release',
44
+ '--features',
45
+ 'extension-module',
46
+ '--message-format=json-render-diagnostics',
47
+ ],
48
+ check=False,
49
+ cwd=root,
50
+ env=env,
51
+ stdout=subprocess.PIPE,
52
+ text=True,
53
+ )
54
+ result.check_returncode()
55
+
56
+ extension = self._cdylib_artifact(result.stdout)
57
+ build_data['force_include'] = {str(extension): f'hashcodecs/_hashcodecs{self._extension_suffix()}'}
58
+ build_data['pure_python'] = False
59
+ build_data['tag'] = wheel_tag
60
+
61
+ @staticmethod
62
+ def _cdylib_artifact(messages: str) -> Path:
63
+ suffixes = {'.dll', '.dylib', '.so'}
64
+ for line in messages.splitlines():
65
+ try:
66
+ message = json.loads(line)
67
+ except json.JSONDecodeError:
68
+ continue
69
+ if (
70
+ message.get('reason') == 'compiler-artifact'
71
+ and message.get('target', {}).get('name') == 'hashcodecs'
72
+ and 'cdylib' in message.get('target', {}).get('crate_types', [])
73
+ ):
74
+ for filename in message.get('filenames', []):
75
+ artifact = Path(filename)
76
+ if artifact.suffix in suffixes:
77
+ return artifact
78
+ raise RuntimeError('Cargo did not report the hashcodecs cdylib artifact')
79
+
80
+ @staticmethod
81
+ def _extension_suffix() -> str:
82
+ return '.pyd' if platform.system() == 'Windows' else '.so'
83
+
84
+ @staticmethod
85
+ def _wheel_tag() -> str:
86
+ return str(next(sys_tags()))
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hashcodecs"
7
- version = "1.2.0"
7
+ version = "1.2.1"
8
8
  description = "SIMD-accelerated Base64, MurmurHash3, and xxHash codecs"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -44,9 +44,9 @@ dev = [
44
44
  "ruff>=0.16,<0.17",
45
45
  ]
46
46
  benchmark = [
47
- "mmh3>=5.2.1,<6",
48
- "pybase64>=1.4.3,<2",
49
- "xxhash>=3.6,<4",
47
+ "mmh3==5.2.1",
48
+ "pybase64==1.4.3",
49
+ "xxhash==3.8.1",
50
50
  ]
51
51
 
52
52
  [tool.hatch.build.targets.wheel]
@@ -71,8 +71,6 @@ include = [
71
71
  "/src",
72
72
  "/tools",
73
73
  ]
74
- exclude = ["/src/base64/tests.rs"]
75
-
76
74
  [tool.hatch.build.hooks.custom]
77
75
  path = "hatch_build.py"
78
76
 
@@ -101,6 +101,7 @@ fn detect() -> Capabilities {
101
101
  {
102
102
  x86_capabilities(
103
103
  std::is_x86_feature_detected!("avx512vbmi"),
104
+ std::is_x86_feature_detected!("avx512bw"),
104
105
  std::is_x86_feature_detected!("avx512f"),
105
106
  std::is_x86_feature_detected!("avx2"),
106
107
  std::is_x86_feature_detected!("sse4.1"),
@@ -121,6 +122,7 @@ fn detect() -> Capabilities {
121
122
  #[cfg(any(test, target_arch = "x86", target_arch = "x86_64"))]
122
123
  fn x86_capabilities(
123
124
  avx512_vbmi: bool,
125
+ avx512_bw: bool,
124
126
  avx512: bool,
125
127
  avx2: bool,
126
128
  sse41: bool,
@@ -143,7 +145,7 @@ fn x86_capabilities(
143
145
  if avx512 && avx2 && ssse3 {
144
146
  simd |= SimdBackend::Avx512.bit();
145
147
  }
146
- if avx512 && avx512_vbmi && avx2 && ssse3 {
148
+ if avx512 && avx512_bw && avx512_vbmi && avx2 && ssse3 {
147
149
  simd |= SimdBackend::Avx512Vbmi.bit();
148
150
  }
149
151
  Capabilities { simd, bmi2 }
@@ -163,40 +165,43 @@ mod tests {
163
165
 
164
166
  #[test]
165
167
  fn x86_detection_tracks_independent_feature_sets() {
166
- let scalar = x86_capabilities(false, false, false, false, false, false);
168
+ let scalar = x86_capabilities(false, false, false, false, false, false, false);
167
169
  assert_eq!(scalar.best(&[SimdBackend::Avx2]), SimdBackend::Scalar);
168
170
 
169
- let ssse3 = x86_capabilities(false, false, false, false, true, false);
171
+ let ssse3 = x86_capabilities(false, false, false, false, false, true, false);
170
172
  assert!(ssse3.supports(SimdBackend::Ssse3));
171
173
  assert!(!ssse3.supports(SimdBackend::Sse41));
172
174
 
173
- let incomplete_sse41 = x86_capabilities(false, false, false, true, false, false);
175
+ let incomplete_sse41 = x86_capabilities(false, false, false, false, true, false, false);
174
176
  assert!(!incomplete_sse41.supports(SimdBackend::Sse41));
175
177
 
176
- let sse41 = x86_capabilities(false, false, false, true, true, false);
178
+ let sse41 = x86_capabilities(false, false, false, false, true, true, false);
177
179
  assert!(sse41.supports(SimdBackend::Sse41));
178
180
 
179
- let incomplete_avx2 = x86_capabilities(false, false, true, false, false, true);
181
+ let incomplete_avx2 = x86_capabilities(false, false, false, true, false, false, true);
180
182
  assert!(!incomplete_avx2.supports(SimdBackend::Avx2));
181
- let avx2 = x86_capabilities(false, false, true, false, true, true);
183
+ let avx2 = x86_capabilities(false, false, false, true, false, true, true);
182
184
  assert!(avx2.supports(SimdBackend::Avx2));
183
185
  assert!(!avx2.supports(SimdBackend::Sse41));
184
186
  assert!(avx2.has_bmi2());
185
187
 
186
- let avx512 = x86_capabilities(false, true, false, false, false, false);
188
+ let avx512 = x86_capabilities(false, false, true, false, false, false, false);
187
189
  assert!(!avx512.supports(SimdBackend::Avx512));
188
190
  assert!(!avx512.supports(SimdBackend::Avx2));
189
191
  assert!(!avx512.supports(SimdBackend::Avx512Vbmi));
190
192
 
191
- let avx512 = x86_capabilities(false, true, true, false, true, false);
193
+ let avx512 = x86_capabilities(false, false, true, true, false, true, false);
192
194
  assert!(avx512.supports(SimdBackend::Avx512));
193
195
  assert!(avx512.supports(SimdBackend::Avx2));
194
196
  assert!(!avx512.supports(SimdBackend::Avx512Vbmi));
195
197
 
196
- let incomplete_vbmi = x86_capabilities(true, false, false, false, false, false);
198
+ let incomplete_bw = x86_capabilities(true, false, true, true, true, true, true);
199
+ assert!(!incomplete_bw.supports(SimdBackend::Avx512Vbmi));
200
+
201
+ let incomplete_vbmi = x86_capabilities(false, true, true, true, true, true, true);
197
202
  assert!(!incomplete_vbmi.supports(SimdBackend::Avx512Vbmi));
198
203
 
199
- let vbmi = x86_capabilities(true, true, true, true, true, true);
204
+ let vbmi = x86_capabilities(true, true, true, true, true, true, true);
200
205
  assert_eq!(
201
206
  vbmi.best(&[
202
207
  SimdBackend::Avx512Vbmi,
@@ -31,7 +31,7 @@ const fn decode_shuffle() -> [u8; 64] {
31
31
  shuffle
32
32
  }
33
33
 
34
- #[target_feature(enable = "avx512vbmi")]
34
+ #[target_feature(enable = "avx512vbmi,avx512bw")]
35
35
  pub(in crate::base64) unsafe fn decode<A: Decoder, S: Store>(
36
36
  input: &[u8],
37
37
  output: *mut u8,
@@ -100,7 +100,7 @@ pub(in crate::base64) unsafe fn decode<A: Decoder, S: Store>(
100
100
  }
101
101
  }
102
102
 
103
- #[target_feature(enable = "avx512vbmi")]
103
+ #[target_feature(enable = "avx512vbmi,avx512bw")]
104
104
  #[inline]
105
105
  unsafe fn decode_64(
106
106
  input: *const u8,
@@ -24,7 +24,8 @@ use super::{
24
24
  ///
25
25
  /// The input must be quartet-aligned, use only the standard alphabet, and have
26
26
  /// valid trailing padding. Unlike Python's lenient decoder, this Rust API does
27
- /// not ignore whitespace or other non-alphabet bytes.
27
+ /// not ignore whitespace or other non-alphabet bytes. Non-zero unused bits in
28
+ /// the final quantum are accepted rather than treated as a canonicality error.
28
29
  ///
29
30
  /// # Arguments
30
31
  ///
@@ -54,7 +55,8 @@ pub fn b64decode(input: &[u8]) -> Result<Vec<u8>, Base64Error> {
54
55
  /// Decodes padded RFC 4648 Base64 with the URL-safe alphabet.
55
56
  ///
56
57
  /// The input must be quartet-aligned, use only the URL-safe alphabet, and have
57
- /// valid trailing padding.
58
+ /// valid trailing padding. Non-zero unused bits in the final quantum are
59
+ /// accepted rather than treated as a canonicality error.
58
60
  ///
59
61
  /// # Arguments
60
62
  ///
@@ -259,6 +261,7 @@ pub(crate) fn decode_unpadded_layout(input: &[u8]) -> Result<DecodeLayout, Base6
259
261
  .and_then(|length| length.checked_add(tail_len))
260
262
  .ok_or(Base64Error::InvalidInput)?;
261
263
  Ok(DecodeLayout {
264
+ input_len: input.len(),
262
265
  padding: 0,
263
266
  output_len,
264
267
  })
@@ -286,7 +289,8 @@ pub(crate) fn decode_to_slice_with_layout_and_alphabet(
286
289
  layout: DecodeLayout,
287
290
  alphabet: DecodeAlphabet,
288
291
  ) -> Result<(), Base64Error> {
289
- debug_assert_eq!(output.len(), layout.output_len);
292
+ assert_eq!(input.len(), layout.input_len);
293
+ assert_eq!(output.len(), layout.output_len);
290
294
  // The slice has exactly the required initialized storage, so only bounded
291
295
  // stores are permitted.
292
296
  unsafe { decode_to_ptr_with_layout(input, output.as_mut_ptr(), layout, alphabet, false) }
@@ -300,7 +304,8 @@ pub(crate) fn decode_to_slice_with_unpadded_layout_and_alphabet(
300
304
  layout: DecodeLayout,
301
305
  alphabet: DecodeAlphabet,
302
306
  ) -> Result<(), Base64Error> {
303
- debug_assert_eq!(output.len(), layout.output_len);
307
+ assert_eq!(input.len(), layout.input_len);
308
+ assert_eq!(output.len(), layout.output_len);
304
309
  unsafe {
305
310
  decode_to_ptr_with_unpadded_layout_mode(input, output.as_mut_ptr(), layout, alphabet, false)
306
311
  }
@@ -314,7 +319,8 @@ pub(crate) fn decode_to_slice_with_unpadded_layout_and_alphabet_transactional(
314
319
  layout: DecodeLayout,
315
320
  alphabet: DecodeAlphabet,
316
321
  ) -> Result<(), Base64Error> {
317
- debug_assert_eq!(output.len(), layout.output_len);
322
+ assert_eq!(input.len(), layout.input_len);
323
+ assert_eq!(output.len(), layout.output_len);
318
324
  unsafe {
319
325
  decode_to_ptr_with_unpadded_layout_mode(input, output.as_mut_ptr(), layout, alphabet, true)
320
326
  }
@@ -350,7 +356,8 @@ pub(crate) fn decode_to_slice_with_layout_and_alphabet_transactional(
350
356
  layout: DecodeLayout,
351
357
  alphabet: DecodeAlphabet,
352
358
  ) -> Result<(), Base64Error> {
353
- debug_assert_eq!(output.len(), layout.output_len);
359
+ assert_eq!(input.len(), layout.input_len);
360
+ assert_eq!(output.len(), layout.output_len);
354
361
  // Transactional SIMD error handling writes only complete, validated blocks,
355
362
  // so a lenient caller can safely fall back without modifying the suffix.
356
363
  unsafe {
@@ -434,6 +441,7 @@ unsafe fn decode_to_ptr_with_unpadded_layout_mode(
434
441
  ) -> Result<(), Base64Error> {
435
442
  let prefix_len = input.len() / 4 * 4;
436
443
  let prefix_layout = DecodeLayout {
444
+ input_len: prefix_len,
437
445
  padding: 0,
438
446
  output_len: prefix_len / 4 * 3,
439
447
  };
@@ -468,6 +476,7 @@ unsafe fn decode_to_ptr_with_unpadded_layout_mode(
468
476
  pub(crate) fn decode_layout(input: &[u8]) -> Result<DecodeLayout, Base64Error> {
469
477
  if input.is_empty() {
470
478
  return Ok(DecodeLayout {
479
+ input_len: 0,
471
480
  padding: 0,
472
481
  output_len: 0,
473
482
  });
@@ -482,6 +491,7 @@ pub(crate) fn decode_layout(input: &[u8]) -> Result<DecodeLayout, Base64Error> {
482
491
  _ => 0,
483
492
  };
484
493
  Ok(DecodeLayout {
494
+ input_len: input.len(),
485
495
  padding,
486
496
  output_len: input.len() / 4 * 3 - padding,
487
497
  })
@@ -489,8 +499,17 @@ pub(crate) fn decode_layout(input: &[u8]) -> Result<DecodeLayout, Base64Error> {
489
499
 
490
500
  #[derive(Clone, Copy)]
491
501
  pub(crate) struct DecodeLayout {
492
- pub(crate) padding: usize,
493
- pub(crate) output_len: usize,
502
+ input_len: usize,
503
+ padding: usize,
504
+ output_len: usize,
505
+ }
506
+
507
+ impl DecodeLayout {
508
+ #[inline(always)]
509
+ #[cfg(any(feature = "python", test))]
510
+ pub(crate) fn output_len(self) -> usize {
511
+ self.output_len
512
+ }
494
513
  }
495
514
 
496
515
  pub(crate) unsafe fn decode_quad_ptr(
@@ -28,7 +28,7 @@ const fn encode_shuffle() -> [u8; 64] {
28
28
  shuffle
29
29
  }
30
30
 
31
- #[target_feature(enable = "avx512vbmi")]
31
+ #[target_feature(enable = "avx512vbmi,avx512bw")]
32
32
  pub(in crate::base64) unsafe fn encode<const URLSAFE: bool>(
33
33
  input: &[u8],
34
34
  output: *mut u8,
@@ -116,7 +116,7 @@ pub(in crate::base64) unsafe fn encode<const URLSAFE: bool>(
116
116
  }
117
117
  }
118
118
 
119
- #[target_feature(enable = "avx512vbmi")]
119
+ #[target_feature(enable = "avx512vbmi,avx512bw")]
120
120
  #[inline]
121
121
  unsafe fn encode_48(
122
122
  input: *const u8,
@@ -489,10 +489,10 @@ fn invalid_scalar_checkpoint_tail<const URLSAFE: bool, const MIXED: bool>() {
489
489
 
490
490
  let output_offset = (input_offset * 7 + invalid_index) & 15;
491
491
  let output_start = GUARD + output_offset;
492
- let mut guarded_output = vec![CANARY; output_start + layout.output_len + GUARD];
492
+ let mut guarded_output = vec![CANARY; output_start + layout.output_len() + GUARD];
493
493
  let result = super::super::decode_to_slice_with_layout_and_alphabet(
494
494
  input,
495
- &mut guarded_output[output_start..output_start + layout.output_len],
495
+ &mut guarded_output[output_start..output_start + layout.output_len()],
496
496
  layout,
497
497
  decode_alphabet::<URLSAFE, MIXED>(),
498
498
  );