Skip to content
This repository was archived by the owner on Aug 19, 2025. It is now read-only.

Commit 6f25af5

Browse files
Switch to coerce_types on validator __init__.
1 parent 6a9590c commit 6f25af5

6 files changed

Lines changed: 72 additions & 67 deletions

File tree

tests/test_fields.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ def test_integer():
152152
value, error = validator.validate_or_error(float("nan"))
153153
assert error == ValidationError(text="Must be an integer.", code="integer")
154154

155-
validator = Integer()
156-
value, error = validator.validate_or_error("123", strict=True)
155+
validator = Integer(coerce_types=False)
156+
value, error = validator.validate_or_error("123")
157157
assert error == ValidationError(text="Must be a number.", code="type")
158158

159159
validator = Integer(allow_null=True)
@@ -166,8 +166,8 @@ def test_integer():
166166
assert value is None
167167
assert error is None
168168

169-
validator = Integer(allow_null=True)
170-
value, error = validator.validate_or_error("", strict=True)
169+
validator = Integer(allow_null=True, coerce_types=False)
170+
value, error = validator.validate_or_error("")
171171
assert error == ValidationError(text="Must be a number.", code="type")
172172

173173
validator = Integer(maximum=10)
@@ -242,8 +242,8 @@ def test_float():
242242
value, error = validator.validate_or_error(float("nan"))
243243
assert error == ValidationError(text="Must be finite.", code="finite")
244244

245-
validator = Float()
246-
value, error = validator.validate_or_error("123", strict=True)
245+
validator = Float(coerce_types=False)
246+
value, error = validator.validate_or_error("123")
247247
assert error == ValidationError(text="Must be a number.", code="type")
248248

249249
validator = Float(allow_null=True)
@@ -361,8 +361,8 @@ def test_boolean():
361361
assert value is None
362362
assert error is None
363363

364-
validator = Boolean()
365-
value, error = validator.validate_or_error("True", strict=True)
364+
validator = Boolean(coerce_types=False)
365+
value, error = validator.validate_or_error("True")
366366
assert error == ValidationError(text="Must be a boolean.", code="type")
367367

368368

tests/test_json_schema.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def load_test_cases():
7878
@pytest.mark.parametrize("schema,data,is_valid,description", test_cases)
7979
def test_json_schema(schema, data, is_valid, description):
8080
validator = from_json_schema(schema)
81-
value, error = validator.validate_or_error(data, strict=True)
81+
value, error = validator.validate_or_error(data)
8282
if is_valid:
8383
assert error is None, description
8484
else:
@@ -104,16 +104,12 @@ def test_to_from_json_schema(schema, data, is_valid, description):
104104
"""
105105
validator = from_json_schema(schema)
106106

107-
value_before_convert, error_before_convert = validator.validate_or_error(
108-
data, strict=True
109-
)
107+
value_before_convert, error_before_convert = validator.validate_or_error(data)
110108

111109
schema_after = to_json_schema(validator)
112110
validator = from_json_schema(schema_after)
113111

114-
value_after_convert, error_after_convert = validator.validate_or_error(
115-
data, strict=True
116-
)
112+
value_after_convert, error_after_convert = validator.validate_or_error(data)
117113

118114
assert error_before_convert == error_after_convert
119115
assert value_before_convert == value_after_convert

typesystem/composites.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def __init__(self, **kwargs: typing.Any) -> None:
1616
assert "allow_null" not in kwargs
1717
super().__init__(**kwargs)
1818

19-
def validate(self, value: typing.Any, strict: bool = False) -> typing.Any:
19+
def validate(self, value: typing.Any) -> typing.Any:
2020
raise self.validation_error("never")
2121

2222

@@ -38,11 +38,11 @@ def __init__(self, one_of: typing.List[Field], **kwargs: typing.Any) -> None:
3838
super().__init__(**kwargs)
3939
self.one_of = one_of
4040

41-
def validate(self, value: typing.Any, strict: bool = False) -> typing.Any:
41+
def validate(self, value: typing.Any) -> typing.Any:
4242
candidate = None
4343
match_count = 0
4444
for child in self.one_of:
45-
validated, error = child.validate_or_error(value, strict=strict)
45+
validated, error = child.validate_or_error(value)
4646
if error is None:
4747
match_count += 1
4848
candidate = validated
@@ -67,9 +67,9 @@ def __init__(self, all_of: typing.List[Field], **kwargs: typing.Any) -> None:
6767
super().__init__(**kwargs)
6868
self.all_of = all_of
6969

70-
def validate(self, value: typing.Any, strict: bool = False) -> typing.Any:
70+
def validate(self, value: typing.Any) -> typing.Any:
7171
for child in self.all_of:
72-
child.validate(value, strict=strict)
72+
child.validate(value)
7373
return value
7474

7575

@@ -87,8 +87,8 @@ def __init__(self, negated: Field, **kwargs: typing.Any) -> None:
8787
super().__init__(**kwargs)
8888
self.negated = negated
8989

90-
def validate(self, value: typing.Any, strict: bool = False) -> typing.Any:
91-
_, error = self.negated.validate_or_error(value, strict=strict)
90+
def validate(self, value: typing.Any) -> typing.Any:
91+
_, error = self.negated.validate_or_error(value)
9292
if error:
9393
return value
9494
raise self.validation_error("negated")
@@ -114,9 +114,9 @@ def __init__(
114114
self.then_clause = Any() if then_clause is None else then_clause
115115
self.else_clause = Any() if else_clause is None else else_clause
116116

117-
def validate(self, value: typing.Any, strict: bool = False) -> typing.Any:
118-
_, error = self.if_clause.validate_or_error(value, strict=strict)
117+
def validate(self, value: typing.Any) -> typing.Any:
118+
_, error = self.if_clause.validate_or_error(value)
119119
if error is None:
120-
return self.then_clause.validate(value, strict=strict)
120+
return self.then_clause.validate(value)
121121
else:
122-
return self.else_clause.validate(value, strict=strict)
122+
return self.else_clause.validate(value)

typesystem/fields.py

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,12 @@ def __init__(
4747
self._creation_counter = Field._creation_counter
4848
Field._creation_counter += 1
4949

50-
def validate(self, value: typing.Any, *, strict: bool = False) -> typing.Any:
50+
def validate(self, value: typing.Any) -> typing.Any:
5151
raise NotImplementedError() # pragma: no cover
5252

53-
def validate_or_error(
54-
self, value: typing.Any, *, strict: bool = False
55-
) -> ValidationResult:
53+
def validate_or_error(self, value: typing.Any) -> ValidationResult:
5654
try:
57-
value = self.validate(value, strict=strict)
55+
value = self.validate(value)
5856
except ValidationError as error:
5957
return ValidationResult(value=None, error=error)
6058
return ValidationResult(value=value, error=None)
@@ -112,6 +110,7 @@ def __init__(
112110
min_length: int = None,
113111
pattern: typing.Union[str, typing.Pattern] = None,
114112
format: str = None,
113+
coerce_types: bool = True,
115114
**kwargs: typing.Any,
116115
) -> None:
117116
super().__init__(**kwargs)
@@ -129,6 +128,7 @@ def __init__(
129128
self.max_length = max_length
130129
self.min_length = min_length
131130
self.format = format
131+
self.coerce_types = coerce_types
132132

133133
if pattern is None:
134134
self.pattern = None
@@ -140,10 +140,10 @@ def __init__(
140140
self.pattern = pattern.pattern
141141
self.pattern_regex = pattern
142142

143-
def validate(self, value: typing.Any, *, strict: bool = False) -> typing.Any:
143+
def validate(self, value: typing.Any) -> typing.Any:
144144
if value is None and self.allow_null:
145145
return None
146-
elif value is None and self.allow_blank and not strict:
146+
elif value is None and self.allow_blank and self.coerce_types:
147147
# Leniently cast nulls to empty strings if allow_blank.
148148
return ""
149149
elif value is None:
@@ -161,7 +161,7 @@ def validate(self, value: typing.Any, *, strict: bool = False) -> typing.Any:
161161
value = value.strip()
162162

163163
if not self.allow_blank and not value:
164-
if self.allow_null and not strict:
164+
if self.allow_null and self.coerce_types:
165165
# Leniently cast empty strings (after trimming) to null if allow_null.
166166
return None
167167
raise self.validation_error("blank")
@@ -212,6 +212,7 @@ def __init__(
212212
exclusive_maximum: typing.Union[int, float, decimal.Decimal] = None,
213213
precision: str = None,
214214
multiple_of: typing.Union[int, float, decimal.Decimal] = None,
215+
coerce_types: bool = True,
215216
**kwargs: typing.Any,
216217
):
217218
super().__init__(**kwargs)
@@ -234,11 +235,12 @@ def __init__(
234235
self.exclusive_maximum = exclusive_maximum
235236
self.multiple_of = multiple_of
236237
self.precision = precision
238+
self.coerce_types = coerce_types
237239

238-
def validate(self, value: typing.Any, *, strict: bool = False) -> typing.Any:
240+
def validate(self, value: typing.Any) -> typing.Any:
239241
if value is None and self.allow_null:
240242
return None
241-
elif value == "" and self.allow_null and not strict:
243+
elif value == "" and self.allow_null and self.coerce_types:
242244
return None
243245
elif value is None:
244246
raise self.validation_error("null")
@@ -250,7 +252,7 @@ def validate(self, value: typing.Any, *, strict: bool = False) -> typing.Any:
250252
and not value.is_integer()
251253
):
252254
raise self.validation_error("integer")
253-
elif not isinstance(value, (int, float)) and strict:
255+
elif not isinstance(value, (int, float)) and not self.coerce_types:
254256
raise self.validation_error("type")
255257

256258
try:
@@ -328,15 +330,19 @@ class Boolean(Field):
328330
}
329331
coerce_null_values = {"", "null", "none"}
330332

331-
def validate(self, value: typing.Any, *, strict: bool = False) -> typing.Any:
333+
def __init__(self, *, coerce_types: bool = True, **kwargs: typing.Any) -> None:
334+
super().__init__(**kwargs)
335+
self.coerce_types = coerce_types
336+
337+
def validate(self, value: typing.Any) -> typing.Any:
332338
if value is None and self.allow_null:
333339
return None
334340

335341
elif value is None:
336342
raise self.validation_error("null")
337343

338344
elif not isinstance(value, bool):
339-
if strict:
345+
if not self.coerce_types:
340346
raise self.validation_error("type")
341347

342348
if isinstance(value, str):
@@ -364,23 +370,25 @@ def __init__(
364370
self,
365371
*,
366372
choices: typing.Sequence[typing.Union[str, typing.Tuple[str, str]]] = None,
373+
coerce_types: bool = True,
367374
**kwargs: typing.Any,
368375
) -> None:
369376
super().__init__(**kwargs)
370377
self.choices = [
371378
(choice if isinstance(choice, (tuple, list)) else (choice, choice))
372379
for choice in choices or []
373380
]
381+
self.coerce_types = coerce_types
374382
assert all(len(choice) == 2 for choice in self.choices)
375383

376-
def validate(self, value: typing.Any, *, strict: bool = False) -> typing.Any:
384+
def validate(self, value: typing.Any) -> typing.Any:
377385
if value is None and self.allow_null:
378386
return None
379387
elif value is None:
380388
raise self.validation_error("null")
381389
elif value not in Uniqueness([key for key, value in self.choices]):
382390
if value == "":
383-
if self.allow_null and not strict:
391+
if self.allow_null and self.coerce_types:
384392
return None
385393
raise self.validation_error("required")
386394
raise self.validation_error("choice")
@@ -443,7 +451,7 @@ def __init__(
443451
self.max_properties = max_properties
444452
self.required = required
445453

446-
def validate(self, value: typing.Any, *, strict: bool = False) -> typing.Any:
454+
def validate(self, value: typing.Any) -> typing.Any:
447455
if value is None and self.allow_null:
448456
return None
449457
elif value is None:
@@ -492,7 +500,7 @@ def validate(self, value: typing.Any, *, strict: bool = False) -> typing.Any:
492500
validated[key] = child_schema.get_default_value()
493501
continue
494502
item = value[key]
495-
child_value, error = child_schema.validate_or_error(item, strict=strict)
503+
child_value, error = child_schema.validate_or_error(item)
496504
if not error:
497505
validated[key] = child_value
498506
else:
@@ -504,9 +512,7 @@ def validate(self, value: typing.Any, *, strict: bool = False) -> typing.Any:
504512
for pattern, child_schema in self.pattern_properties.items():
505513
if isinstance(key, str) and re.search(pattern, key):
506514
item = value[key]
507-
child_value, error = child_schema.validate_or_error(
508-
item, strict=strict
509-
)
515+
child_value, error = child_schema.validate_or_error(item)
510516
if not error:
511517
validated[key] = child_value
512518
else:
@@ -535,7 +541,7 @@ def validate(self, value: typing.Any, *, strict: bool = False) -> typing.Any:
535541
child_schema = self.additional_properties
536542
for key in remaining:
537543
item = value[key]
538-
child_value, error = child_schema.validate_or_error(item, strict=strict)
544+
child_value, error = child_schema.validate_or_error(item)
539545
if not error:
540546
validated[key] = child_value
541547
else:
@@ -599,7 +605,7 @@ def __init__(
599605
self.max_items = max_items
600606
self.unique_items = unique_items
601607

602-
def validate(self, value: typing.Any, *, strict: bool = False) -> typing.Any:
608+
def validate(self, value: typing.Any) -> typing.Any:
603609
if value is None and self.allow_null:
604610
return None
605611
elif value is None:
@@ -639,7 +645,7 @@ def validate(self, value: typing.Any, *, strict: bool = False) -> typing.Any:
639645
if validator is None:
640646
validated.append(item)
641647
else:
642-
item, error = validator.validate_or_error(item, strict=strict)
648+
item, error = validator.validate_or_error(item)
643649
if error:
644650
error_messages += error.messages(add_prefix=pos)
645651
else:
@@ -704,15 +710,15 @@ def __init__(self, any_of: typing.List[Field], **kwargs: typing.Any):
704710
if any([child.allow_null for child in any_of]):
705711
self.allow_null = True
706712

707-
def validate(self, value: typing.Any, strict: bool = False) -> typing.Any:
713+
def validate(self, value: typing.Any) -> typing.Any:
708714
if value is None and self.allow_null:
709715
return None
710716
elif value is None:
711717
raise self.validation_error("null")
712718

713719
candidate_errors = []
714720
for child in self.any_of:
715-
validated, error = child.validate_or_error(value, strict=strict)
721+
validated, error = child.validate_or_error(value)
716722
if error is None:
717723
return validated
718724
else:
@@ -738,7 +744,7 @@ class Any(Field):
738744
Always matches.
739745
"""
740746

741-
def validate(self, value: typing.Any, strict: bool = False) -> typing.Any:
747+
def validate(self, value: typing.Any) -> typing.Any:
742748
return value
743749

744750

@@ -754,7 +760,7 @@ def __init__(self, const: typing.Any, **kwargs: typing.Any):
754760
super().__init__(**kwargs)
755761
self.const = const
756762

757-
def validate(self, value: typing.Any, strict: bool = False) -> typing.Any:
763+
def validate(self, value: typing.Any) -> typing.Any:
758764
if value != self.const:
759765
if self.const is None:
760766
raise self.validation_error("only_null")

typesystem/json_schema.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ def from_json_schema_type(
212212
"exclusive_maximum": data.get("exclusiveMaximum", None),
213213
"multiple_of": data.get("multipleOf", None),
214214
"default": data.get("default", NO_DEFAULT),
215+
"coerce_types": False,
215216
}
216217
return Float(**kwargs)
217218

@@ -224,6 +225,7 @@ def from_json_schema_type(
224225
"exclusive_maximum": data.get("exclusiveMaximum", None),
225226
"multiple_of": data.get("multipleOf", None),
226227
"default": data.get("default", NO_DEFAULT),
228+
"coerce_types": False,
227229
}
228230
return Integer(**kwargs)
229231

@@ -237,11 +239,16 @@ def from_json_schema_type(
237239
"format": data.get("format"),
238240
"pattern": data.get("pattern", None),
239241
"default": data.get("default", NO_DEFAULT),
242+
"coerce_types": False,
240243
}
241244
return String(**kwargs)
242245

243246
elif type_string == "boolean":
244-
kwargs = {"allow_null": allow_null, "default": data.get("default", NO_DEFAULT)}
247+
kwargs = {
248+
"allow_null": allow_null,
249+
"default": data.get("default", NO_DEFAULT),
250+
"coerce_types": False,
251+
}
245252
return Boolean(**kwargs)
246253

247254
elif type_string == "array":

0 commit comments

Comments
 (0)