Skip to content

Commit 30fb873

Browse files
authored
Support Python >=3.8 (#112)
By using explicit typing classes.
1 parent 91f4d95 commit 30fb873

10 files changed

Lines changed: 60 additions & 44 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
runs-on: ubuntu-latest
1717
strategy:
1818
matrix:
19-
python-version: ["3.10", "3.11"]
19+
python-version: ["3.8", "3.9", "3.10", "3.11"]
2020
steps:
2121
- name: Checkout code
2222
uses: actions/checkout@v4

.github/workflows/conformance.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
runs-on: ubuntu-latest
1717
strategy:
1818
matrix:
19-
python-version: ["3.10", "3.11"]
19+
python-version: ["3.8", "3.9", "3.10", "3.11"]
2020
steps:
2121
- name: Checkout code
2222
uses: actions/checkout@v4

Pipfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ exceptiongroup = "*"
1717
tomli = "*"
1818

1919
[requires]
20-
python_version = "3.10"
20+
python_version = "3.8"

Pipfile.lock

Lines changed: 10 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

protovalidate/internal/constraints.py

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def unwrap(msg: message.Message) -> celtypes.Value:
6262
}
6363

6464

65-
def _msg_to_cel(msg: message.Message) -> dict[str, celtypes.Value]:
65+
def _msg_to_cel(msg: message.Message) -> typing.Dict[str, celtypes.Value]:
6666
ctor = _MSG_TYPE_URL_TO_CTOR.get(msg.DESCRIPTOR.full_name)
6767
if ctor is not None:
6868
return ctor(msg)
@@ -214,16 +214,21 @@ def validate(self, ctx: ConstraintContext, message: message.Message): # noqa: A
214214
class CelConstraintRules(ConstraintRules):
215215
"""A constraint that has rules written in CEL."""
216216

217-
_runners: list[tuple[celpy.Runner, expression_pb2.Constraint | private_pb2.Constraint]]
217+
_runners: typing.List[typing.Tuple[celpy.Runner, typing.Union[expression_pb2.Constraint, private_pb2.Constraint]]]
218218
_rules_cel: celtypes.Value = None
219219

220-
def __init__(self, rules: message.Message | None):
220+
def __init__(self, rules: typing.Optional[message.Message]):
221221
self._runners = []
222222
if rules is not None:
223223
self._rules_cel = _msg_to_cel(rules)
224224

225225
def _validate_cel(
226-
self, ctx: ConstraintContext, field_name: str, activation: dict[str, typing.Any], *, for_key: bool = False
226+
self,
227+
ctx: ConstraintContext,
228+
field_name: str,
229+
activation: typing.Dict[str, typing.Any],
230+
*,
231+
for_key: bool = False,
227232
):
228233
activation["rules"] = self._rules_cel
229234
activation["now"] = celtypes.TimestampType(datetime.datetime.now(tz=datetime.timezone.utc))
@@ -241,8 +246,8 @@ def _validate_cel(
241246
def add_rule(
242247
self,
243248
env: celpy.Environment,
244-
funcs: dict[str, celpy.CELFunction],
245-
rules: expression_pb2.Constraint | private_pb2.Constraint,
249+
funcs: typing.Dict[str, celpy.CELFunction],
250+
rules: typing.Union[expression_pb2.Constraint, private_pb2.Constraint],
246251
):
247252
ast = env.compile(rules.expression)
248253
prog = env.program(ast, functions=funcs)
@@ -256,7 +261,7 @@ def validate(self, ctx: ConstraintContext, message: message.Message):
256261
self._validate_cel(ctx, "", {"this": _msg_to_cel(message)})
257262

258263

259-
def check_field_type(field: descriptor.FieldDescriptor, expected: int, wrapper_name: str | None = None):
264+
def check_field_type(field: descriptor.FieldDescriptor, expected: int, wrapper_name: typing.Optional[str] = None):
260265
if field.type != expected and (
261266
field.type != descriptor.FieldDescriptor.TYPE_MESSAGE or field.message_type.full_name != wrapper_name
262267
):
@@ -273,7 +278,7 @@ class FieldConstraintRules(CelConstraintRules):
273278
def __init__(
274279
self,
275280
env: celpy.Environment,
276-
funcs: dict[str, celpy.CELFunction],
281+
funcs: typing.Dict[str, celpy.CELFunction],
277282
field: descriptor.FieldDescriptor,
278283
field_level: validate_pb2.FieldConstraints,
279284
):
@@ -320,13 +325,13 @@ def _validate_value(self, ctx: ConstraintContext, field_path: str, val: typing.A
320325
class AnyConstraintRules(FieldConstraintRules):
321326
"""Rules for an Any field."""
322327

323-
_in: list[str] = [] # noqa: RUF012
324-
_not_in: list[str] = [] # noqa: RUF012
328+
_in: typing.List[str] = [] # noqa: RUF012
329+
_not_in: typing.List[str] = [] # noqa: RUF012
325330

326331
def __init__(
327332
self,
328333
env: celpy.Environment,
329-
funcs: dict[str, celpy.CELFunction],
334+
funcs: typing.Dict[str, celpy.CELFunction],
330335
field: descriptor.FieldDescriptor,
331336
field_level: validate_pb2.FieldConstraints,
332337
):
@@ -362,7 +367,7 @@ class EnumConstraintRules(FieldConstraintRules):
362367
def __init__(
363368
self,
364369
env: celpy.Environment,
365-
funcs: dict[str, celpy.CELFunction],
370+
funcs: typing.Dict[str, celpy.CELFunction],
366371
field: descriptor.FieldDescriptor,
367372
field_level: validate_pb2.FieldConstraints,
368373
):
@@ -387,15 +392,15 @@ def validate(self, ctx: ConstraintContext, message: message.Message):
387392
class RepeatedConstraintRules(FieldConstraintRules):
388393
"""Rules for a repeated field."""
389394

390-
_item_rules: FieldConstraintRules | None = None
395+
_item_rules: typing.Optional[FieldConstraintRules] = None
391396

392397
def __init__(
393398
self,
394399
env: celpy.Environment,
395-
funcs: dict[str, celpy.CELFunction],
400+
funcs: typing.Dict[str, celpy.CELFunction],
396401
field: descriptor.FieldDescriptor,
397402
field_level: validate_pb2.FieldConstraints,
398-
item_rules: FieldConstraintRules | None,
403+
item_rules: typing.Optional[FieldConstraintRules],
399404
):
400405
super().__init__(env, funcs, field, field_level)
401406
if item_rules is not None:
@@ -422,17 +427,17 @@ def validate(self, ctx: ConstraintContext, message: message.Message):
422427
class MapConstraintRules(FieldConstraintRules):
423428
"""Rules for a map field."""
424429

425-
_key_rules: FieldConstraintRules | None = None
426-
_value_rules: FieldConstraintRules | None = None
430+
_key_rules: typing.Optional[FieldConstraintRules] = None
431+
_value_rules: typing.Optional[FieldConstraintRules] = None
427432

428433
def __init__(
429434
self,
430435
env: celpy.Environment,
431-
funcs: dict[str, celpy.CELFunction],
436+
funcs: typing.Dict[str, celpy.CELFunction],
432437
field: descriptor.FieldDescriptor,
433438
field_level: validate_pb2.FieldConstraints,
434-
key_rules: FieldConstraintRules | None,
435-
value_rules: FieldConstraintRules | None,
439+
key_rules: typing.Optional[FieldConstraintRules],
440+
value_rules: typing.Optional[FieldConstraintRules],
436441
):
437442
super().__init__(env, funcs, field, field_level)
438443
if key_rules is not None:
@@ -480,15 +485,15 @@ class ConstraintFactory:
480485
"""Factory for creating and caching constraints."""
481486

482487
_env: celpy.Environment
483-
_funcs: dict[str, celpy.CELFunction]
484-
_cache: dict[descriptor.Descriptor, list[ConstraintRules] | Exception]
488+
_funcs: typing.Dict[str, celpy.CELFunction]
489+
_cache: typing.Dict[descriptor.Descriptor, typing.Union[typing.List[ConstraintRules], Exception]]
485490

486-
def __init__(self, funcs: dict[str, celpy.CELFunction]):
491+
def __init__(self, funcs: typing.Dict[str, celpy.CELFunction]):
487492
self._env = celpy.Environment()
488493
self._funcs = funcs
489494
self._cache = {}
490495

491-
def get(self, descriptor: descriptor.Descriptor) -> list[ConstraintRules]:
496+
def get(self, descriptor: descriptor.Descriptor) -> typing.List[ConstraintRules]:
492497
if descriptor not in self._cache:
493498
try:
494499
self._cache[descriptor] = self._new_constraints(descriptor)
@@ -647,9 +652,9 @@ def _new_field_constraint(
647652
item_rule = self._new_scalar_field_constraint(field, rules.repeated.items)
648653
return RepeatedConstraintRules(self._env, self._funcs, field, rules, item_rule)
649654

650-
def _new_constraints(self, desc: descriptor.Descriptor) -> list[ConstraintRules]:
651-
result: list[ConstraintRules] = []
652-
constraint: ConstraintRules | None = None
655+
def _new_constraints(self, desc: descriptor.Descriptor) -> typing.List[ConstraintRules]:
656+
result: typing.List[ConstraintRules] = []
657+
constraint: typing.Optional[ConstraintRules] = None
653658
if validate_pb2.message in desc.GetOptions().Extensions:
654659
message_level = desc.GetOptions().Extensions[validate_pb2.message]
655660
if message_level.disabled:

protovalidate/internal/extra_func.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import math
16+
import typing
1617
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network, ip_address, ip_network
1718
from urllib import parse as urlparse
1819

@@ -59,8 +60,8 @@ def validate_email(addr):
5960
return _validate_hostname(parts[1])
6061

6162

62-
def is_ip(val: celtypes.Value, version: celtypes.Value | None = None) -> celpy.Result:
63-
if not isinstance(val, celtypes.BytesType | celtypes.StringType):
63+
def is_ip(val: celtypes.Value, version: typing.Optional[celtypes.Value] = None) -> celpy.Result:
64+
if not isinstance(val, (celtypes.BytesType, celtypes.StringType)):
6465
msg = "invalid argument, expected string or bytes"
6566
raise celpy.EvalError(msg)
6667
try:
@@ -79,7 +80,7 @@ def is_ip(val: celtypes.Value, version: celtypes.Value | None = None) -> celpy.R
7980

8081

8182
def is_ip_prefix(val: celtypes.Value, *args) -> celpy.Result:
82-
if not isinstance(val, celtypes.BytesType | celtypes.StringType):
83+
if not isinstance(val, (celtypes.BytesType, celtypes.StringType)):
8384
msg = "invalid argument, expected string or bytes"
8485
raise celpy.EvalError(msg)
8586
version = None
@@ -147,7 +148,7 @@ def is_nan(val: celtypes.Value) -> celpy.Result:
147148
return celtypes.BoolType(math.isnan(val))
148149

149150

150-
def is_inf(val: celtypes.Value, sign: None | celtypes.Value = None) -> celpy.Result:
151+
def is_inf(val: celtypes.Value, sign: typing.Optional[celtypes.Value] = None) -> celpy.Result:
151152
if not isinstance(val, celtypes.DoubleType):
152153
msg = "invalid argument, expected double"
153154
raise celpy.EvalError(msg)
@@ -172,7 +173,7 @@ def unique(val: celtypes.Value) -> celpy.Result:
172173
return celtypes.BoolType(len(val) == len(set(val)))
173174

174175

175-
def make_extra_funcs(locale: str) -> dict[str, celpy.CELFunction]:
176+
def make_extra_funcs(locale: str) -> typing.Dict[str, celpy.CELFunction]:
176177
string_fmt = string_format.StringFormat(locale)
177178
return {
178179
# Missing standard functions

protovalidate/internal/string_format.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def format_string(self, arg: celtypes.Value) -> celpy.Result:
152152
return celtypes.StringType(arg)
153153

154154
def format_value(self, arg: celtypes.Value) -> celpy.Result:
155-
if isinstance(arg, celtypes.StringType | str):
155+
if isinstance(arg, (celtypes.StringType, str)):
156156
return celtypes.StringType(quote(arg))
157157
if isinstance(arg, celtypes.UintType):
158158
return celtypes.StringType(arg)

protovalidate/validator.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import typing
16+
1517
from google.protobuf import message
1618

1719
from buf.validate import expression_pb2 # type: ignore
@@ -98,7 +100,7 @@ def __init__(self, msg: str, violations: expression_pb2.Violations):
98100
super().__init__(msg)
99101
self.violations = violations
100102

101-
def errors(self) -> list[expression_pb2.Violation]:
103+
def errors(self) -> typing.List[expression_pb2.Violation]:
102104
"""
103105
Returns the validation errors as a simple Python list, rather than the
104106
Protobuf-specific collection type used by Violations.

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ description = "Protocol Buffer Validation for Python"
88
readme = "README.md"
99
license = { file = "LICENSE" }
1010
keywords = ["validate", "protobuf", "protocol buffer"]
11-
requires-python = ">=3.10"
11+
requires-python = ">=3.8"
1212
classifiers = [
1313
"Programming Language :: Python :: 3",
1414
"License :: OSI Approved :: Apache Software License",
@@ -26,11 +26,11 @@ Issues = "https://github.com/bufbuild/protovalidate-python/issues"
2626
source = "vcs"
2727

2828
[tool.black]
29-
target-version = ["py310"]
29+
target-version = ["py38"]
3030
line-length = 120
3131

3232
[tool.ruff]
33-
target-version = "py310"
33+
target-version = "py38"
3434
line-length = 120
3535
select = [
3636
"A",

tests/conformance/runner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
from buf.validate.conformance.harness import harness_pb2
5050

5151

52-
def run_test_case(tc: typing.Any, result: harness_pb2.TestResult | None = None) -> harness_pb2.TestResult:
52+
def run_test_case(tc: typing.Any, result: typing.Optional[harness_pb2.TestResult] = None) -> harness_pb2.TestResult:
5353
if result is None:
5454
result = harness_pb2.TestResult()
5555
# Run the validator
@@ -67,7 +67,7 @@ def run_test_case(tc: typing.Any, result: harness_pb2.TestResult | None = None)
6767
def run_any_test_case(
6868
pool: descriptor_pool.DescriptorPool,
6969
tc: any_pb2.Any,
70-
result: harness_pb2.TestResult | None = None,
70+
result: typing.Optional[harness_pb2.TestResult] = None,
7171
) -> harness_pb2.TestResult:
7272
type_name = tc.type_url.split("/")[-1]
7373
desc: descriptor.Descriptor = pool.FindMessageTypeByName(type_name)

0 commit comments

Comments
 (0)