From 8e40f7d4dd46f1205a83e0caaf4557a642f54ae0 Mon Sep 17 00:00:00 2001 From: Steve Ayers Date: Thu, 31 Jul 2025 13:29:40 -0400 Subject: [PATCH 1/4] Add re2 and tests for syntax --- Makefile | 6 ++ protovalidate/internal/extra_func.py | 29 +++++++ pyproject.toml | 4 + test/test_matches.py | 44 ++++++++++ uv.lock | 118 +++++++++++++++++++++++++++ 5 files changed, 201 insertions(+) create mode 100644 test/test_matches.py diff --git a/Makefile b/Makefile index 2433783f..1a8a2e2e 100644 --- a/Makefile +++ b/Makefile @@ -52,6 +52,12 @@ format: install $(BIN)/license-header ## Format code .PHONY: test test: generate install gettestdata ## Run unit tests uv run -- python -m unittest + $(MAKE) testextra + +.PHONY: testextra +testextra: + uv pip install .[re2] + uv run -- python -m unittest .PHONY: conformance conformance: $(BIN)/protovalidate-conformance generate install ## Run conformance tests diff --git a/protovalidate/internal/extra_func.py b/protovalidate/internal/extra_func.py index d8b3a928..4602b095 100644 --- a/protovalidate/internal/extra_func.py +++ b/protovalidate/internal/extra_func.py @@ -23,6 +23,12 @@ from protovalidate.internal import string_format from protovalidate.internal.rules import MessageType, field_to_cel +_USE_RE2 = True +try: + import re2 # type: ignore +except ImportError: + _USE_RE2 = False + # See https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address _email_regex = re.compile( r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$" @@ -1553,11 +1559,34 @@ def __peek(self, char: str) -> bool: return self._index < len(self._string) and self._string[self._index] == char +def cel_matches_re(text: str, pattern: str) -> celpy.Result: + try: + m = re.search(pattern, text) + except re.error as ex: + return celpy.CELEvalError("match error", ex.__class__, ex.args) + + return celtypes.BoolType(m is not None) + + +def cel_matches_re2(text: str, pattern: str) -> celpy.Result: + try: + m = re2.search(pattern, text) + except re2.error as ex: + return celpy.CELEvalError("match error", ex.__class__, ex.args) + + return celtypes.BoolType(m is not None) + + +cel_matches = cel_matches_re2 if _USE_RE2 else cel_matches_re + + def make_extra_funcs() -> dict[str, celpy.CELFunction]: string_fmt = string_format.StringFormat() return { # Missing standard functions "format": string_fmt.format, + # Overridden standard functions + "matches": cel_matches, # protovalidate specific functions "getField": cel_get_field, "isNan": cel_is_nan, diff --git a/pyproject.toml b/pyproject.toml index 4bab7aa5..d7717060 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,10 @@ dependencies = [ "protobuf==6.*", "cel-python==0.2.*", ] +[project.optional-dependencies] +re2 = [ + "google-re2", +] [project.urls] Homepage = "https://github.com/bufbuild/protovalidate-python" diff --git a/test/test_matches.py b/test/test_matches.py new file mode 100644 index 00000000..85c48fc6 --- /dev/null +++ b/test/test_matches.py @@ -0,0 +1,44 @@ +# Copyright 2023-2025 Buf Technologies, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +import celpy +from celpy import celtypes + +from protovalidate.internal.extra_func import cel_matches_re2, cel_matches_re + +_USE_RE2 = True +try: + import re2 # type: ignore +except ImportError: + _USE_RE2 = False + +class TestCollectViolations(unittest.TestCase): + + @unittest.skipUnless(_USE_RE2, "Requires 're2'") + def test_function_matches_re2(self): + empty_string = celtypes.StringType("") + # \z is valid re2 syntax for end of text + assert cel_matches_re2(empty_string, "^\\z") + # \Z is invalid re2 syntax + assert isinstance(cel_matches_re2(empty_string, "^\\Z"), celpy.CELEvalError) + + @unittest.skipUnless(_USE_RE2 is False, "Requires 're'") + def test_function_matches_re(self): + empty_string = celtypes.StringType("") + # \z is invalid re syntax + assert isinstance(cel_matches_re(empty_string, "^\\z"), celpy.CELEvalError) + # \Z is valid re syntax for end of text + assert cel_matches_re(empty_string, "^\\Z") diff --git a/uv.lock b/uv.lock index e7d57ce5..98b5a082 100644 --- a/uv.lock +++ b/uv.lock @@ -19,6 +19,117 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/28/08871462a0347b3e707658a8308be6f979167488a2196f93b402c2ea7170/cel_python-0.2.0-py3-none-any.whl", hash = "sha256:478ff73def7b39d51e6982f95d937a57c2b088c491c578fe5cecdbd79f476f60", size = 71337, upload-time = "2025-02-14T11:42:19.996Z" }, ] +[[package]] +name = "google-re2" +version = "1.1.20250722" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/28/3d/254eb1651da04fb38b28558510072f06ba4992ea6cb080e75d2d8ee2f3ff/google_re2-1.1.20250722.tar.gz", hash = "sha256:5e2a464df75dbcef9fe0daf18a78f73c3f0a51b81cdb865460a0579b226f2ef3", size = 11698, upload-time = "2025-07-28T15:40:52.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/a5/48f716cede7de0223dc9ba937e97b69324cf03d0cbc909aaafb48a42be34/google_re2-1.1.20250722-1-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:c37147bf584605f1445a9fe6965708e801d81529b0f704d562c7e12d08ed1340", size = 459207, upload-time = "2025-07-28T15:39:43.052Z" }, + { url = "https://files.pythonhosted.org/packages/81/ba/4659aa20a08cea158af5adbf8c6f5dddca5b5d33568cfac3aa39b8565047/google_re2-1.1.20250722-1-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:74e33250977a1b74c3c6048b4e0bb9a7c82fa4b26b5bcaf714b79831cf28714c", size = 487141, upload-time = "2025-07-28T15:39:44.644Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d0/51a1c6edc555926b683baff1f997809a424e6663c02e4d0a4e9e3aa7665a/google_re2-1.1.20250722-1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:31c5ca2a8bed6e036744afb72af3936e8c3141aa632e2946fd126a728d5e64e6", size = 459959, upload-time = "2025-07-28T15:39:47.492Z" }, + { url = "https://files.pythonhosted.org/packages/93/c9/50203881f649a75c6266e865f986b8ebbd58dcac0593d1073c5dd14dc75e/google_re2-1.1.20250722-1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:be7bab182e3f0509e2b4d89cb0c61ad1cd7b35eaf016e606b8ef9bb54f5ec39e", size = 487971, upload-time = "2025-07-28T15:39:49.082Z" }, + { url = "https://files.pythonhosted.org/packages/f0/07/5489e6b170da78c9b65730a84aafe130f458c577196e9e4780d2dd13dc73/google_re2-1.1.20250722-1-cp310-cp310-macosx_15_0_arm64.whl", hash = "sha256:918d69b0e285893f39d51a5b18d6eba2f3d130b03a1f3d4c9502d01e5580df6d", size = 460765, upload-time = "2025-07-28T15:39:50.263Z" }, + { url = "https://files.pythonhosted.org/packages/0e/6c/75e48ae2924db4e480c8d1432db51630bc39dd82c764f3ec0219de452b04/google_re2-1.1.20250722-1-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:51ec67b6c4ab2f9937cd6c7bbf7f8002984a63343ad3efde6adbc17ce6677ff0", size = 484453, upload-time = "2025-07-28T15:39:51.647Z" }, + { url = "https://files.pythonhosted.org/packages/05/b2/dd998249c667bdf3af853df974b2987956971e4c580b6a7db6fcc457bcf3/google_re2-1.1.20250722-1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92f78394d18ab06a63cf1c22f650fe42e751588fcd8733029f40ba8789cf7920", size = 551401, upload-time = "2025-07-28T15:39:53.286Z" }, + { url = "https://files.pythonhosted.org/packages/a0/51/e4877b8c789319d5ac2dc2e756f8f1334654b546a7a1193b99f089ce9c05/google_re2-1.1.20250722-1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3266965761d25ea4d037aedf54710d6f0ca28fc63c2b2b9270d0b786a91a486f", size = 565722, upload-time = "2025-07-28T15:39:54.501Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1c/0e7bccf2210ea4a1dea445af27924f46b32873cc30473a7f6bc52b56b0a9/google_re2-1.1.20250722-1-cp310-cp310-win32.whl", hash = "sha256:750ebea85a7006d580d69ca6a5629d745e49bc816183317cc7993a578076c84b", size = 427781, upload-time = "2025-07-28T15:39:55.826Z" }, + { url = "https://files.pythonhosted.org/packages/87/84/2ed1b51f7d61d09cb00dd806f69a0f1f43621b804fa55b385b6dc1733c73/google_re2-1.1.20250722-1-cp310-cp310-win_amd64.whl", hash = "sha256:34572a8e2a54af45abe853db9590018e69ac29d7fb0cf4496542e386a40f0606", size = 485273, upload-time = "2025-07-28T15:39:57.136Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a1/817218049238970605e41a627daa01942c1a16fb32cc71aaf141c04b3b75/google_re2-1.1.20250722-1-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:91650927b1062c703699bbac97906f366e2a6cf2f45ced505fe16b2cf53e012e", size = 460038, upload-time = "2025-07-28T15:39:58.332Z" }, + { url = "https://files.pythonhosted.org/packages/fd/0f/ce0147f9d6c43efcac395fac9dcd0413cd13e12b02075312c778f7c9c76e/google_re2-1.1.20250722-1-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:919b0f1064509002024b1510845d8d50442d51428f564d09fffaab46802b2f19", size = 488265, upload-time = "2025-07-28T15:39:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/46/54/5b2d23ede4509deddec0894fb57d56334b79dd34215823aaf9d70a2d23c1/google_re2-1.1.20250722-1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:7bf0658f628b7f24fb4f5754c3688128bb4a650576abc7f3c6b18688692ab40f", size = 461440, upload-time = "2025-07-28T15:40:01.001Z" }, + { url = "https://files.pythonhosted.org/packages/cc/34/0e31db1a790be0f351972925f0546644c0debf045c9810a8aa5c82b62ec6/google_re2-1.1.20250722-1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f7eedc88377ddfa145a58f2fd3441df106916f42d1e8c437c10d94dcfb1591fe", size = 489723, upload-time = "2025-07-28T15:40:02.361Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c7/fe6bcd3b1eb9f07d801f64fa69e581a52d2ab82da5feaecf772a92dd4799/google_re2-1.1.20250722-1-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:03761cb3b18144051cffec0e62424d98aaffbbb0c6f14c626b8645757fb6b75e", size = 462024, upload-time = "2025-07-28T15:40:04.074Z" }, + { url = "https://files.pythonhosted.org/packages/16/0a/30aa60d5a795167c7c710dc50bf70b2c67d3de1c37c99f70ff26ca089662/google_re2-1.1.20250722-1-cp311-cp311-macosx_15_0_x86_64.whl", hash = "sha256:bdad8093c371540a87a82f9b75ce268ff5878fd7fd90c058c1e13d498fa109c4", size = 485394, upload-time = "2025-07-28T15:40:05.632Z" }, + { url = "https://files.pythonhosted.org/packages/92/6d/84008e814a59e3cfec2dbb2186de7d90563f16e448328d8893d51a04de90/google_re2-1.1.20250722-1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9b2a9ea4a2bdeb4cb03283f513974bbae2db72fbf983a60100c0300e9f23c3a", size = 552039, upload-time = "2025-07-28T15:40:07.284Z" }, + { url = "https://files.pythonhosted.org/packages/74/a0/8277488e4f8575f3a271804611935d4b70083483a18aa62a344e3a7686cf/google_re2-1.1.20250722-1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:121b6a60e57d3d74a69eb514220d2f882fe28b5e83e53cda6e9c54b456fb4b66", size = 566652, upload-time = "2025-07-28T15:40:08.567Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c1/8e897f16640e1615b0b969d5a9e103b189974203233897648e855d4a9fb3/google_re2-1.1.20250722-1-cp311-cp311-win32.whl", hash = "sha256:1e99ae727729388897a561d190cf26802fbfce8d00366281f225a7e9ba363714", size = 428987, upload-time = "2025-07-28T15:40:10.018Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ba/6729a76b7409ad6e862d6f9bab35c3dbd26684265fa6d90af11914087ab4/google_re2-1.1.20250722-1-cp311-cp311-win_amd64.whl", hash = "sha256:84ced13526d25350ebbad85a26945d374b35757c22e519de939d0d2fe6750f63", size = 486208, upload-time = "2025-07-28T15:40:11.294Z" }, + { url = "https://files.pythonhosted.org/packages/9e/13/b9d07a68ac323f02ac96865df5a7f587e8ff7d08ea4143a944ead674f5d7/google_re2-1.1.20250722-1-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:52797f960c25086a29ea909e7d8e83a7812489ae179b174014b28701295687e4", size = 461397, upload-time = "2025-07-28T15:40:12.67Z" }, + { url = "https://files.pythonhosted.org/packages/7c/1b/0abbf1765186b04d9907042ec2ee6a3b16b85069f96084c5fad7813848e8/google_re2-1.1.20250722-1-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:81409451f8e2a6cdac3e016ae7cb5d618098da3446150a01ade7945dec4feae5", size = 491271, upload-time = "2025-07-28T15:40:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/ec/05/c8b01adc8bbb6d79f6485859a88d961dd1081e610aad389fc3e574331884/google_re2-1.1.20250722-1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:d31db243dc595af0773cd983b2ea49e1dd34bd5e6daf6d1f89eeed76a154c2d4", size = 463134, upload-time = "2025-07-28T15:40:15.409Z" }, + { url = "https://files.pythonhosted.org/packages/b7/38/1da5d111352f1e775d45280e3fe187e5220a53a1bc4288ec08570601d176/google_re2-1.1.20250722-1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:32574106eb821a719fd64bf26f291bcc19cd4c874b9bd32594f4c3d1be081cec", size = 492806, upload-time = "2025-07-28T15:40:16.619Z" }, + { url = "https://files.pythonhosted.org/packages/05/a3/ebcab973b4e767304ad20571ebb36e1769435d9cfaaaa1d5439ad2507667/google_re2-1.1.20250722-1-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:5bc328a1c9cd22e325839044f404ff2649ec05fc825a060b2511bd7162627a2f", size = 463116, upload-time = "2025-07-28T15:40:17.915Z" }, + { url = "https://files.pythonhosted.org/packages/3a/14/940907ed7d9cab14368b8cfaffb6b489e0c39d7f4510b13cd71badba5c89/google_re2-1.1.20250722-1-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:7f9ddd67a78a59e5e29592c9e2d19608146b5107a3446b922976aaa1d1001889", size = 487411, upload-time = "2025-07-28T15:40:19.128Z" }, + { url = "https://files.pythonhosted.org/packages/c1/64/112965b786c55497c0fe73849673f8fb953086f2319dad9a78c6422ee429/google_re2-1.1.20250722-1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4fc2f670b97695458e69f64e830ff9f7ba2383825e40f111c1e8d7225fadbc3", size = 550383, upload-time = "2025-07-28T15:40:20.715Z" }, + { url = "https://files.pythonhosted.org/packages/83/82/c95b7bb80ffb929429cb5f37d4fbdff5428c87076ff764f6b11737965131/google_re2-1.1.20250722-1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3208a8010902c8994cee12caa0dcef818b3f56e5109ef34ceede24ba909c8990", size = 566667, upload-time = "2025-07-28T15:40:21.958Z" }, + { url = "https://files.pythonhosted.org/packages/42/e3/e9b496bf96232e31422dc65f8f33d55f2dfb492de81eed575368e5332223/google_re2-1.1.20250722-1-cp312-cp312-win32.whl", hash = "sha256:40f10ec0e686b7b313cbbb45ac4fb404a5d262d7ebe50ff5ad3d000e8d4fb253", size = 429065, upload-time = "2025-07-28T15:40:23.381Z" }, + { url = "https://files.pythonhosted.org/packages/d0/fd/062cef7714eed6a7790639e00b7ed20aa837e10dd3878120e2cce3932325/google_re2-1.1.20250722-1-cp312-cp312-win_amd64.whl", hash = "sha256:225b3f8712280cac1c307d9d0b3cf4323b20962cb3b2f57bf37db3e4f1e09067", size = 486635, upload-time = "2025-07-28T15:40:24.598Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b5/62966d57169641e608bfb9e592c65ecaa4011572cfdf3491451b5d7fb5ff/google_re2-1.1.20250722-1-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:e318796ed53c743d319d409e166fbc83c3e5f8f19c1c8c30a019a1a5a0790022", size = 461596, upload-time = "2025-07-28T15:40:25.777Z" }, + { url = "https://files.pythonhosted.org/packages/98/bc/2191460356e0fdcf82e27c52d0b3c4c4eb8ca7ea3e1756d96f4c918c5237/google_re2-1.1.20250722-1-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:2661eb54dedf4de0bf83e11c3d4526cbe2664a31a22372df1967590a164cd654", size = 491310, upload-time = "2025-07-28T15:40:26.986Z" }, + { url = "https://files.pythonhosted.org/packages/12/a6/84f993126414000945fda38de39ff2155be0204337aa3ecb2200e8e0edfa/google_re2-1.1.20250722-1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:2487b5149786260a70844264c8c646faf382ab92a12ab1acc48669fbaa2f561a", size = 463216, upload-time = "2025-07-28T15:40:28.247Z" }, + { url = "https://files.pythonhosted.org/packages/e1/00/04ef211677595eeda99610c3259c02a73eefc39a8882663c455c4b979d54/google_re2-1.1.20250722-1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:c0327b174519ef76c266090d77359ecce8ac8ca28760b82b24ff825a76fcca8a", size = 492797, upload-time = "2025-07-28T15:40:29.726Z" }, + { url = "https://files.pythonhosted.org/packages/81/81/e1223144e8bbe4e01b5d220199239aea98166456464bfe719f4d008a5666/google_re2-1.1.20250722-1-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:250137a6da01d62262eab6466c6486d2c088a39bac9000edf9e3d11996eba053", size = 463229, upload-time = "2025-07-28T15:40:31.374Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5a/3c25ace76f7d16385fcdd6c46afbfaac3f5b5afcfb142e39fa81db245a6b/google_re2-1.1.20250722-1-cp313-cp313-macosx_15_0_x86_64.whl", hash = "sha256:5fdade52207219b73e9102dfd0d607ee03e09229ceed6b71d350bc106df181a8", size = 487451, upload-time = "2025-07-28T15:40:32.644Z" }, + { url = "https://files.pythonhosted.org/packages/bd/90/291cc45296f0c4599b82eecf04ab78043e40ceb3528fd7eb5f06e8e1d566/google_re2-1.1.20250722-1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c05ab5108713eb0d0fad7cf0a6856a18418625ae3468e62525f0d31914b3137c", size = 550492, upload-time = "2025-07-28T15:40:33.906Z" }, + { url = "https://files.pythonhosted.org/packages/45/98/9a88c831f726ca9ed157c2934722111426f0578cb54d49467112d2c0afde/google_re2-1.1.20250722-1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:282346724a98c04543ca13e2210f06d7e613933fe1475c2e9b577c2133587861", size = 566806, upload-time = "2025-07-28T15:40:35.132Z" }, + { url = "https://files.pythonhosted.org/packages/b9/43/77598154e34e6d992cb81d004510473265b440a1bc30148f7a79e5ae86a8/google_re2-1.1.20250722-1-cp313-cp313-win32.whl", hash = "sha256:3961c05530981daae19a7452724fe6de93448dbc7fdbafb36f017bd8d5b3a482", size = 429049, upload-time = "2025-07-28T15:40:36.342Z" }, + { url = "https://files.pythonhosted.org/packages/37/6d/d2cfcdabad0207b0f9ff813214518a15f573426e064b90ddcb75d77e9f50/google_re2-1.1.20250722-1-cp313-cp313-win_amd64.whl", hash = "sha256:879f1439e514b461b525f971afb6bee9a37743267f52a6ac60e1bbc26827a45d", size = 486706, upload-time = "2025-07-28T15:40:37.544Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3e/d0df4fe9d6acb89d6a67fbaa71eac04fe4081c443046b8affc3cac237d0d/google_re2-1.1.20250722-1-cp39-cp39-macosx_13_0_arm64.whl", hash = "sha256:4ac3b83ca1c7d54fadefd094dbcbcda7e78e4eae52f402dec2abc11128a5d452", size = 459223, upload-time = "2025-07-28T15:40:38.925Z" }, + { url = "https://files.pythonhosted.org/packages/44/b7/e4dfa4b691e03ae5656a515e899a4d9c7782857e3290dd2cb7d890e20641/google_re2-1.1.20250722-1-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:7f424835dbb89aab4b3d5b5df9d9134800e21aa5732d1a80a01317fcc421f7d3", size = 487197, upload-time = "2025-07-28T15:40:40.182Z" }, + { url = "https://files.pythonhosted.org/packages/2b/24/2f90ba0a265d11aac36d0c2a166a944de4ecd93e8585c89c3679daaf379d/google_re2-1.1.20250722-1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:552e0cd71f8902b8fb6ad72f8b63d77cb8caad6b65e25a836c9f676053b8a396", size = 460012, upload-time = "2025-07-28T15:40:41.411Z" }, + { url = "https://files.pythonhosted.org/packages/e3/05/5affc3617ed57eaba49cd8f314907341fc47db0e570d05349afcae5df46d/google_re2-1.1.20250722-1-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:9447d321a9697c3084f7bf8d468b9549e53eb0dc15bd1e578a252e440e32c2fd", size = 488040, upload-time = "2025-07-28T15:40:42.624Z" }, + { url = "https://files.pythonhosted.org/packages/dc/74/21d429e287a05d88ee94d37e11f31c0caea7bfdb049e57150cccbefdc13b/google_re2-1.1.20250722-1-cp39-cp39-macosx_15_0_arm64.whl", hash = "sha256:ead8a2557175fb1609e18445c819bb0f31813be08d1162cc61501d26fbbf3c15", size = 460945, upload-time = "2025-07-28T15:40:44.722Z" }, + { url = "https://files.pythonhosted.org/packages/4d/2d/d0ce2f0eebee76942be711d58b4dc54ed96bbd704decf091271e670288db/google_re2-1.1.20250722-1-cp39-cp39-macosx_15_0_x86_64.whl", hash = "sha256:fd71cb2a313bc8f218b71af44de569c062def6781290aeadfb0de75514ff63a3", size = 484530, upload-time = "2025-07-28T15:40:45.957Z" }, + { url = "https://files.pythonhosted.org/packages/1b/18/2982d60e0789b16555aee1a1734fd8e258e592978f51e3604881133e3977/google_re2-1.1.20250722-1-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3111ebd01c1d88746134a24b7e4370557548cdf232dcad6e362e3b7d45cac", size = 551843, upload-time = "2025-07-28T15:40:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/da/fa/c2db7feb34e7a22399bceadcd92ac5d90a9d718db0753d02a0359c82e35c/google_re2-1.1.20250722-1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c074f1a59b587004f1c929fbc8c5441b1d1ebc5f8a3ff876db972d311f281cfd", size = 565706, upload-time = "2025-07-28T15:40:48.459Z" }, + { url = "https://files.pythonhosted.org/packages/e6/a2/daf610bcddecc0b16c58746d8c5873c4ab0aff7f4a6c632e4ac00869f137/google_re2-1.1.20250722-1-cp39-cp39-win32.whl", hash = "sha256:fd98a1ea4da9cb9245a3cccdf0a8169fbfa1d516ac1bcac87dc49914f57f6a61", size = 428492, upload-time = "2025-07-28T15:40:49.733Z" }, + { url = "https://files.pythonhosted.org/packages/33/0b/d1a79ef5e3c2d37feb7c371ea0df602441200bfcbde667cd18d3813a0389/google_re2-1.1.20250722-1-cp39-cp39-win_amd64.whl", hash = "sha256:8922de94320c698f831525ceadd2d8f24912c4b02621308984d3dd1fcb10f6a0", size = 485734, upload-time = "2025-07-28T15:40:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/5e/87/881a3249dd20c4852c4c5b9c457dd0f4f439e13f21f99c355faff5b4c0d9/google_re2-1.1.20250722-2-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:78f5bdd587cf33a85914b6be383ff889d7b04abe2bf7c0d3ce3cd9ee97935954", size = 459207, upload-time = "2025-07-28T18:19:11.216Z" }, + { url = "https://files.pythonhosted.org/packages/39/0c/1987a8d4d7081e920b3bdce0b6f67afac1d02ce85befb1329e57536503ec/google_re2-1.1.20250722-2-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:7fc37f5ccef0138b79eef36f06b9955d24e6e70855f1ec3cf9c202ca478284d2", size = 487142, upload-time = "2025-07-28T18:19:13.017Z" }, + { url = "https://files.pythonhosted.org/packages/bc/03/f253fdeccf816aa0a0fbe029a123a6e9260bcf0ef30f1497656853cdddf6/google_re2-1.1.20250722-2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:138ecb65512edd788548b314b2192bd5bdf5d943d5aef1efc53afb507fc1980f", size = 459958, upload-time = "2025-07-28T18:19:16.695Z" }, + { url = "https://files.pythonhosted.org/packages/1d/6b/299c32ee5d7676cd26f6cf6d1af296627454ed023feb376781b4a5b25235/google_re2-1.1.20250722-2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:fcf665531e69e3543e74cc8b27242cb978bf8b442250b7fc2ca5d248387f4418", size = 487971, upload-time = "2025-07-28T18:19:18.016Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f9/e8b699e59a6b82e3adee305ad37c7d1c61008316dca4e38977e85455960c/google_re2-1.1.20250722-2-cp310-cp310-macosx_15_0_arm64.whl", hash = "sha256:eda4d7db1cf1907cad34f796ccaeaa463c80fefa1a6ecb6857dda7e456e50d9b", size = 460765, upload-time = "2025-07-28T18:19:19.697Z" }, + { url = "https://files.pythonhosted.org/packages/72/99/32b1d2b489bce5764950ededbf85aa6023b00ec5abef725961673dcbbd8c/google_re2-1.1.20250722-2-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:574b7f497b0f14c0003dde7edd5b6024529ac5aab9efd10e17766ecd94c16597", size = 484454, upload-time = "2025-07-28T18:19:21.507Z" }, + { url = "https://files.pythonhosted.org/packages/80/f9/4d8607186d3b87f3e0b4276cae57ab43aaa9d9f89162a9f8742b39ddd7f7/google_re2-1.1.20250722-2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:942e8564e1de4068168d4691acf658527e9bd98af91b917144c88290fa6f4631", size = 551397, upload-time = "2025-07-28T18:19:23.239Z" }, + { url = "https://files.pythonhosted.org/packages/dd/9e/7f289da0eeb1f6e4c07bdab901a690574c81fc38a256d50708142d4186a8/google_re2-1.1.20250722-2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b084131dea3253ac09c29eacc6eda326392da8081505c2e1d38e80d0e0b4e474", size = 565719, upload-time = "2025-07-28T18:19:24.585Z" }, + { url = "https://files.pythonhosted.org/packages/f6/7d/1b2c851a6d9a8bc73414cdd6d7bece57763c95c37c0bc3667e0179e762e7/google_re2-1.1.20250722-2-cp310-cp310-win32.whl", hash = "sha256:97030af2a903e18130229089bcddadf7817279645dd99842d0a967b91f56aba3", size = 427782, upload-time = "2025-07-28T18:19:25.893Z" }, + { url = "https://files.pythonhosted.org/packages/bf/14/caf5deec9ab6d1c8573178ab7f22b9b8d73fd52f908ca9cfc6f402bb651f/google_re2-1.1.20250722-2-cp310-cp310-win_amd64.whl", hash = "sha256:6dce0594f46aa8798e19829d3aa2c8622ccc2d4ce21ad2c7468141b50b78835c", size = 485274, upload-time = "2025-07-28T18:19:27.317Z" }, + { url = "https://files.pythonhosted.org/packages/60/de/df9457ea5138c08dad0c9ed4b49e5ed032543a850762603e54f3886648d5/google_re2-1.1.20250722-2-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:d43b3a32e0bb5397ecc5fb158c1a11b7cdf658dfc35ffd7b41032a6f105d3f51", size = 460040, upload-time = "2025-07-28T18:19:28.684Z" }, + { url = "https://files.pythonhosted.org/packages/ab/93/29ddfa354d41cd67e7206dee93013f7da848bc2cd447589493b9952d55bd/google_re2-1.1.20250722-2-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:4e96f8ae224e219cd047b6a533e38cd3bba749243788208786747630df3557e5", size = 488266, upload-time = "2025-07-28T18:19:29.993Z" }, + { url = "https://files.pythonhosted.org/packages/d1/54/e98cccd3f147f6ee24905f2527ae0bcbb21dc127a6eb8a8a30095cc6f171/google_re2-1.1.20250722-2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:62b142650dba4df5f6f9546723d0e4464e19e2756ff63d60d249be0089aedcd9", size = 461442, upload-time = "2025-07-28T18:19:31.629Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c5/9ed44fcde1568b6f2cce03f9b5de9afbcfda8385c764a8cc52e6bf6aee9c/google_re2-1.1.20250722-2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:1cc204ea8ac21e52f83f71e693960d3764160d0c4ed29b7ffde6cd6d7b984d50", size = 489723, upload-time = "2025-07-28T18:19:33.452Z" }, + { url = "https://files.pythonhosted.org/packages/35/fe/9d36e7c99bc2e4421803c800a27cf5e26742a0efb825914401eec5d00b26/google_re2-1.1.20250722-2-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:a5fd5eb3f34eb942c6929eda246ec227bed7e50cb906b75b6c2fe26b658e20b9", size = 462022, upload-time = "2025-07-28T18:19:34.848Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e8/1aaf8326c772e3f4ca7d97d599f77e8b054928f2a8113ed298b88f66d85e/google_re2-1.1.20250722-2-cp311-cp311-macosx_15_0_x86_64.whl", hash = "sha256:f49d1ff6f5b526b224a69fdc4f0df82cc806ea031be391d648aa94d6e8afec61", size = 485395, upload-time = "2025-07-28T18:19:36.757Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ea/ec3af787d3947f9ff5e59b3eed7e83b4f822f59f7a3e961e87f809cefa99/google_re2-1.1.20250722-2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bd791db1eadab27f12268594adf649abe0d310d00d51d1e3f9d944578f58c0a", size = 552039, upload-time = "2025-07-28T18:19:38.528Z" }, + { url = "https://files.pythonhosted.org/packages/cf/16/4704f7d3652dc141c3af74f157913a75a3698e59d819ab86de3562cf541b/google_re2-1.1.20250722-2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7edd8d743813e6354b9145dbf32509987e4b6876decae05b5532ee55e89ebf57", size = 566648, upload-time = "2025-07-28T18:19:39.967Z" }, + { url = "https://files.pythonhosted.org/packages/dc/82/2ad3572d4401996da8f2ab22a47f9bd8da3e0d0be1c1d5502b4ac421d04c/google_re2-1.1.20250722-2-cp311-cp311-win32.whl", hash = "sha256:01f8f97693926b10313785b4a069f3850b36cbad184b85a004111869d1e2fbe6", size = 428984, upload-time = "2025-07-28T18:19:41.71Z" }, + { url = "https://files.pythonhosted.org/packages/d3/40/89b4627cdc424dd944636adab7ee96695700a76890ebd407e0c4d0f76727/google_re2-1.1.20250722-2-cp311-cp311-win_amd64.whl", hash = "sha256:720b96d0179dfd6f6c07ad731d30218515436bd6e0ad3e5c506c5433ef30929f", size = 486209, upload-time = "2025-07-28T18:19:43.124Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a9/b0a7f59e5f3b4d0c70f38c7e7ac6bbacc96d085ce2576ea0b1d9f1fc4db6/google_re2-1.1.20250722-2-cp311-cp311-win_arm64.whl", hash = "sha256:cbdcdc9b2765eb80414ed9574f9bcc1f52e8a18ce91c6ad344fc1e80868e89a1", size = 642155, upload-time = "2025-07-28T18:19:45.078Z" }, + { url = "https://files.pythonhosted.org/packages/6f/09/aaaf173eaed1367cd6c285a72c2a660e8f831ea8117330539440506c8795/google_re2-1.1.20250722-2-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:e0bc1bd9b0f31364a48a5c9d2e3ddef31c47b3567b2270b27b6ba56e0aee8405", size = 461397, upload-time = "2025-07-28T18:19:46.909Z" }, + { url = "https://files.pythonhosted.org/packages/68/77/f8dfaed9527dfe9f0515c7f0f9495b319dc42f82049ceb291c655fd555b7/google_re2-1.1.20250722-2-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:34a4630810dbdb7022639f4b61c834eb6846eda1601ebf5cb63fa220d30f331d", size = 491271, upload-time = "2025-07-28T18:19:48.33Z" }, + { url = "https://files.pythonhosted.org/packages/38/22/5313c77e3feb055c60b6c21a92432dfbb2fc7043e3a4e7b142a52f753cc9/google_re2-1.1.20250722-2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:728cfbf611706a7dd2cf04fd50e7b84eca630ad7e4daf04cf1101cfdada5db7e", size = 463134, upload-time = "2025-07-28T18:19:51.473Z" }, + { url = "https://files.pythonhosted.org/packages/12/29/0b87d17885990271fa0af6a40b03880701a335a8ace10f6f60cfc7c0c8b3/google_re2-1.1.20250722-2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fb199f86f5538ba8a6ef4540ae2b1fa1e805b457652901b06cb3310a5a6cc357", size = 492806, upload-time = "2025-07-28T18:19:52.905Z" }, + { url = "https://files.pythonhosted.org/packages/62/5f/7d6fef577a97b428ca1b9ecf6060bb800d69ddb4aef636f4d67d139074a2/google_re2-1.1.20250722-2-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:0aee96c2a2785b7ae0c225d52837e898132371adfe9ccda04e7b61dd6f5c2a9a", size = 463117, upload-time = "2025-07-28T18:19:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/42/5f/881b2c2665e33499403d11746122818e5bdee618fc16c4d6c11352146ce2/google_re2-1.1.20250722-2-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:7faaa0910f5df1b29a40395da193756beb630303fc9c39a8488a6a97de395463", size = 487411, upload-time = "2025-07-28T18:19:55.66Z" }, + { url = "https://files.pythonhosted.org/packages/43/2f/2e6e16b0dfe7738ee36c19f84b916299cf0386b3c434269b34e03852d7ff/google_re2-1.1.20250722-2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2703c396ec1bb9bdaa765aefbdb7164f44ae3de5cfb7ea76a40955bcd8305328", size = 550382, upload-time = "2025-07-28T18:19:57.179Z" }, + { url = "https://files.pythonhosted.org/packages/77/96/5cc8849771fc9dec5d2448117c9e827f4b4f2539b6b37afedf567e4ba575/google_re2-1.1.20250722-2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:739c0ac4729a79f22f986606c8a996a6cc1c5ef300ae59ac28cb76f250a5df08", size = 566666, upload-time = "2025-07-28T18:19:59.147Z" }, + { url = "https://files.pythonhosted.org/packages/cd/09/f55d7fde76ccb3fb95f99cb196233c085caece8c5d8f49be55d5baf6ddd4/google_re2-1.1.20250722-2-cp312-cp312-win32.whl", hash = "sha256:1d1a235d77695805e59efe907abe438388f280ea5d31bf0758d5b63cdc1e3c2f", size = 429063, upload-time = "2025-07-28T18:20:00.913Z" }, + { url = "https://files.pythonhosted.org/packages/66/97/a09d75b80f2e28fa4710687905d4531d411b1eff9f0cc4e3ef2faab0fb2e/google_re2-1.1.20250722-2-cp312-cp312-win_amd64.whl", hash = "sha256:e2dc7a81e06fb1caefbd145e54ca6fa0fed05e894b2821a6116c909f196362a9", size = 486638, upload-time = "2025-07-28T18:20:02.254Z" }, + { url = "https://files.pythonhosted.org/packages/da/33/a68c3129ec5a84d1789e0d499af7078797895897feaadc604035937befaa/google_re2-1.1.20250722-2-cp312-cp312-win_arm64.whl", hash = "sha256:836458e4d8f05b9118b2c27a9e66a8f4bcf4f2b2f647d5e7f810efbff11be8ac", size = 642420, upload-time = "2025-07-28T18:20:03.626Z" }, + { url = "https://files.pythonhosted.org/packages/27/d6/e2b14b0a3cee1840b5b30d9d219cbb77adb9b67042761055a3ad24c1ad0c/google_re2-1.1.20250722-2-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:fb1be22193a9e801f8ab0347aa9f9408290fe04c2fa56bd5ff66104667cf1796", size = 461594, upload-time = "2025-07-28T18:20:05.005Z" }, + { url = "https://files.pythonhosted.org/packages/14/70/cac8be15c380d2eea556792b4d594de3e67617cf625b0807121bd174a4b6/google_re2-1.1.20250722-2-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:503433f378fe9f7785a68f012bc136fd2e998de749b5f2f3f4a06770177da720", size = 491311, upload-time = "2025-07-28T18:20:06.422Z" }, + { url = "https://files.pythonhosted.org/packages/e9/30/f74c143ac2f939a499702dcf4a7648bdb8f06a7545db4f3e01325b41549a/google_re2-1.1.20250722-2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:48cb29756d47bab8a07ff4e4a8048c9b0dcbabe49e90e87f8c5aa4f090e219a9", size = 463215, upload-time = "2025-07-28T18:20:07.772Z" }, + { url = "https://files.pythonhosted.org/packages/6e/90/52652f9da956aae4f80011bb03194698112d025935484de0f563141428b0/google_re2-1.1.20250722-2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3cc5091fae3554cb52f04ae98bf137c02bd678671f97806e3ef13e8ec52ade99", size = 492798, upload-time = "2025-07-28T18:20:09.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/48/27101caf49aa0cc4ef84bf9dd718269a0f4fab260d95f5dfa53ef6f28caa/google_re2-1.1.20250722-2-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:91dd7f34ed573c7b70fdf940b57d30ba1f87af1440273142b400cba0d898bb3c", size = 463228, upload-time = "2025-07-28T18:20:10.888Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/e011c0a5f001fa761909fcae404e16db34792f71b8425bf9a4ba4412ea7b/google_re2-1.1.20250722-2-cp313-cp313-macosx_15_0_x86_64.whl", hash = "sha256:c0fc0854f0ede86457ec7d70bc8bb23e7f6ab2fff3358fecca40e00b49927b96", size = 487451, upload-time = "2025-07-28T18:20:12.401Z" }, + { url = "https://files.pythonhosted.org/packages/d5/b3/1322ebaf07fd5738bd62b28259c4ab6046ead169cc5a64b7e94e196df4ff/google_re2-1.1.20250722-2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c7b9d5fd899610062eca570f38b66fd6de6f52031feccd1eb02d0ca6a60982b", size = 550492, upload-time = "2025-07-28T18:20:13.783Z" }, + { url = "https://files.pythonhosted.org/packages/d6/8a/721e41a6d1658ee12c9274fc4bbee02c23894be1bf714c27b67465821a18/google_re2-1.1.20250722-2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3697f258420ef9180e82459d526043078feee20d17642bd7ab09354634b732ed", size = 566804, upload-time = "2025-07-28T18:20:15.191Z" }, + { url = "https://files.pythonhosted.org/packages/cc/ef/64a4ac3c206e419c0eb4ac91eee807097738ad7d59aff10f7e22d2da9abc/google_re2-1.1.20250722-2-cp313-cp313-win32.whl", hash = "sha256:a201a4ca5c96736ae276d4ba8284bcd80d1a091988ea2a9d44ef576ae5e925ab", size = 429049, upload-time = "2025-07-28T18:20:16.534Z" }, + { url = "https://files.pythonhosted.org/packages/05/58/c8ec5a1c070922f0272ca3d7d44853eb5babc37668e25e4f45f0a0e1de1a/google_re2-1.1.20250722-2-cp313-cp313-win_amd64.whl", hash = "sha256:8575ed57522af14c00a6ce616459c934a553cdaa2f6d83312e2dbc2364bf1d03", size = 486707, upload-time = "2025-07-28T18:20:17.881Z" }, + { url = "https://files.pythonhosted.org/packages/e7/5c/c11c47908dbf287e7d26313f7cb766c6f7150ae9844bd718f4a48a4bbdbd/google_re2-1.1.20250722-2-cp313-cp313-win_arm64.whl", hash = "sha256:3d9ec2052befcada22b0941cd5ac6ada18023353c1e146aa5c9c16a3189b3cbf", size = 642451, upload-time = "2025-07-28T18:20:19.605Z" }, + { url = "https://files.pythonhosted.org/packages/2c/da/3d4b0860efc0ade017c197fd69d47a9bad36039e2a6ee54fbba4875da4db/google_re2-1.1.20250722-2-cp39-cp39-macosx_13_0_arm64.whl", hash = "sha256:0cdc640d98a619937a970fae1115095da8cb5a02b6f763913b4d1df784bd5891", size = 459221, upload-time = "2025-07-28T18:20:21.034Z" }, + { url = "https://files.pythonhosted.org/packages/05/cc/630afe4c2641e69213f303ee715508c7d8e575c756f0214ed7a2e9fd3c29/google_re2-1.1.20250722-2-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:8c34d555f26e80a6aee40f9b3022c7080de2d1600af56a1ffac57db5907216b1", size = 487197, upload-time = "2025-07-28T18:20:22.549Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ec/986e39a414b406da9c2c48517b03c1cab2d3888e51dc83cd1c4bc31eddaa/google_re2-1.1.20250722-2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:78103346dcd05a91dd4ef85e70f5f01ba47c5b34699c5d4d7b4deec39d38fd6f", size = 460012, upload-time = "2025-07-28T18:20:23.956Z" }, + { url = "https://files.pythonhosted.org/packages/fe/02/bd3d4560632dbeec184fc196b5f2a16af7c76c883ea5f8f23d79215c2486/google_re2-1.1.20250722-2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:a3b0b20c4241003fe94e1784a16e9f046d156a5f27049c89287718e0e844d128", size = 488040, upload-time = "2025-07-28T18:20:25.721Z" }, + { url = "https://files.pythonhosted.org/packages/a8/69/b0d940ca732dd2a09656af002bb7602eaef59bfd45d800e3ecce16595d79/google_re2-1.1.20250722-2-cp39-cp39-macosx_15_0_arm64.whl", hash = "sha256:ecdc0811be0a83ed180e22437d68d08192b65c7bc52988f43bd19e8560e9ebbe", size = 460944, upload-time = "2025-07-28T18:20:27.059Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/cd81139f4bad6b91d2add966e5b0f348e25bebcc5bd4b5e09be855a1f244/google_re2-1.1.20250722-2-cp39-cp39-macosx_15_0_x86_64.whl", hash = "sha256:fd6e66ca19a09647887fb2127d6ac5dde33afc0a34c1ea989e86f5102b37d4eb", size = 484534, upload-time = "2025-07-28T18:20:28.521Z" }, + { url = "https://files.pythonhosted.org/packages/ff/50/3c2208c79fea92e9cb68b8a27ff866796b63ef31764904cd9191daa3ccec/google_re2-1.1.20250722-2-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6fdde12541a5be971e4bb32ddeb69131a8998285713e1ee783bdd86e2a08b18", size = 551841, upload-time = "2025-07-28T18:20:29.896Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2e/1babde5bd843c6c64d005926a7a30e90f81cc69e2865cd03b84c899edcad/google_re2-1.1.20250722-2-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c76f94685960801eac4ffd52e5d83c3f61cff7ba29c2d81dc7cb8126bafe5341", size = 565704, upload-time = "2025-07-28T18:20:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/30/fd/4e01c8acd4c1799a35b144a96bbb23a3b52a096225507193708540e4e7dd/google_re2-1.1.20250722-2-cp39-cp39-win32.whl", hash = "sha256:2618f8dad592cf02efd6900fd6c539c3acc4ffbd0295d205a3297e8198c093e8", size = 428492, upload-time = "2025-07-28T18:20:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4e/bb6856475c91961db9bed13982fd6ee6329206e08ba4718da5d7009cb9b6/google_re2-1.1.20250722-2-cp39-cp39-win_amd64.whl", hash = "sha256:dfc3cf4d7dc9445a54e7af88d5bd6e4d24269c83885a1d3325fd56567ce7e59d", size = 485737, upload-time = "2025-07-28T18:20:34.438Z" }, +] + [[package]] name = "jmespath" version = "1.0.1" @@ -114,6 +225,11 @@ dependencies = [ { name = "protobuf" }, ] +[package.optional-dependencies] +re2 = [ + { name = "google-re2" }, +] + [package.dev-dependencies] dev = [ { name = "mypy" }, @@ -124,8 +240,10 @@ dev = [ [package.metadata] requires-dist = [ { name = "cel-python", specifier = "==0.2.*" }, + { name = "google-re2", marker = "extra == 're2'" }, { name = "protobuf", specifier = "==6.*" }, ] +provides-extras = ["re2"] [package.metadata.requires-dev] dev = [ From bb14f7cf1f19175a2de5ecc5f9a6378d74fa161c Mon Sep 17 00:00:00 2001 From: Steve Ayers Date: Thu, 31 Jul 2025 13:39:36 -0400 Subject: [PATCH 2/4] Use find_spec --- pyproject.toml | 2 +- test/test_matches.py | 18 +++++++++--------- uv.lock | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d7717060..db77fd4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ dependencies = [ ] [project.optional-dependencies] re2 = [ - "google-re2", + "google-re2==1.*", ] [project.urls] diff --git a/test/test_matches.py b/test/test_matches.py index 85c48fc6..3accacdf 100644 --- a/test/test_matches.py +++ b/test/test_matches.py @@ -12,33 +12,33 @@ # See the License for the specific language governing permissions and # limitations under the License. +import importlib.util import unittest import celpy from celpy import celtypes -from protovalidate.internal.extra_func import cel_matches_re2, cel_matches_re +from protovalidate.internal.extra_func import cel_matches_re, cel_matches_re2 _USE_RE2 = True -try: - import re2 # type: ignore -except ImportError: +spec = importlib.util.find_spec("re2") +if spec is None: _USE_RE2 = False -class TestCollectViolations(unittest.TestCase): +class TestCollectViolations(unittest.TestCase): @unittest.skipUnless(_USE_RE2, "Requires 're2'") def test_function_matches_re2(self): empty_string = celtypes.StringType("") # \z is valid re2 syntax for end of text - assert cel_matches_re2(empty_string, "^\\z") + self.assertTrue(cel_matches_re2(empty_string, "^\\z")) # \Z is invalid re2 syntax - assert isinstance(cel_matches_re2(empty_string, "^\\Z"), celpy.CELEvalError) + self.assertIsInstance(cel_matches_re2(empty_string, "^\\Z"), celpy.CELEvalError) @unittest.skipUnless(_USE_RE2 is False, "Requires 're'") def test_function_matches_re(self): empty_string = celtypes.StringType("") # \z is invalid re syntax - assert isinstance(cel_matches_re(empty_string, "^\\z"), celpy.CELEvalError) + self.assertIsInstance(cel_matches_re(empty_string, "^\\z"), celpy.CELEvalError) # \Z is valid re syntax for end of text - assert cel_matches_re(empty_string, "^\\Z") + self.assertTrue(cel_matches_re(empty_string, "^\\Z")) diff --git a/uv.lock b/uv.lock index 98b5a082..cba7e025 100644 --- a/uv.lock +++ b/uv.lock @@ -240,7 +240,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "cel-python", specifier = "==0.2.*" }, - { name = "google-re2", marker = "extra == 're2'" }, + { name = "google-re2", marker = "extra == 're2'", specifier = "==1.*" }, { name = "protobuf", specifier = "==6.*" }, ] provides-extras = ["re2"] From d98e9c0f7510f439275d15668857795c8033f9ed Mon Sep 17 00:00:00 2001 From: Steve Ayers Date: Thu, 31 Jul 2025 13:43:19 -0400 Subject: [PATCH 3/4] Update README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 991ebc04..e83bd26a 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,12 @@ To install the package, use `pip`: pip install protovalidate ``` +Protovalidate also has an optional dependency on [google-re2](https://pypi.org/project/google-re2/). If you require re2 syntax for your regular expressions, install the extra dep as follows: + +```shell +pip install protovalidate[re2] +``` + ## Documentation Comprehensive documentation for Protovalidate is available in [Buf's documentation library][protovalidate]. From 80ffd4d12b9aab034a732c54a4adfc7e593adb78 Mon Sep 17 00:00:00 2001 From: Steve Ayers Date: Thu, 31 Jul 2025 14:58:22 -0400 Subject: [PATCH 4/4] Feedback --- Makefile | 2 +- protovalidate/internal/extra_func.py | 2 +- pyproject.toml | 1 + uv.lock | 14 ++++++++++++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1a8a2e2e..23ea2515 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ test: generate install gettestdata ## Run unit tests .PHONY: testextra testextra: - uv pip install .[re2] + uv sync --extra re2 uv run -- python -m unittest .PHONY: conformance diff --git a/protovalidate/internal/extra_func.py b/protovalidate/internal/extra_func.py index 4602b095..b5a726f7 100644 --- a/protovalidate/internal/extra_func.py +++ b/protovalidate/internal/extra_func.py @@ -25,7 +25,7 @@ _USE_RE2 = True try: - import re2 # type: ignore + import re2 except ImportError: _USE_RE2 = False diff --git a/pyproject.toml b/pyproject.toml index db77fd4b..d96cf05f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ Issues = "https://github.com/bufbuild/protovalidate-python/issues" [dependency-groups] dev = [ + "google-re2-stubs>=0.1.1", "mypy", "ruff", "types-protobuf==6.30.2.20250503", diff --git a/uv.lock b/uv.lock index cba7e025..1e4d5b3a 100644 --- a/uv.lock +++ b/uv.lock @@ -130,6 +130,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5e/4e/bb6856475c91961db9bed13982fd6ee6329206e08ba4718da5d7009cb9b6/google_re2-1.1.20250722-2-cp39-cp39-win_amd64.whl", hash = "sha256:dfc3cf4d7dc9445a54e7af88d5bd6e4d24269c83885a1d3325fd56567ce7e59d", size = 485737, upload-time = "2025-07-28T18:20:34.438Z" }, ] +[[package]] +name = "google-re2-stubs" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-re2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/a4/d8d16007eb6a6eb137ec3b6344a67199837e14a73709efeaa7285d2660eb/google_re2_stubs-0.1.1.tar.gz", hash = "sha256:f5ebef2f4188957bf980a5ad88ab266589638e5090329e6a0fde99f6bb684657", size = 4009, upload-time = "2024-10-21T15:22:43.861Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/86/2134e8ff65a22e30883bfc38ac34c5c76fd88cc9cef02ff3561f05db2b68/google_re2_stubs-0.1.1-py3-none-any.whl", hash = "sha256:a82eb6c3accd20879d711cd38151583d8a154fcca755f43a5595143a484c8118", size = 3676, upload-time = "2024-10-21T15:22:43.08Z" }, +] + [[package]] name = "jmespath" version = "1.0.1" @@ -232,6 +244,7 @@ re2 = [ [package.dev-dependencies] dev = [ + { name = "google-re2-stubs" }, { name = "mypy" }, { name = "ruff" }, { name = "types-protobuf" }, @@ -247,6 +260,7 @@ provides-extras = ["re2"] [package.metadata.requires-dev] dev = [ + { name = "google-re2-stubs", specifier = ">=0.1.1" }, { name = "mypy" }, { name = "ruff" }, { name = "types-protobuf", specifier = "==6.30.2.20250503" },