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.
- {pypcre-0.3.2 → pypcre-0.6.0}/PKG-INFO +69 -16
- {pypcre-0.3.2 → pypcre-0.6.0}/PyPcre.egg-info/PKG-INFO +69 -16
- {pypcre-0.3.2 → pypcre-0.6.0}/PyPcre.egg-info/SOURCES.txt +11 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/README.md +68 -15
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre/__init__.py +2 -1
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre/pcre.py +166 -54
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre/re_compat.py +17 -3
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/cache.c +165 -72
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/flag.c +8 -4
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/memory.c +34 -9
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/pattern_cache.c +95 -5
- pypcre-0.6.0/pcre_ext/pcre2.c +4418 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/pcre2_module.h +14 -1
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/string_helpers.c +66 -34
- pypcre-0.6.0/pcre_ext/util.c +408 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/pyproject.toml +1 -1
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_api_parity.py +91 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_bytes.py +6 -8
- pypcre-0.6.0/tests/test_c_api_audit.py +349 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_cache.py +66 -1
- pypcre-0.6.0/tests/test_cache_global.py +136 -0
- pypcre-0.6.0/tests/test_cache_global_inproc.py +163 -0
- pypcre-0.6.0/tests/test_coverage_gaps.py +407 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_errors.py +6 -0
- pypcre-0.6.0/tests/test_gil_zero_safety.py +128 -0
- pypcre-0.6.0/tests/test_jit_anchoring.py +166 -0
- pypcre-0.6.0/tests/test_lastindex_semantics.py +115 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_memory.py +66 -8
- pypcre-0.6.0/tests/test_mmap.py +139 -0
- pypcre-0.6.0/tests/test_python_coverage_audit.py +348 -0
- pypcre-0.6.0/tests/test_re_compat.py +220 -0
- pypcre-0.6.0/tests/test_threads.py +132 -0
- pypcre-0.3.2/pcre_ext/pcre2.c +0 -2476
- pypcre-0.3.2/pcre_ext/util.c +0 -212
- {pypcre-0.3.2 → pypcre-0.6.0}/LICENSE +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/MANIFEST.in +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/PyPcre.egg-info/dependency_links.txt +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/PyPcre.egg-info/top_level.txt +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre/_stdlib_re.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre/cache.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre/error.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre/flags.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre/threads.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/atomic_compat.h +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/error.c +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/jit_support.c +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/pcre_ext/pcre2.h +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/setup.cfg +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/setup.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/setup_utils.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_accuracy.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_basic.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_bench_string.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_benchmark.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_clobber.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_clobber_thread.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_core.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_flags.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_jit.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_module.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_pattern.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_setup_utils_multiarch.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_simd.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_threaded_backend.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_transformers_regex.py +0 -0
- {pypcre-0.3.2 → pypcre-0.6.0}/tests/test_utf8.py +0 -0
- {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
|
+
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.
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
|
|
|
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
|
|
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 |
|
|
104
|
-
| --- | ---: | ---: | ---: |
|
|
105
|
-
|
|
|
106
|
-
|
|
|
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
|
+
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.
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
|
|
|
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
|
|
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 |
|
|
104
|
-
| --- | ---: | ---: | ---: |
|
|
105
|
-
|
|
|
106
|
-
|
|
|
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.
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
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
|
|
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 |
|
|
81
|
-
| --- | ---: | ---: | ---: |
|
|
82
|
-
|
|
|
83
|
-
|
|
|
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")
|