From 415d038a3c56de2b850661593651bd0d80581b94 Mon Sep 17 00:00:00 2001 From: Stefan VanBuren Date: Mon, 2 Mar 2026 09:08:40 -0500 Subject: [PATCH 1/3] Add handling for protobuf==7 `protobuf` v7 was released last week, which dropped support for some of the APIs that we use for checking field types. This fixes those accesses so that we can still support all the way back to v5. Fixes #410. Ref: https://pypi.org/project/protobuf/ Ref: https://github.com/protocolbuffers/protobuf/releases/tag/v34.0 --- protovalidate/internal/rules.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/protovalidate/internal/rules.py b/protovalidate/internal/rules.py index ba8b0de..416ccb9 100644 --- a/protovalidate/internal/rules.py +++ b/protovalidate/internal/rules.py @@ -24,6 +24,17 @@ from buf.validate import validate_pb2 from protovalidate.internal.cel_field_presence import InterpretedRunner, in_has +# protobuf 7+ removed FieldDescriptor.label / LABEL_REPEATED in favour of is_repeated. +if hasattr(descriptor.FieldDescriptor, "LABEL_REPEATED"): + + def _is_repeated(field: descriptor.FieldDescriptor) -> bool: + return field.label == descriptor.FieldDescriptor.LABEL_REPEATED # type: ignore[attr-defined] + +else: + + def _is_repeated(field: descriptor.FieldDescriptor) -> bool: + return field.is_repeated # type: ignore[attr-defined] + class CompilationError(Exception): pass @@ -155,7 +166,7 @@ def _scalar_field_value_to_cel(val: typing.Any, field: descriptor.FieldDescripto def _field_value_to_cel(val: typing.Any, field: descriptor.FieldDescriptor) -> celtypes.Value: - if field.label == descriptor.FieldDescriptor.LABEL_REPEATED: + if _is_repeated(field): if field.message_type is not None and field.message_type.GetOptions().map_entry: return _map_field_value_to_cel(val, field) return _repeated_field_value_to_cel(val, field) @@ -165,7 +176,7 @@ def _field_value_to_cel(val: typing.Any, field: descriptor.FieldDescriptor) -> c def _is_empty_field(msg: message.Message, field: descriptor.FieldDescriptor) -> bool: if field.has_presence: return not _proto_message_has_field(msg, field) - if field.label == descriptor.FieldDescriptor.LABEL_REPEATED: + if _is_repeated(field): return len(_proto_message_get_field(msg, field)) == 0 return _proto_message_get_field(msg, field) == field.default_value @@ -194,7 +205,7 @@ def _map_field_to_cel(msg: message.Message, field: descriptor.FieldDescriptor) - def field_to_cel(msg: message.Message, field: descriptor.FieldDescriptor) -> celtypes.Value: - if field.label == descriptor.FieldDescriptor.LABEL_REPEATED: + if _is_repeated(field): return _repeated_field_to_cel(msg, field) elif field.message_type is not None and not _proto_message_has_field(msg, field): return None @@ -492,19 +503,15 @@ def check_field_type(field: descriptor.FieldDescriptor, expected: int, wrapper_n def _is_map(field: descriptor.FieldDescriptor): - return ( - field.label == descriptor.FieldDescriptor.LABEL_REPEATED - and field.message_type is not None - and field.message_type.GetOptions().map_entry - ) + return _is_repeated(field) and field.message_type is not None and field.message_type.GetOptions().map_entry def _is_list(field: descriptor.FieldDescriptor): - return field.label == descriptor.FieldDescriptor.LABEL_REPEATED and not _is_map(field) + return _is_repeated(field) and not _is_map(field) def _zero_value(field: descriptor.FieldDescriptor): - if field.message_type is not None and field.label != descriptor.FieldDescriptor.LABEL_REPEATED: + if field.message_type is not None and not _is_repeated(field): return _field_value_to_cel(message_factory.GetMessageClass(field.message_type)(), field) else: return _field_value_to_cel(field.default_value, field) @@ -1030,7 +1037,7 @@ def _new_field_rule( field: descriptor.FieldDescriptor, rules: validate_pb2.FieldRules, ) -> FieldRules: - if field.label != descriptor.FieldDescriptor.LABEL_REPEATED: + if not _is_repeated(field): return self._new_scalar_field_rule(field, rules) if field.message_type is not None and field.message_type.GetOptions().map_entry: key_rules = None @@ -1084,7 +1091,7 @@ def _new_rules(self, desc: descriptor.Descriptor) -> list[Rules]: if value_field.type != descriptor.FieldDescriptor.TYPE_MESSAGE: continue result.append(MapValMsgRule(self, field, key_field, value_field)) - elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: + elif _is_repeated(field): result.append(RepeatedMsgRule(self, field)) else: result.append(SubMsgRule(self, field)) From 7d6a87eb74c1073f8639f5f67921d48a1e226ab1 Mon Sep 17 00:00:00 2001 From: Stefan VanBuren Date: Mon, 2 Mar 2026 09:16:03 -0500 Subject: [PATCH 2/3] Specifically upgrade `protobuf` to v7 --- uv.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/uv.lock b/uv.lock index 8d5319e..3406592 100644 --- a/uv.lock +++ b/uv.lock @@ -386,17 +386,17 @@ wheels = [ [[package]] name = "protobuf" -version = "6.33.5" +version = "7.34.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/00/04a2ab36b70a52d0356852979e08b44edde0435f2115dc66e25f2100f3ab/protobuf-7.34.0.tar.gz", hash = "sha256:3871a3df67c710aaf7bb8d214cc997342e63ceebd940c8c7fc65c9b3d697591a", size = 454726, upload-time = "2026-02-27T00:30:25.421Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, - { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, - { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, - { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, - { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, + { url = "https://files.pythonhosted.org/packages/13/c4/6322ab5c8f279c4c358bc14eb8aefc0550b97222a39f04eb3c1af7a830fa/protobuf-7.34.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e329966799f2c271d5e05e236459fe1cbfdb8755aaa3b0914fa60947ddea408", size = 429248, upload-time = "2026-02-27T00:30:14.924Z" }, + { url = "https://files.pythonhosted.org/packages/45/99/b029bbbc61e8937545da5b79aa405ab2d9cf307a728f8c9459ad60d7a481/protobuf-7.34.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:9d7a5005fb96f3c1e64f397f91500b0eb371b28da81296ae73a6b08a5b76cdd6", size = 325753, upload-time = "2026-02-27T00:30:17.247Z" }, + { url = "https://files.pythonhosted.org/packages/cc/79/09f02671eb75b251c5550a1c48e7b3d4b0623efd7c95a15a50f6f9fc1e2e/protobuf-7.34.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:4a72a8ec94e7a9f7ef7fe818ed26d073305f347f8b3b5ba31e22f81fd85fca02", size = 340200, upload-time = "2026-02-27T00:30:18.672Z" }, + { url = "https://files.pythonhosted.org/packages/b5/57/89727baef7578897af5ed166735ceb315819f1c184da8c3441271dbcfde7/protobuf-7.34.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:964cf977e07f479c0697964e83deda72bcbc75c3badab506fb061b352d991b01", size = 324268, upload-time = "2026-02-27T00:30:20.088Z" }, + { url = "https://files.pythonhosted.org/packages/1f/3e/38ff2ddee5cc946f575c9d8cc822e34bde205cf61acf8099ad88ef19d7d2/protobuf-7.34.0-cp310-abi3-win32.whl", hash = "sha256:f791ec509707a1d91bd02e07df157e75e4fb9fbdad12a81b7396201ec244e2e3", size = 426628, upload-time = "2026-02-27T00:30:21.555Z" }, + { url = "https://files.pythonhosted.org/packages/cb/71/7c32eaf34a61a1bae1b62a2ac4ffe09b8d1bb0cf93ad505f42040023db89/protobuf-7.34.0-cp310-abi3-win_amd64.whl", hash = "sha256:9f9079f1dde4e32342ecbd1c118d76367090d4aaa19da78230c38101c5b3dd40", size = 437901, upload-time = "2026-02-27T00:30:22.836Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e7/14dc9366696dcb53a413449881743426ed289d687bcf3d5aee4726c32ebb/protobuf-7.34.0-py3-none-any.whl", hash = "sha256:e3b914dd77fa33fa06ab2baa97937746ab25695f389869afdf03e81f34e45dc7", size = 170716, upload-time = "2026-02-27T00:30:23.994Z" }, ] [[package]] From edcda2c55a45ae383ca34f10dddb86d290e935ed Mon Sep 17 00:00:00 2001 From: Stefan VanBuren Date: Mon, 2 Mar 2026 09:23:34 -0500 Subject: [PATCH 3/3] Flip check --- protovalidate/internal/rules.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/protovalidate/internal/rules.py b/protovalidate/internal/rules.py index 416ccb9..2669faa 100644 --- a/protovalidate/internal/rules.py +++ b/protovalidate/internal/rules.py @@ -25,15 +25,15 @@ from protovalidate.internal.cel_field_presence import InterpretedRunner, in_has # protobuf 7+ removed FieldDescriptor.label / LABEL_REPEATED in favour of is_repeated. -if hasattr(descriptor.FieldDescriptor, "LABEL_REPEATED"): +if hasattr(descriptor.FieldDescriptor, "is_repeated"): def _is_repeated(field: descriptor.FieldDescriptor) -> bool: - return field.label == descriptor.FieldDescriptor.LABEL_REPEATED # type: ignore[attr-defined] + return field.is_repeated # type: ignore[attr-defined] else: def _is_repeated(field: descriptor.FieldDescriptor) -> bool: - return field.is_repeated # type: ignore[attr-defined] + return field.label == descriptor.FieldDescriptor.LABEL_REPEATED # type: ignore[attr-defined] class CompilationError(Exception):