PyPcre 0.6.4__tar.gz → 0.6.6__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 (86) hide show
  1. {pypcre-0.6.4 → pypcre-0.6.6}/PKG-INFO +26 -3
  2. {pypcre-0.6.4 → pypcre-0.6.6}/PyPcre.egg-info/PKG-INFO +26 -3
  3. {pypcre-0.6.4 → pypcre-0.6.6}/PyPcre.egg-info/SOURCES.txt +5 -0
  4. {pypcre-0.6.4 → pypcre-0.6.6}/README.md +25 -2
  5. {pypcre-0.6.4 → pypcre-0.6.6}/pcre/__init__.py +2 -0
  6. {pypcre-0.6.4 → pypcre-0.6.6}/pcre/cache.py +4 -5
  7. {pypcre-0.6.4 → pypcre-0.6.6}/pcre/pcre.py +167 -96
  8. {pypcre-0.6.4 → pypcre-0.6.6}/pcre/threads.py +21 -1
  9. {pypcre-0.6.4 → pypcre-0.6.6}/pcre_ext/cache.c +18 -8
  10. {pypcre-0.6.4 → pypcre-0.6.6}/pcre_ext/error.c +8 -0
  11. {pypcre-0.6.4 → pypcre-0.6.6}/pcre_ext/memory.c +3 -1
  12. {pypcre-0.6.4 → pypcre-0.6.6}/pcre_ext/pcre2.c +423 -76
  13. {pypcre-0.6.4 → pypcre-0.6.6}/pcre_ext/pcre2_module.h +11 -0
  14. {pypcre-0.6.4 → pypcre-0.6.6}/pyproject.toml +1 -1
  15. {pypcre-0.6.4 → pypcre-0.6.6}/setup.py +6 -1
  16. {pypcre-0.6.4 → pypcre-0.6.6}/setup_utils.py +80 -3
  17. pypcre-0.6.6/tests/test_build_hardening.py +162 -0
  18. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_cache_scope_safety.py +70 -0
  19. pypcre-0.6.6/tests/test_capture_limits.py +39 -0
  20. pypcre-0.6.6/tests/test_lastindex_replay_and_jit_stack_fallback.py +191 -0
  21. pypcre-0.6.6/tests/test_literal_capture_sub_fastpath.py +64 -0
  22. pypcre-0.6.6/tests/test_match_limits.py +152 -0
  23. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_python_coverage_audit.py +12 -4
  24. {pypcre-0.6.4 → pypcre-0.6.6}/LICENSE +0 -0
  25. {pypcre-0.6.4 → pypcre-0.6.6}/MANIFEST.in +0 -0
  26. {pypcre-0.6.4 → pypcre-0.6.6}/PyPcre.egg-info/dependency_links.txt +0 -0
  27. {pypcre-0.6.4 → pypcre-0.6.6}/PyPcre.egg-info/top_level.txt +0 -0
  28. {pypcre-0.6.4 → pypcre-0.6.6}/pcre/_stdlib_re.py +0 -0
  29. {pypcre-0.6.4 → pypcre-0.6.6}/pcre/error.py +0 -0
  30. {pypcre-0.6.4 → pypcre-0.6.6}/pcre/flags.py +0 -0
  31. {pypcre-0.6.4 → pypcre-0.6.6}/pcre/re_compat.py +0 -0
  32. {pypcre-0.6.4 → pypcre-0.6.6}/pcre_ext/atomic_compat.h +0 -0
  33. {pypcre-0.6.4 → pypcre-0.6.6}/pcre_ext/flag.c +0 -0
  34. {pypcre-0.6.4 → pypcre-0.6.6}/pcre_ext/jit_support.c +0 -0
  35. {pypcre-0.6.4 → pypcre-0.6.6}/pcre_ext/pattern_cache.c +0 -0
  36. {pypcre-0.6.4 → pypcre-0.6.6}/pcre_ext/pcre2.h +0 -0
  37. {pypcre-0.6.4 → pypcre-0.6.6}/pcre_ext/string_helpers.c +0 -0
  38. {pypcre-0.6.4 → pypcre-0.6.6}/pcre_ext/util.c +0 -0
  39. {pypcre-0.6.4 → pypcre-0.6.6}/setup.cfg +0 -0
  40. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_accuracy.py +0 -0
  41. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_api_parity.py +0 -0
  42. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_basic.py +0 -0
  43. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_bench_string.py +0 -0
  44. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_benchmark.py +0 -0
  45. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_bytes.py +0 -0
  46. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_c_api_audit.py +0 -0
  47. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_cache.py +0 -0
  48. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_cache_global.py +0 -0
  49. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_cache_global_inproc.py +0 -0
  50. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_clobber.py +0 -0
  51. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_clobber_thread.py +0 -0
  52. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_clobber_verify.py +0 -0
  53. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_compile_utf_no_check_safety.py +0 -0
  54. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_core.py +0 -0
  55. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_coverage_gaps.py +0 -0
  56. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_errors.py +0 -0
  57. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_escape_fastpath.py +0 -0
  58. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_expand_fastpath.py +0 -0
  59. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_flags.py +0 -0
  60. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_gil_zero_safety.py +0 -0
  61. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_jit.py +0 -0
  62. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_jit_anchoring.py +0 -0
  63. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_jit_start_optimize_workaround.py +0 -0
  64. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_lastindex_ovector_fastpath.py +0 -0
  65. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_lastindex_semantics.py +0 -0
  66. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_literal_capture_findall_fastpath.py +0 -0
  67. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_literal_capture_split_fastpath.py +0 -0
  68. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_memory.py +0 -0
  69. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_mmap.py +0 -0
  70. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_module.py +0 -0
  71. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_multi_literal_capture_findall_fastpath.py +0 -0
  72. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_multi_literal_capture_split_fastpath.py +0 -0
  73. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_pattern.py +0 -0
  74. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_re_compat.py +0 -0
  75. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_setup_msvc_pkg_config.py +0 -0
  76. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_setup_utils_multiarch.py +0 -0
  77. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_simd.py +0 -0
  78. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_stdlib_flag_fastpath.py +0 -0
  79. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_sub_count_one_fastpath.py +0 -0
  80. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_sub_reference_fastpath.py +0 -0
  81. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_template_dispatch_fastpath.py +0 -0
  82. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_threaded_backend.py +0 -0
  83. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_threads.py +0 -0
  84. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_transformers_regex.py +0 -0
  85. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_utf8.py +0 -0
  86. {pypcre-0.6.4 → pypcre-0.6.6}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPcre
3
- Version: 0.6.4
3
+ Version: 0.6.6
4
4
  Summary: Modern, GIL-friendly, Fast Python bindings for PCRE2 with auto caching and JIT of compiled patterns.
5
5
  Author-email: ModelCloud <qubitium@modelcloud.ai>
6
6
  License-Expression: Apache-2.0
@@ -47,7 +47,8 @@ Fast, free-threaded Python bindings for `PCRE2` with a stable `stdlib.re`-compat
47
47
 
48
48
 
49
49
  ## Latest News 🚀
50
- * 10/20/2026 **[0.6.4](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.4)**: Fixed free-threaded races in `Match.lastindex` replay and PCRE2 version initialization, reduced Python dispatch overhead across module-level and compiled-pattern `match`, `search`, `fullmatch`, `findall`, `finditer`, `split`, `sub`, and `subn` hot paths, and prevented native Windows MSVC builds from inheriting MinGW `pkg-config` flags. CI now uses interpreter-scoped pip/pytest bootstraps and the matching FreeBSD Python pip package. 🛡️🧵🚀⚡🪟🐧
50
+ * 10/21/2026 **[0.6.5](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.5)**: Inlined the generation-validated module hot lookup for faster module-level `match`, `search`, `fullmatch`, `findall`, and `finditer`, while preserving legacy-backend, cache, thread-default, and free-threaded fallbacks. 🚀⚡🧵
51
+ * 10/20/2026 **[0.6.4](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.4)**: Fixed free-threaded races in `Match.lastindex` replay and PCRE2 version initialization, reduced Python dispatch overhead across module-level and compiled-pattern `match`, `search`, `fullmatch`, `findall`, `finditer`, `split`, `sub`, and `subn` hot paths, added generation-validated module caches and exact literal-capture substitution paths, and prevented native Windows MSVC builds from inheriting MinGW `pkg-config` flags. CI now uses interpreter-scoped pip/pytest bootstraps and the matching FreeBSD Python pip package. 🛡️🧵🚀⚡🪟🐧
51
52
  * 08/25/2026 **[0.6.2](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.2)**: Hardened packaging metadata and fixed FreeBSD/WSL CI failures on PCRE2 10.42 runtimes, including legacy replacement-template and JIT-fallback compatibility. 🛠️🐧🪟
52
53
  * 08/25/2026 **[0.6.1](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.1)**: Workaround for PCRE2 10.46/10.47 JIT start-optimization regressions, new verifying clobber suite, second memory/thread-safety sweep, Python 3.15 `sre_parse` removal compatibility, aligned `Pattern` `pos`/`endpos` signatures, and `setuptools` metadata fixes. 🛡️🧵🐍
53
54
  * 08/09/2026 **[0.6.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.0)**: `findall`, `finditer`, `sub`/`subn`, `split`, and `match`/`search`/`fullmatch` hot-path speedups up to **46x** vs `stdlib.re` and **48x** vs `regex`, with free-threaded `findall` reaching **13.8x** on 8 threads. 🚀⚡
@@ -255,7 +256,8 @@ numbers = pattern.findall(b"line 1\nline 22")
255
256
 
256
257
  - Module helpers: `prefixmatch`, `match`, `search`, `fullmatch`, `finditer`,
257
258
  `findall`, `split`, `sub`, `subn`, `compile`, `escape`, `purge`,
258
- `template`, `parallel_map`, `configure`, `configure_threads`,
259
+ `template`, `parallel_map`, `configure`, `configure_match_limits`,
260
+ `configure_threads`,
259
261
  `configure_thread_pool`, `shutdown_thread_pool`, `set_cache_limit`,
260
262
  `get_cache_limit`, and `clear_cache`.
261
263
  - `compile()` returns a `Pattern` object with the familiar matching helpers
@@ -270,6 +272,12 @@ numbers = pattern.findall(b"line 1\nline 22")
270
272
  `NOFLAG`.
271
273
  - Errors are raised as `pcre.PcreError`; `error` and `PatternError` are kept as
272
274
  compatibility aliases.
275
+ - Buffer-protocol subjects (`mmap.mmap`, `bytearray`, `array.array`,
276
+ `memoryview`) are accepted anywhere `bytes` is. Each call takes an immutable
277
+ snapshot of the buffer before matching, so concurrent writers (or another
278
+ process writing a shared mapping) cannot corrupt a match in progress. That
279
+ snapshot is a full copy: searching a 200 MB `mmap` allocates 200 MB per call.
280
+ For very large inputs, match over slices or chunks instead of the whole map.
273
281
 
274
282
  ### Common examples 🧪
275
283
 
@@ -367,10 +375,25 @@ the conversion without repeating the flag.
367
375
  workers' high-level helper caches on their next cache-backed call. Backend
368
376
  scratch buffers remain thread-scoped and are released when that thread exits.
369
377
  Oversized patterns and templates are not retained.
378
+ - 🛑 `pcre.configure_match_limits(match_limit=..., depth_limit=..., heap_limit=...)`
379
+ bounds the work any single match may do, process-wide (PCRE2's
380
+ `pcre2_set_match_limit` / `depth_limit` / `heap_limit`; the heap limit is in
381
+ KiB). A match that exceeds a limit raises `PcreError` (`MATCHLIMIT`,
382
+ `DEPTHLIMIT` or `HEAPLIMIT`) instead of running unbounded, which is useful
383
+ for untrusted patterns or subjects. `0` restores the PCRE2 build default.
384
+ PCRE2 counts the match limit per start position, so a search over `n`
385
+ characters can still take up to `n × match_limit` steps.
370
386
  - 🧵 `pcre.configure_threads()`, `pcre.configure_thread_pool()`,
371
387
  `shutdown_thread_pool()`, `Flag.THREADS`, and `Flag.NO_THREADS` are available
372
388
  if you want to opt into or restrict threaded execution.
373
389
 
390
+ ## Documentation 📚
391
+
392
+ - [`lastindex` replay and JIT stack fallback](docs/lastindex-and-jit-stack.md) —
393
+ how `Match.lastindex`/`lastgroup` are reconstructed, why the replay must use
394
+ the original start offset and options, and how an exhausted JIT stack falls
395
+ back to the interpreter instead of raising.
396
+
374
397
  ## Building 🏗️
375
398
 
376
399
  The extension links against an existing `libpcre2-8` installation. Install the development headers for your platform before building,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPcre
3
- Version: 0.6.4
3
+ Version: 0.6.6
4
4
  Summary: Modern, GIL-friendly, Fast Python bindings for PCRE2 with auto caching and JIT of compiled patterns.
5
5
  Author-email: ModelCloud <qubitium@modelcloud.ai>
6
6
  License-Expression: Apache-2.0
@@ -47,7 +47,8 @@ Fast, free-threaded Python bindings for `PCRE2` with a stable `stdlib.re`-compat
47
47
 
48
48
 
49
49
  ## Latest News 🚀
50
- * 10/20/2026 **[0.6.4](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.4)**: Fixed free-threaded races in `Match.lastindex` replay and PCRE2 version initialization, reduced Python dispatch overhead across module-level and compiled-pattern `match`, `search`, `fullmatch`, `findall`, `finditer`, `split`, `sub`, and `subn` hot paths, and prevented native Windows MSVC builds from inheriting MinGW `pkg-config` flags. CI now uses interpreter-scoped pip/pytest bootstraps and the matching FreeBSD Python pip package. 🛡️🧵🚀⚡🪟🐧
50
+ * 10/21/2026 **[0.6.5](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.5)**: Inlined the generation-validated module hot lookup for faster module-level `match`, `search`, `fullmatch`, `findall`, and `finditer`, while preserving legacy-backend, cache, thread-default, and free-threaded fallbacks. 🚀⚡🧵
51
+ * 10/20/2026 **[0.6.4](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.4)**: Fixed free-threaded races in `Match.lastindex` replay and PCRE2 version initialization, reduced Python dispatch overhead across module-level and compiled-pattern `match`, `search`, `fullmatch`, `findall`, `finditer`, `split`, `sub`, and `subn` hot paths, added generation-validated module caches and exact literal-capture substitution paths, and prevented native Windows MSVC builds from inheriting MinGW `pkg-config` flags. CI now uses interpreter-scoped pip/pytest bootstraps and the matching FreeBSD Python pip package. 🛡️🧵🚀⚡🪟🐧
51
52
  * 08/25/2026 **[0.6.2](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.2)**: Hardened packaging metadata and fixed FreeBSD/WSL CI failures on PCRE2 10.42 runtimes, including legacy replacement-template and JIT-fallback compatibility. 🛠️🐧🪟
52
53
  * 08/25/2026 **[0.6.1](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.1)**: Workaround for PCRE2 10.46/10.47 JIT start-optimization regressions, new verifying clobber suite, second memory/thread-safety sweep, Python 3.15 `sre_parse` removal compatibility, aligned `Pattern` `pos`/`endpos` signatures, and `setuptools` metadata fixes. 🛡️🧵🐍
53
54
  * 08/09/2026 **[0.6.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.0)**: `findall`, `finditer`, `sub`/`subn`, `split`, and `match`/`search`/`fullmatch` hot-path speedups up to **46x** vs `stdlib.re` and **48x** vs `regex`, with free-threaded `findall` reaching **13.8x** on 8 threads. 🚀⚡
@@ -255,7 +256,8 @@ numbers = pattern.findall(b"line 1\nline 22")
255
256
 
256
257
  - Module helpers: `prefixmatch`, `match`, `search`, `fullmatch`, `finditer`,
257
258
  `findall`, `split`, `sub`, `subn`, `compile`, `escape`, `purge`,
258
- `template`, `parallel_map`, `configure`, `configure_threads`,
259
+ `template`, `parallel_map`, `configure`, `configure_match_limits`,
260
+ `configure_threads`,
259
261
  `configure_thread_pool`, `shutdown_thread_pool`, `set_cache_limit`,
260
262
  `get_cache_limit`, and `clear_cache`.
261
263
  - `compile()` returns a `Pattern` object with the familiar matching helpers
@@ -270,6 +272,12 @@ numbers = pattern.findall(b"line 1\nline 22")
270
272
  `NOFLAG`.
271
273
  - Errors are raised as `pcre.PcreError`; `error` and `PatternError` are kept as
272
274
  compatibility aliases.
275
+ - Buffer-protocol subjects (`mmap.mmap`, `bytearray`, `array.array`,
276
+ `memoryview`) are accepted anywhere `bytes` is. Each call takes an immutable
277
+ snapshot of the buffer before matching, so concurrent writers (or another
278
+ process writing a shared mapping) cannot corrupt a match in progress. That
279
+ snapshot is a full copy: searching a 200 MB `mmap` allocates 200 MB per call.
280
+ For very large inputs, match over slices or chunks instead of the whole map.
273
281
 
274
282
  ### Common examples 🧪
275
283
 
@@ -367,10 +375,25 @@ the conversion without repeating the flag.
367
375
  workers' high-level helper caches on their next cache-backed call. Backend
368
376
  scratch buffers remain thread-scoped and are released when that thread exits.
369
377
  Oversized patterns and templates are not retained.
378
+ - 🛑 `pcre.configure_match_limits(match_limit=..., depth_limit=..., heap_limit=...)`
379
+ bounds the work any single match may do, process-wide (PCRE2's
380
+ `pcre2_set_match_limit` / `depth_limit` / `heap_limit`; the heap limit is in
381
+ KiB). A match that exceeds a limit raises `PcreError` (`MATCHLIMIT`,
382
+ `DEPTHLIMIT` or `HEAPLIMIT`) instead of running unbounded, which is useful
383
+ for untrusted patterns or subjects. `0` restores the PCRE2 build default.
384
+ PCRE2 counts the match limit per start position, so a search over `n`
385
+ characters can still take up to `n × match_limit` steps.
370
386
  - 🧵 `pcre.configure_threads()`, `pcre.configure_thread_pool()`,
371
387
  `shutdown_thread_pool()`, `Flag.THREADS`, and `Flag.NO_THREADS` are available
372
388
  if you want to opt into or restrict threaded execution.
373
389
 
390
+ ## Documentation 📚
391
+
392
+ - [`lastindex` replay and JIT stack fallback](docs/lastindex-and-jit-stack.md) —
393
+ how `Match.lastindex`/`lastgroup` are reconstructed, why the replay must use
394
+ the original start offset and options, and how an exhausted JIT stack falls
395
+ back to the interpreter instead of raising.
396
+
374
397
  ## Building 🏗️
375
398
 
376
399
  The extension links against an existing `libpcre2-8` installation. Install the development headers for your platform before building,
@@ -33,12 +33,14 @@ tests/test_api_parity.py
33
33
  tests/test_basic.py
34
34
  tests/test_bench_string.py
35
35
  tests/test_benchmark.py
36
+ tests/test_build_hardening.py
36
37
  tests/test_bytes.py
37
38
  tests/test_c_api_audit.py
38
39
  tests/test_cache.py
39
40
  tests/test_cache_global.py
40
41
  tests/test_cache_global_inproc.py
41
42
  tests/test_cache_scope_safety.py
43
+ tests/test_capture_limits.py
42
44
  tests/test_clobber.py
43
45
  tests/test_clobber_thread.py
44
46
  tests/test_clobber_verify.py
@@ -54,9 +56,12 @@ tests/test_jit.py
54
56
  tests/test_jit_anchoring.py
55
57
  tests/test_jit_start_optimize_workaround.py
56
58
  tests/test_lastindex_ovector_fastpath.py
59
+ tests/test_lastindex_replay_and_jit_stack_fallback.py
57
60
  tests/test_lastindex_semantics.py
58
61
  tests/test_literal_capture_findall_fastpath.py
59
62
  tests/test_literal_capture_split_fastpath.py
63
+ tests/test_literal_capture_sub_fastpath.py
64
+ tests/test_match_limits.py
60
65
  tests/test_memory.py
61
66
  tests/test_mmap.py
62
67
  tests/test_module.py
@@ -24,7 +24,8 @@ Fast, free-threaded Python bindings for `PCRE2` with a stable `stdlib.re`-compat
24
24
 
25
25
 
26
26
  ## Latest News 🚀
27
- * 10/20/2026 **[0.6.4](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.4)**: Fixed free-threaded races in `Match.lastindex` replay and PCRE2 version initialization, reduced Python dispatch overhead across module-level and compiled-pattern `match`, `search`, `fullmatch`, `findall`, `finditer`, `split`, `sub`, and `subn` hot paths, and prevented native Windows MSVC builds from inheriting MinGW `pkg-config` flags. CI now uses interpreter-scoped pip/pytest bootstraps and the matching FreeBSD Python pip package. 🛡️🧵🚀⚡🪟🐧
27
+ * 10/21/2026 **[0.6.5](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.5)**: Inlined the generation-validated module hot lookup for faster module-level `match`, `search`, `fullmatch`, `findall`, and `finditer`, while preserving legacy-backend, cache, thread-default, and free-threaded fallbacks. 🚀⚡🧵
28
+ * 10/20/2026 **[0.6.4](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.4)**: Fixed free-threaded races in `Match.lastindex` replay and PCRE2 version initialization, reduced Python dispatch overhead across module-level and compiled-pattern `match`, `search`, `fullmatch`, `findall`, `finditer`, `split`, `sub`, and `subn` hot paths, added generation-validated module caches and exact literal-capture substitution paths, and prevented native Windows MSVC builds from inheriting MinGW `pkg-config` flags. CI now uses interpreter-scoped pip/pytest bootstraps and the matching FreeBSD Python pip package. 🛡️🧵🚀⚡🪟🐧
28
29
  * 08/25/2026 **[0.6.2](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.2)**: Hardened packaging metadata and fixed FreeBSD/WSL CI failures on PCRE2 10.42 runtimes, including legacy replacement-template and JIT-fallback compatibility. 🛠️🐧🪟
29
30
  * 08/25/2026 **[0.6.1](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.1)**: Workaround for PCRE2 10.46/10.47 JIT start-optimization regressions, new verifying clobber suite, second memory/thread-safety sweep, Python 3.15 `sre_parse` removal compatibility, aligned `Pattern` `pos`/`endpos` signatures, and `setuptools` metadata fixes. 🛡️🧵🐍
30
31
  * 08/09/2026 **[0.6.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.6.0)**: `findall`, `finditer`, `sub`/`subn`, `split`, and `match`/`search`/`fullmatch` hot-path speedups up to **46x** vs `stdlib.re` and **48x** vs `regex`, with free-threaded `findall` reaching **13.8x** on 8 threads. 🚀⚡
@@ -232,7 +233,8 @@ numbers = pattern.findall(b"line 1\nline 22")
232
233
 
233
234
  - Module helpers: `prefixmatch`, `match`, `search`, `fullmatch`, `finditer`,
234
235
  `findall`, `split`, `sub`, `subn`, `compile`, `escape`, `purge`,
235
- `template`, `parallel_map`, `configure`, `configure_threads`,
236
+ `template`, `parallel_map`, `configure`, `configure_match_limits`,
237
+ `configure_threads`,
236
238
  `configure_thread_pool`, `shutdown_thread_pool`, `set_cache_limit`,
237
239
  `get_cache_limit`, and `clear_cache`.
238
240
  - `compile()` returns a `Pattern` object with the familiar matching helpers
@@ -247,6 +249,12 @@ numbers = pattern.findall(b"line 1\nline 22")
247
249
  `NOFLAG`.
248
250
  - Errors are raised as `pcre.PcreError`; `error` and `PatternError` are kept as
249
251
  compatibility aliases.
252
+ - Buffer-protocol subjects (`mmap.mmap`, `bytearray`, `array.array`,
253
+ `memoryview`) are accepted anywhere `bytes` is. Each call takes an immutable
254
+ snapshot of the buffer before matching, so concurrent writers (or another
255
+ process writing a shared mapping) cannot corrupt a match in progress. That
256
+ snapshot is a full copy: searching a 200 MB `mmap` allocates 200 MB per call.
257
+ For very large inputs, match over slices or chunks instead of the whole map.
250
258
 
251
259
  ### Common examples 🧪
252
260
 
@@ -344,10 +352,25 @@ the conversion without repeating the flag.
344
352
  workers' high-level helper caches on their next cache-backed call. Backend
345
353
  scratch buffers remain thread-scoped and are released when that thread exits.
346
354
  Oversized patterns and templates are not retained.
355
+ - 🛑 `pcre.configure_match_limits(match_limit=..., depth_limit=..., heap_limit=...)`
356
+ bounds the work any single match may do, process-wide (PCRE2's
357
+ `pcre2_set_match_limit` / `depth_limit` / `heap_limit`; the heap limit is in
358
+ KiB). A match that exceeds a limit raises `PcreError` (`MATCHLIMIT`,
359
+ `DEPTHLIMIT` or `HEAPLIMIT`) instead of running unbounded, which is useful
360
+ for untrusted patterns or subjects. `0` restores the PCRE2 build default.
361
+ PCRE2 counts the match limit per start position, so a search over `n`
362
+ characters can still take up to `n × match_limit` steps.
347
363
  - 🧵 `pcre.configure_threads()`, `pcre.configure_thread_pool()`,
348
364
  `shutdown_thread_pool()`, `Flag.THREADS`, and `Flag.NO_THREADS` are available
349
365
  if you want to opt into or restrict threaded execution.
350
366
 
367
+ ## Documentation 📚
368
+
369
+ - [`lastindex` replay and JIT stack fallback](docs/lastindex-and-jit-stack.md) —
370
+ how `Match.lastindex`/`lastgroup` are reconstructed, why the replay must use
371
+ the original start offset and options, and how an exhausted JIT stack falls
372
+ back to the interpreter instead of raising.
373
+
351
374
  ## Building 🏗️
352
375
 
353
376
  The extension links against an existing `libpcre2-8` installation. Install the development headers for your platform before building,
@@ -26,6 +26,7 @@ from .pcre import (
26
26
  clear_cache,
27
27
  compile,
28
28
  configure,
29
+ configure_match_limits,
29
30
  findall,
30
31
  finditer,
31
32
  fullmatch,
@@ -109,6 +110,7 @@ __all__ = [
109
110
  "clear_cache",
110
111
  "purge",
111
112
  "configure",
113
+ "configure_match_limits",
112
114
  "configure_threads",
113
115
  "configure_thread_pool",
114
116
  "cache_strategy",
@@ -7,7 +7,6 @@
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- import os
11
10
  from enum import Enum
12
11
  from threading import RLock, local
13
12
  from typing import Any, Callable, TypeVar, cast
@@ -113,10 +112,10 @@ def _env_flag_is_true(value: str | None) -> bool:
113
112
  return value[0] not in {"0", "f", "F", "n", "N"}
114
113
 
115
114
 
116
- if _env_flag_is_true(os.getenv("PYPCRE_CACHE_PATTERN_GLOBAL")):
117
- _CACHE_STRATEGY = _CacheStrategy.GLOBAL
118
- else:
119
- _CACHE_STRATEGY = _CacheStrategy.THREAD_LOCAL
115
+ # The C extension latches the strategy when it is imported (honouring -E/-I
116
+ # and the PCRE2_CACHE_PATTERN_GLOBAL alias via Py_GETENV); mirror it rather
117
+ # than re-reading the environment so the two layers can never disagree.
118
+ _CACHE_STRATEGY = _CacheStrategy(_pcre2.get_cache_strategy())
120
119
 
121
120
 
122
121
  def cache_strategy(strategy: str | None = None) -> str: