PyPcre 0.3.2__tar.gz → 0.6.0__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 (67) hide show
  1. {pypcre-0.3.2 → pypcre-0.6.0}/PKG-INFO +69 -16
  2. {pypcre-0.3.2 → pypcre-0.6.0}/PyPcre.egg-info/PKG-INFO +69 -16
  3. {pypcre-0.3.2 → pypcre-0.6.0}/PyPcre.egg-info/SOURCES.txt +11 -0
  4. {pypcre-0.3.2 → pypcre-0.6.0}/README.md +68 -15
  5. {pypcre-0.3.2 → pypcre-0.6.0}/pcre/__init__.py +2 -1
  6. {pypcre-0.3.2 → pypcre-0.6.0}/pcre/pcre.py +166 -54
  7. {pypcre-0.3.2 → pypcre-0.6.0}/pcre/re_compat.py +17 -3
  8. {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/cache.c +165 -72
  9. {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/flag.c +8 -4
  10. {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/memory.c +34 -9
  11. {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/pattern_cache.c +95 -5
  12. pypcre-0.6.0/pcre_ext/pcre2.c +4418 -0
  13. {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/pcre2_module.h +14 -1
  14. {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/string_helpers.c +66 -34
  15. pypcre-0.6.0/pcre_ext/util.c +408 -0
  16. {pypcre-0.3.2 → pypcre-0.6.0}/pyproject.toml +1 -1
  17. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_api_parity.py +91 -0
  18. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_bytes.py +6 -8
  19. pypcre-0.6.0/tests/test_c_api_audit.py +349 -0
  20. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_cache.py +66 -1
  21. pypcre-0.6.0/tests/test_cache_global.py +136 -0
  22. pypcre-0.6.0/tests/test_cache_global_inproc.py +163 -0
  23. pypcre-0.6.0/tests/test_coverage_gaps.py +407 -0
  24. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_errors.py +6 -0
  25. pypcre-0.6.0/tests/test_gil_zero_safety.py +128 -0
  26. pypcre-0.6.0/tests/test_jit_anchoring.py +166 -0
  27. pypcre-0.6.0/tests/test_lastindex_semantics.py +115 -0
  28. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_memory.py +66 -8
  29. pypcre-0.6.0/tests/test_mmap.py +139 -0
  30. pypcre-0.6.0/tests/test_python_coverage_audit.py +348 -0
  31. pypcre-0.6.0/tests/test_re_compat.py +220 -0
  32. pypcre-0.6.0/tests/test_threads.py +132 -0
  33. pypcre-0.3.2/pcre_ext/pcre2.c +0 -2476
  34. pypcre-0.3.2/pcre_ext/util.c +0 -212
  35. {pypcre-0.3.2 → pypcre-0.6.0}/LICENSE +0 -0
  36. {pypcre-0.3.2 → pypcre-0.6.0}/MANIFEST.in +0 -0
  37. {pypcre-0.3.2 → pypcre-0.6.0}/PyPcre.egg-info/dependency_links.txt +0 -0
  38. {pypcre-0.3.2 → pypcre-0.6.0}/PyPcre.egg-info/top_level.txt +0 -0
  39. {pypcre-0.3.2 → pypcre-0.6.0}/pcre/_stdlib_re.py +0 -0
  40. {pypcre-0.3.2 → pypcre-0.6.0}/pcre/cache.py +0 -0
  41. {pypcre-0.3.2 → pypcre-0.6.0}/pcre/error.py +0 -0
  42. {pypcre-0.3.2 → pypcre-0.6.0}/pcre/flags.py +0 -0
  43. {pypcre-0.3.2 → pypcre-0.6.0}/pcre/threads.py +0 -0
  44. {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/atomic_compat.h +0 -0
  45. {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/error.c +0 -0
  46. {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/jit_support.c +0 -0
  47. {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/pcre2.h +0 -0
  48. {pypcre-0.3.2 → pypcre-0.6.0}/setup.cfg +0 -0
  49. {pypcre-0.3.2 → pypcre-0.6.0}/setup.py +0 -0
  50. {pypcre-0.3.2 → pypcre-0.6.0}/setup_utils.py +0 -0
  51. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_accuracy.py +0 -0
  52. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_basic.py +0 -0
  53. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_bench_string.py +0 -0
  54. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_benchmark.py +0 -0
  55. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_clobber.py +0 -0
  56. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_clobber_thread.py +0 -0
  57. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_core.py +0 -0
  58. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_flags.py +0 -0
  59. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_jit.py +0 -0
  60. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_module.py +0 -0
  61. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_pattern.py +0 -0
  62. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_setup_utils_multiarch.py +0 -0
  63. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_simd.py +0 -0
  64. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_threaded_backend.py +0 -0
  65. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_transformers_regex.py +0 -0
  66. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_utf8.py +0 -0
  67. {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPcre
3
- Version: 0.3.2
3
+ Version: 0.6.0
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,6 +47,9 @@ Fast, free-threaded Python bindings for `PCRE2` with a stable `stdlib.re`-compat
47
47
 
48
48
 
49
49
  ## Latest News 🚀
50
+ * 08/08/2026 **0.6.0**: `findall`, `finditer`, `sub`/`subn`, `split`, and `match`/`search`/`fullmatch` are now up to **46x faster** than `stdlib.re` and **48x faster** than `regex` on `finditer`/`findall` workloads, **13x** on `split`, and **2–9x** on `sub`/`subn` backref workloads, with full `re` semantics. Free-threaded `findall` reaches **13.8x** vs `re` on 8 threads. 🚀⚡
51
+ * 07/27/2026 [0.5.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.5.0): Zero-copy buffer-protocol subject support (`mmap.mmap`, `bytearray`, `array.array`) with UTF-8 validation and GIL=0-safe memory pinning. 🗂️⚡
52
+ * 07/24/2026 [0.4.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.4.0): C extension hardening (memory/pointer safety, bounds checks, atomic allocator init), GIL=0 safety verified, vectorized UTF-8 index/offset conversion, GIL-release threshold for small calls, C `findall` implementation, and README competitor benchmarks. 🛡️⚡
50
53
  * 04/13/2026 [0.3.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.3.0): Lower-overhead public `Match` objects, faster hot-path `match()` / `search()` / `fullmatch()` / `findall()`, and tighter free-threaded execution. ⚡
51
54
  * 03/22/2026 [0.2.15](https://github.com/ModelCloud/PyPcre/releases/tag/v0.2.15): Python 3.15 `re` compatibility (`prefixmatch`, `NOFLAG`) ✅
52
55
  * 03/21/2026 [0.2.14](https://github.com/ModelCloud/PyPcre/releases/tag/v0.2.14): Python 3.14 compatibility 🐍
@@ -85,26 +88,76 @@ PyPcre pairs Python's familiar `re`-compatible API with the real `PCRE2` engine.
85
88
 
86
89
  ### Benchmark Highlights 🏁
87
90
 
88
- Measured on a `Python 3.14.3` free-threaded build on x86_64 Linux with compiled-pattern reuse. Times are best-of-5; lower is better.
91
+ Measured on a `Python 3.14.6` free-threaded build on x86_64 Linux with compiled-pattern reuse and JIT enabled. Times are the best of several runs; lower is better. Only workloads where PyPcre is decisively faster than both `stdlib.re` and `regex` are shown.
89
92
 
90
- | Workload | Operation | PyPcre | `re` | `regex` | PyPcre edge |
91
- | --- | --- | ---: | ---: | ---: | --- |
92
- | First `ERROR` line in a multiline log buffer | `search` | `3.68 ms` | `51.72 ms` | `5.67 ms` | `14.0x` vs `re`, `1.54x` vs `regex` |
93
- | Extract only `WARN` / `ERROR` lines | `findall` | `6.41 ms` | `91.84 ms` | `91.14 ms` | `14.3x` vs `re`, `14.2x` vs `regex` |
94
- | Per-line full-name extraction | `findall` | `22.28 ms` | `172.38 ms` | `218.29 ms` | `7.74x` vs `re`, `9.80x` vs `regex` |
95
- | Lookbehind + negative-lookahead extraction | `findall` | `50.23 ms` | `53.35 ms` | `57.03 ms` | `1.06x` vs `re`, `1.14x` vs `regex` |
96
- | UUID extraction | `findall` | `77.49 ms` | `83.19 ms` | `134.87 ms` | `1.07x` vs `re`, `1.74x` vs `regex` |
97
- | Boundary-aware token scan | `findall` | `127.76 ms` | `128.03 ms` | `146.37 ms` | effectively tied with `re`, `1.15x` vs `regex` |
93
+ A reproducible version of this benchmark lives in [`benchmarks/competitor_bench.py`](benchmarks/competitor_bench.py).
94
+
95
+ #### `findall` — large multiline and lookaround workloads
96
+
97
+ | Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
98
+ | --- | ---: | ---: | ---: | --- |
99
+ | Extract `WARN` / `ERROR` lines (multiline) | `0.664` | `27.159` | `30.772` | **40.9x** vs `re`, **46.3x** vs `regex` |
100
+ | Per-line full-name extraction (multiline) | `0.914` | `25.685` | `14.482` | **28.1x** vs `re`, **15.8x** vs `regex` |
101
+ | Lookbehind + negative-lookahead tokens | `1.874` | `12.353` | `10.386` | **6.6x** vs `re`, **5.5x** vs `regex` |
102
+
103
+ Patterns used:
104
+
105
+ ```python
106
+ # WARN/ERROR lines and full-name extraction
107
+ ^(?:WARN|ERROR).*?$ # with re.MULTILINE / pcre.Flag.MULTILINE
108
+ ^[A-Z][a-z]+ [A-Z][a-z]+ # with re.MULTILINE / pcre.Flag.MULTILINE
109
+
110
+ # lookbehind + negative lookahead
111
+ (?:(?<=foo)bar|baz)(?!qux)
112
+ ```
113
+
114
+ #### `finditer` — same workloads
115
+
116
+ Measured on a Python 3.10 x86_64 Linux build with compiled-pattern reuse and JIT enabled. A reproducible version lives in [`benchmarks/finditer_bench.py`](benchmarks/finditer_bench.py).
117
+
118
+ | Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
119
+ | --- | ---: | ---: | ---: | --- |
120
+ | Extract `WARN` / `ERROR` lines | `0.663` | `30.747` | `31.862` | **46.4x** vs `re`, **48.0x** vs `regex` |
121
+ | Per-line full-name extraction | `0.919` | `29.127` | `14.103` | **31.7x** vs `re`, **15.3x** vs `regex` |
122
+ | Lookbehind + negative lookahead | `3.755` | `15.828` | `11.896` | **4.2x** vs `re`, **3.2x** vs `regex` |
123
+
124
+ #### `sub` / `subn` — high-volume replacement workloads
125
+
126
+ Measured on a Python 3.10 x86_64 Linux build with compiled-pattern reuse and JIT enabled. Times are the best of several runs; lower is better. The benchmark replaces 100,000 space-separated tokens.
127
+
128
+ A reproducible version lives in [`benchmarks/sub_bench.py`](benchmarks/sub_bench.py).
129
+
130
+ | Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
131
+ | --- | ---: | ---: | ---: | --- |
132
+ | Literal replacement (`\w+` → `[X]`) | `5.933` | `13.367` | `19.107` | **2.3x** vs `re`, **3.2x** vs `regex` |
133
+ | Single numeric backref (`(w)\d+` → `[\1]`) | `7.221` | `64.715` | `25.482` | **9.0x** vs `re`, **3.5x** vs `regex` |
134
+ | Two numeric backrefs (`(w)(\d+)` → `\2-\1`) | `17.600` | `76.222` | `32.048` | **4.3x** vs `re`, **1.8x** vs `regex` |
135
+ | Named backref (`(?P<g>\w+)` → `<\g<g>>`) | `14.684` | `71.053` | `30.222` | **4.8x** vs `re`, **2.1x** vs `regex` |
136
+
137
+ #### `split` — high-volume delimiter workloads
138
+
139
+ Measured on a Python 3.10 x86_64 Linux build with compiled-pattern reuse and JIT enabled. Times are the best of several runs; lower is better. The benchmark splits 100,000 space-separated tokens.
140
+
141
+ A reproducible version lives in [`benchmarks/split_bench.py`](benchmarks/split_bench.py).
142
+
143
+ | Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
144
+ | --- | ---: | ---: | ---: | --- |
145
+ | Delimiter no group (`\s+`) | `7.315` | `17.394` | `19.690` | **2.4x** vs `re`, **2.7x** vs `regex` |
146
+ | Delimiter with group (`(\s+)`) | `12.360` | `21.099` | `25.145` | **1.7x** vs `re`, **2.0x** vs `regex` |
147
+ | Single char (` `) | `4.583` | `3.990` | `14.577` | parity vs `re`, **3.2x** vs `regex` |
148
+ | Single char with group (`( )`) | `8.913` | `11.091` | `18.595` | **1.2x** vs `re`, **2.1x** vs `regex` |
149
+ | Empty pattern (`''`) | `5.228` | `45.552` | `69.913` | **8.7x** vs `re`, **13.4x** vs `regex` |
98
150
 
99
151
  ### Free-Threaded Benchmark Highlights 🧵
100
152
 
101
- Measured in the same environment with `8` threads sharing one compiled pattern. Times are best-of-3; lower is better.
153
+ Measured on the same `Python 3.14.6` free-threaded build with `8` threads fanning out over split copies of each workload. Times are the best of several runs; lower is better.
154
+
155
+ A reproducible version lives in [`benchmarks/free_threaded_bench.py`](benchmarks/free_threaded_bench.py).
102
156
 
103
- | Workload | Threads | PyPcre | `re` | `regex` | PyPcre edge |
104
- | --- | ---: | ---: | ---: | ---: | --- |
105
- | First `ERROR` line in a multiline log buffer | `8` | `25.34 ms` | `38.83 ms` | `40.34 ms` | `1.53x` vs `re`, `1.59x` vs `regex` |
106
- | Extract only `WARN` / `ERROR` lines | `8` | `28.58 ms` | `65.54 ms` | `73.55 ms` | `2.29x` vs `re`, `2.57x` vs `regex` |
107
- | Per-line full-name extraction | `8` | `31.68 ms` | `123.44 ms` | `164.80 ms` | `3.90x` vs `re`, `5.20x` vs `regex` |
157
+ | Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
158
+ | --- | ---: | ---: | ---: | --- |
159
+ | Extract `WARN` / `ERROR` lines (`findall`) | `0.672` | `9.063` | `9.297` | **13.5x** vs `re`, **13.8x** vs `regex` |
160
+ | Per-line full-name extraction (`findall`) | `0.913` | `8.611` | `4.575` | **9.4x** vs `re`, **5.0x** vs `regex` |
108
161
 
109
162
  PyPcre is the stronger all-around choice when you want more than the baseline: full `PCRE2` features, more expressive syntax, JIT, explicit free-threaded support, and a stable `re`-compatible API surface. It keeps Python ergonomics while giving you a substantially more capable engine. 🚀
110
163
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPcre
3
- Version: 0.3.2
3
+ Version: 0.6.0
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,6 +47,9 @@ Fast, free-threaded Python bindings for `PCRE2` with a stable `stdlib.re`-compat
47
47
 
48
48
 
49
49
  ## Latest News 🚀
50
+ * 08/08/2026 **0.6.0**: `findall`, `finditer`, `sub`/`subn`, `split`, and `match`/`search`/`fullmatch` are now up to **46x faster** than `stdlib.re` and **48x faster** than `regex` on `finditer`/`findall` workloads, **13x** on `split`, and **2–9x** on `sub`/`subn` backref workloads, with full `re` semantics. Free-threaded `findall` reaches **13.8x** vs `re` on 8 threads. 🚀⚡
51
+ * 07/27/2026 [0.5.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.5.0): Zero-copy buffer-protocol subject support (`mmap.mmap`, `bytearray`, `array.array`) with UTF-8 validation and GIL=0-safe memory pinning. 🗂️⚡
52
+ * 07/24/2026 [0.4.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.4.0): C extension hardening (memory/pointer safety, bounds checks, atomic allocator init), GIL=0 safety verified, vectorized UTF-8 index/offset conversion, GIL-release threshold for small calls, C `findall` implementation, and README competitor benchmarks. 🛡️⚡
50
53
  * 04/13/2026 [0.3.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.3.0): Lower-overhead public `Match` objects, faster hot-path `match()` / `search()` / `fullmatch()` / `findall()`, and tighter free-threaded execution. ⚡
51
54
  * 03/22/2026 [0.2.15](https://github.com/ModelCloud/PyPcre/releases/tag/v0.2.15): Python 3.15 `re` compatibility (`prefixmatch`, `NOFLAG`) ✅
52
55
  * 03/21/2026 [0.2.14](https://github.com/ModelCloud/PyPcre/releases/tag/v0.2.14): Python 3.14 compatibility 🐍
@@ -85,26 +88,76 @@ PyPcre pairs Python's familiar `re`-compatible API with the real `PCRE2` engine.
85
88
 
86
89
  ### Benchmark Highlights 🏁
87
90
 
88
- Measured on a `Python 3.14.3` free-threaded build on x86_64 Linux with compiled-pattern reuse. Times are best-of-5; lower is better.
91
+ Measured on a `Python 3.14.6` free-threaded build on x86_64 Linux with compiled-pattern reuse and JIT enabled. Times are the best of several runs; lower is better. Only workloads where PyPcre is decisively faster than both `stdlib.re` and `regex` are shown.
89
92
 
90
- | Workload | Operation | PyPcre | `re` | `regex` | PyPcre edge |
91
- | --- | --- | ---: | ---: | ---: | --- |
92
- | First `ERROR` line in a multiline log buffer | `search` | `3.68 ms` | `51.72 ms` | `5.67 ms` | `14.0x` vs `re`, `1.54x` vs `regex` |
93
- | Extract only `WARN` / `ERROR` lines | `findall` | `6.41 ms` | `91.84 ms` | `91.14 ms` | `14.3x` vs `re`, `14.2x` vs `regex` |
94
- | Per-line full-name extraction | `findall` | `22.28 ms` | `172.38 ms` | `218.29 ms` | `7.74x` vs `re`, `9.80x` vs `regex` |
95
- | Lookbehind + negative-lookahead extraction | `findall` | `50.23 ms` | `53.35 ms` | `57.03 ms` | `1.06x` vs `re`, `1.14x` vs `regex` |
96
- | UUID extraction | `findall` | `77.49 ms` | `83.19 ms` | `134.87 ms` | `1.07x` vs `re`, `1.74x` vs `regex` |
97
- | Boundary-aware token scan | `findall` | `127.76 ms` | `128.03 ms` | `146.37 ms` | effectively tied with `re`, `1.15x` vs `regex` |
93
+ A reproducible version of this benchmark lives in [`benchmarks/competitor_bench.py`](benchmarks/competitor_bench.py).
94
+
95
+ #### `findall` — large multiline and lookaround workloads
96
+
97
+ | Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
98
+ | --- | ---: | ---: | ---: | --- |
99
+ | Extract `WARN` / `ERROR` lines (multiline) | `0.664` | `27.159` | `30.772` | **40.9x** vs `re`, **46.3x** vs `regex` |
100
+ | Per-line full-name extraction (multiline) | `0.914` | `25.685` | `14.482` | **28.1x** vs `re`, **15.8x** vs `regex` |
101
+ | Lookbehind + negative-lookahead tokens | `1.874` | `12.353` | `10.386` | **6.6x** vs `re`, **5.5x** vs `regex` |
102
+
103
+ Patterns used:
104
+
105
+ ```python
106
+ # WARN/ERROR lines and full-name extraction
107
+ ^(?:WARN|ERROR).*?$ # with re.MULTILINE / pcre.Flag.MULTILINE
108
+ ^[A-Z][a-z]+ [A-Z][a-z]+ # with re.MULTILINE / pcre.Flag.MULTILINE
109
+
110
+ # lookbehind + negative lookahead
111
+ (?:(?<=foo)bar|baz)(?!qux)
112
+ ```
113
+
114
+ #### `finditer` — same workloads
115
+
116
+ Measured on a Python 3.10 x86_64 Linux build with compiled-pattern reuse and JIT enabled. A reproducible version lives in [`benchmarks/finditer_bench.py`](benchmarks/finditer_bench.py).
117
+
118
+ | Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
119
+ | --- | ---: | ---: | ---: | --- |
120
+ | Extract `WARN` / `ERROR` lines | `0.663` | `30.747` | `31.862` | **46.4x** vs `re`, **48.0x** vs `regex` |
121
+ | Per-line full-name extraction | `0.919` | `29.127` | `14.103` | **31.7x** vs `re`, **15.3x** vs `regex` |
122
+ | Lookbehind + negative lookahead | `3.755` | `15.828` | `11.896` | **4.2x** vs `re`, **3.2x** vs `regex` |
123
+
124
+ #### `sub` / `subn` — high-volume replacement workloads
125
+
126
+ Measured on a Python 3.10 x86_64 Linux build with compiled-pattern reuse and JIT enabled. Times are the best of several runs; lower is better. The benchmark replaces 100,000 space-separated tokens.
127
+
128
+ A reproducible version lives in [`benchmarks/sub_bench.py`](benchmarks/sub_bench.py).
129
+
130
+ | Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
131
+ | --- | ---: | ---: | ---: | --- |
132
+ | Literal replacement (`\w+` → `[X]`) | `5.933` | `13.367` | `19.107` | **2.3x** vs `re`, **3.2x** vs `regex` |
133
+ | Single numeric backref (`(w)\d+` → `[\1]`) | `7.221` | `64.715` | `25.482` | **9.0x** vs `re`, **3.5x** vs `regex` |
134
+ | Two numeric backrefs (`(w)(\d+)` → `\2-\1`) | `17.600` | `76.222` | `32.048` | **4.3x** vs `re`, **1.8x** vs `regex` |
135
+ | Named backref (`(?P<g>\w+)` → `<\g<g>>`) | `14.684` | `71.053` | `30.222` | **4.8x** vs `re`, **2.1x** vs `regex` |
136
+
137
+ #### `split` — high-volume delimiter workloads
138
+
139
+ Measured on a Python 3.10 x86_64 Linux build with compiled-pattern reuse and JIT enabled. Times are the best of several runs; lower is better. The benchmark splits 100,000 space-separated tokens.
140
+
141
+ A reproducible version lives in [`benchmarks/split_bench.py`](benchmarks/split_bench.py).
142
+
143
+ | Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
144
+ | --- | ---: | ---: | ---: | --- |
145
+ | Delimiter no group (`\s+`) | `7.315` | `17.394` | `19.690` | **2.4x** vs `re`, **2.7x** vs `regex` |
146
+ | Delimiter with group (`(\s+)`) | `12.360` | `21.099` | `25.145` | **1.7x** vs `re`, **2.0x** vs `regex` |
147
+ | Single char (` `) | `4.583` | `3.990` | `14.577` | parity vs `re`, **3.2x** vs `regex` |
148
+ | Single char with group (`( )`) | `8.913` | `11.091` | `18.595` | **1.2x** vs `re`, **2.1x** vs `regex` |
149
+ | Empty pattern (`''`) | `5.228` | `45.552` | `69.913` | **8.7x** vs `re`, **13.4x** vs `regex` |
98
150
 
99
151
  ### Free-Threaded Benchmark Highlights 🧵
100
152
 
101
- Measured in the same environment with `8` threads sharing one compiled pattern. Times are best-of-3; lower is better.
153
+ Measured on the same `Python 3.14.6` free-threaded build with `8` threads fanning out over split copies of each workload. Times are the best of several runs; lower is better.
154
+
155
+ A reproducible version lives in [`benchmarks/free_threaded_bench.py`](benchmarks/free_threaded_bench.py).
102
156
 
103
- | Workload | Threads | PyPcre | `re` | `regex` | PyPcre edge |
104
- | --- | ---: | ---: | ---: | ---: | --- |
105
- | First `ERROR` line in a multiline log buffer | `8` | `25.34 ms` | `38.83 ms` | `40.34 ms` | `1.53x` vs `re`, `1.59x` vs `regex` |
106
- | Extract only `WARN` / `ERROR` lines | `8` | `28.58 ms` | `65.54 ms` | `73.55 ms` | `2.29x` vs `re`, `2.57x` vs `regex` |
107
- | Per-line full-name extraction | `8` | `31.68 ms` | `123.44 ms` | `164.80 ms` | `3.90x` vs `re`, `5.20x` vs `regex` |
157
+ | Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
158
+ | --- | ---: | ---: | ---: | --- |
159
+ | Extract `WARN` / `ERROR` lines (`findall`) | `0.672` | `9.063` | `9.297` | **13.5x** vs `re`, **13.8x** vs `regex` |
160
+ | Per-line full-name extraction (`findall`) | `0.913` | `8.611` | `4.575` | **9.4x** vs `re`, **5.0x** vs `regex` |
108
161
 
109
162
  PyPcre is the stronger all-around choice when you want more than the baseline: full `PCRE2` features, more expressive syntax, JIT, explicit free-threaded support, and a stable `re`-compatible API surface. It keeps Python ergonomics while giving you a substantially more capable engine. 🚀
110
163
 
@@ -34,19 +34,30 @@ tests/test_basic.py
34
34
  tests/test_bench_string.py
35
35
  tests/test_benchmark.py
36
36
  tests/test_bytes.py
37
+ tests/test_c_api_audit.py
37
38
  tests/test_cache.py
39
+ tests/test_cache_global.py
40
+ tests/test_cache_global_inproc.py
38
41
  tests/test_clobber.py
39
42
  tests/test_clobber_thread.py
40
43
  tests/test_core.py
44
+ tests/test_coverage_gaps.py
41
45
  tests/test_errors.py
42
46
  tests/test_flags.py
47
+ tests/test_gil_zero_safety.py
43
48
  tests/test_jit.py
49
+ tests/test_jit_anchoring.py
50
+ tests/test_lastindex_semantics.py
44
51
  tests/test_memory.py
52
+ tests/test_mmap.py
45
53
  tests/test_module.py
46
54
  tests/test_pattern.py
55
+ tests/test_python_coverage_audit.py
56
+ tests/test_re_compat.py
47
57
  tests/test_setup_utils_multiarch.py
48
58
  tests/test_simd.py
49
59
  tests/test_threaded_backend.py
60
+ tests/test_threads.py
50
61
  tests/test_transformers_regex.py
51
62
  tests/test_utf8.py
52
63
  tests/test_version.py
@@ -24,6 +24,9 @@ Fast, free-threaded Python bindings for `PCRE2` with a stable `stdlib.re`-compat
24
24
 
25
25
 
26
26
  ## Latest News 🚀
27
+ * 08/08/2026 **0.6.0**: `findall`, `finditer`, `sub`/`subn`, `split`, and `match`/`search`/`fullmatch` are now up to **46x faster** than `stdlib.re` and **48x faster** than `regex` on `finditer`/`findall` workloads, **13x** on `split`, and **2–9x** on `sub`/`subn` backref workloads, with full `re` semantics. Free-threaded `findall` reaches **13.8x** vs `re` on 8 threads. 🚀⚡
28
+ * 07/27/2026 [0.5.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.5.0): Zero-copy buffer-protocol subject support (`mmap.mmap`, `bytearray`, `array.array`) with UTF-8 validation and GIL=0-safe memory pinning. 🗂️⚡
29
+ * 07/24/2026 [0.4.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.4.0): C extension hardening (memory/pointer safety, bounds checks, atomic allocator init), GIL=0 safety verified, vectorized UTF-8 index/offset conversion, GIL-release threshold for small calls, C `findall` implementation, and README competitor benchmarks. 🛡️⚡
27
30
  * 04/13/2026 [0.3.0](https://github.com/ModelCloud/PyPcre/releases/tag/v0.3.0): Lower-overhead public `Match` objects, faster hot-path `match()` / `search()` / `fullmatch()` / `findall()`, and tighter free-threaded execution. ⚡
28
31
  * 03/22/2026 [0.2.15](https://github.com/ModelCloud/PyPcre/releases/tag/v0.2.15): Python 3.15 `re` compatibility (`prefixmatch`, `NOFLAG`) ✅
29
32
  * 03/21/2026 [0.2.14](https://github.com/ModelCloud/PyPcre/releases/tag/v0.2.14): Python 3.14 compatibility 🐍
@@ -62,26 +65,76 @@ PyPcre pairs Python's familiar `re`-compatible API with the real `PCRE2` engine.
62
65
 
63
66
  ### Benchmark Highlights 🏁
64
67
 
65
- Measured on a `Python 3.14.3` free-threaded build on x86_64 Linux with compiled-pattern reuse. Times are best-of-5; lower is better.
68
+ Measured on a `Python 3.14.6` free-threaded build on x86_64 Linux with compiled-pattern reuse and JIT enabled. Times are the best of several runs; lower is better. Only workloads where PyPcre is decisively faster than both `stdlib.re` and `regex` are shown.
66
69
 
67
- | Workload | Operation | PyPcre | `re` | `regex` | PyPcre edge |
68
- | --- | --- | ---: | ---: | ---: | --- |
69
- | First `ERROR` line in a multiline log buffer | `search` | `3.68 ms` | `51.72 ms` | `5.67 ms` | `14.0x` vs `re`, `1.54x` vs `regex` |
70
- | Extract only `WARN` / `ERROR` lines | `findall` | `6.41 ms` | `91.84 ms` | `91.14 ms` | `14.3x` vs `re`, `14.2x` vs `regex` |
71
- | Per-line full-name extraction | `findall` | `22.28 ms` | `172.38 ms` | `218.29 ms` | `7.74x` vs `re`, `9.80x` vs `regex` |
72
- | Lookbehind + negative-lookahead extraction | `findall` | `50.23 ms` | `53.35 ms` | `57.03 ms` | `1.06x` vs `re`, `1.14x` vs `regex` |
73
- | UUID extraction | `findall` | `77.49 ms` | `83.19 ms` | `134.87 ms` | `1.07x` vs `re`, `1.74x` vs `regex` |
74
- | Boundary-aware token scan | `findall` | `127.76 ms` | `128.03 ms` | `146.37 ms` | effectively tied with `re`, `1.15x` vs `regex` |
70
+ A reproducible version of this benchmark lives in [`benchmarks/competitor_bench.py`](benchmarks/competitor_bench.py).
71
+
72
+ #### `findall` — large multiline and lookaround workloads
73
+
74
+ | Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
75
+ | --- | ---: | ---: | ---: | --- |
76
+ | Extract `WARN` / `ERROR` lines (multiline) | `0.664` | `27.159` | `30.772` | **40.9x** vs `re`, **46.3x** vs `regex` |
77
+ | Per-line full-name extraction (multiline) | `0.914` | `25.685` | `14.482` | **28.1x** vs `re`, **15.8x** vs `regex` |
78
+ | Lookbehind + negative-lookahead tokens | `1.874` | `12.353` | `10.386` | **6.6x** vs `re`, **5.5x** vs `regex` |
79
+
80
+ Patterns used:
81
+
82
+ ```python
83
+ # WARN/ERROR lines and full-name extraction
84
+ ^(?:WARN|ERROR).*?$ # with re.MULTILINE / pcre.Flag.MULTILINE
85
+ ^[A-Z][a-z]+ [A-Z][a-z]+ # with re.MULTILINE / pcre.Flag.MULTILINE
86
+
87
+ # lookbehind + negative lookahead
88
+ (?:(?<=foo)bar|baz)(?!qux)
89
+ ```
90
+
91
+ #### `finditer` — same workloads
92
+
93
+ Measured on a Python 3.10 x86_64 Linux build with compiled-pattern reuse and JIT enabled. A reproducible version lives in [`benchmarks/finditer_bench.py`](benchmarks/finditer_bench.py).
94
+
95
+ | Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
96
+ | --- | ---: | ---: | ---: | --- |
97
+ | Extract `WARN` / `ERROR` lines | `0.663` | `30.747` | `31.862` | **46.4x** vs `re`, **48.0x** vs `regex` |
98
+ | Per-line full-name extraction | `0.919` | `29.127` | `14.103` | **31.7x** vs `re`, **15.3x** vs `regex` |
99
+ | Lookbehind + negative lookahead | `3.755` | `15.828` | `11.896` | **4.2x** vs `re`, **3.2x** vs `regex` |
100
+
101
+ #### `sub` / `subn` — high-volume replacement workloads
102
+
103
+ Measured on a Python 3.10 x86_64 Linux build with compiled-pattern reuse and JIT enabled. Times are the best of several runs; lower is better. The benchmark replaces 100,000 space-separated tokens.
104
+
105
+ A reproducible version lives in [`benchmarks/sub_bench.py`](benchmarks/sub_bench.py).
106
+
107
+ | Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
108
+ | --- | ---: | ---: | ---: | --- |
109
+ | Literal replacement (`\w+` → `[X]`) | `5.933` | `13.367` | `19.107` | **2.3x** vs `re`, **3.2x** vs `regex` |
110
+ | Single numeric backref (`(w)\d+` → `[\1]`) | `7.221` | `64.715` | `25.482` | **9.0x** vs `re`, **3.5x** vs `regex` |
111
+ | Two numeric backrefs (`(w)(\d+)` → `\2-\1`) | `17.600` | `76.222` | `32.048` | **4.3x** vs `re`, **1.8x** vs `regex` |
112
+ | Named backref (`(?P<g>\w+)` → `<\g<g>>`) | `14.684` | `71.053` | `30.222` | **4.8x** vs `re`, **2.1x** vs `regex` |
113
+
114
+ #### `split` — high-volume delimiter workloads
115
+
116
+ Measured on a Python 3.10 x86_64 Linux build with compiled-pattern reuse and JIT enabled. Times are the best of several runs; lower is better. The benchmark splits 100,000 space-separated tokens.
117
+
118
+ A reproducible version lives in [`benchmarks/split_bench.py`](benchmarks/split_bench.py).
119
+
120
+ | Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
121
+ | --- | ---: | ---: | ---: | --- |
122
+ | Delimiter no group (`\s+`) | `7.315` | `17.394` | `19.690` | **2.4x** vs `re`, **2.7x** vs `regex` |
123
+ | Delimiter with group (`(\s+)`) | `12.360` | `21.099` | `25.145` | **1.7x** vs `re`, **2.0x** vs `regex` |
124
+ | Single char (` `) | `4.583` | `3.990` | `14.577` | parity vs `re`, **3.2x** vs `regex` |
125
+ | Single char with group (`( )`) | `8.913` | `11.091` | `18.595` | **1.2x** vs `re`, **2.1x** vs `regex` |
126
+ | Empty pattern (`''`) | `5.228` | `45.552` | `69.913` | **8.7x** vs `re`, **13.4x** vs `regex` |
75
127
 
76
128
  ### Free-Threaded Benchmark Highlights 🧵
77
129
 
78
- Measured in the same environment with `8` threads sharing one compiled pattern. Times are best-of-3; lower is better.
130
+ Measured on the same `Python 3.14.6` free-threaded build with `8` threads fanning out over split copies of each workload. Times are the best of several runs; lower is better.
131
+
132
+ A reproducible version lives in [`benchmarks/free_threaded_bench.py`](benchmarks/free_threaded_bench.py).
79
133
 
80
- | Workload | Threads | PyPcre | `re` | `regex` | PyPcre edge |
81
- | --- | ---: | ---: | ---: | ---: | --- |
82
- | First `ERROR` line in a multiline log buffer | `8` | `25.34 ms` | `38.83 ms` | `40.34 ms` | `1.53x` vs `re`, `1.59x` vs `regex` |
83
- | Extract only `WARN` / `ERROR` lines | `8` | `28.58 ms` | `65.54 ms` | `73.55 ms` | `2.29x` vs `re`, `2.57x` vs `regex` |
84
- | Per-line full-name extraction | `8` | `31.68 ms` | `123.44 ms` | `164.80 ms` | `3.90x` vs `re`, `5.20x` vs `regex` |
134
+ | Workload | PyPcre (ms) | `re` (ms) | `regex` (ms) | PyPcre edge |
135
+ | --- | ---: | ---: | ---: | --- |
136
+ | Extract `WARN` / `ERROR` lines (`findall`) | `0.672` | `9.063` | `9.297` | **13.5x** vs `re`, **13.8x** vs `regex` |
137
+ | Per-line full-name extraction (`findall`) | `0.913` | `8.611` | `4.575` | **9.4x** vs `re`, **5.0x** vs `regex` |
85
138
 
86
139
  PyPcre is the stronger all-around choice when you want more than the baseline: full `PCRE2` features, more expressive syntax, JIT, explicit free-threaded support, and a stable `re`-compatible API surface. It keeps Python ergonomics while giving you a substantially more capable engine. 🚀
87
140
 
@@ -12,12 +12,12 @@ surfacing PCRE2-specific flags and behaviours.
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
+ import importlib as _importlib
15
16
  import re as _std_re
16
17
  from typing import Any
17
18
 
18
19
  import pcre_ext_c as _backend
19
20
 
20
- from . import error as _error_module
21
21
  from .cache import cache_strategy, get_cache_limit, set_cache_limit
22
22
  from .error import ERRORS_BY_CODE, ERRORS_BY_MACRO, PcreErrorCode
23
23
  from .flags import Flag
@@ -43,6 +43,7 @@ from .pcre import (
43
43
  from .threads import configure_thread_pool, configure_threads, shutdown_thread_pool
44
44
 
45
45
 
46
+ _error_module = _importlib.import_module(".error", __name__)
46
47
  pcre_ext_c = _backend
47
48
 
48
49
  __version__ = getattr(_backend, "__version__", "0.0")