Skip to content

Commit be03c66

Browse files
authored
support trailing-strings with allow_partial (#1539)
1 parent a3f13c7 commit be03c66

21 files changed

+159
-106
lines changed

.mypy-stubtest-allowlist

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# TODO: don't want to expose this staticmethod, requires https://github.com/PyO3/pyo3/issues/2384
22
pydantic_core._pydantic_core.PydanticUndefinedType.new
3-
# As per #1240, from_json has custom logic to coverage the `cache_strings` kwarg
3+
# See #1540 for discussion
44
pydantic_core._pydantic_core.from_json
5+
pydantic_core._pydantic_core.SchemaValidator.validate_python
6+
pydantic_core._pydantic_core.SchemaValidator.validate_json
7+
pydantic_core._pydantic_core.SchemaValidator.validate_strings
58
# the `warnings` kwarg for SchemaSerializer functions has custom logic
69
pydantic_core._pydantic_core.SchemaSerializer.to_python
710
pydantic_core._pydantic_core.SchemaSerializer.to_json

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ base64 = "0.22.1"
4646
num-bigint = "0.4.6"
4747
python3-dll-a = "0.2.10"
4848
uuid = "1.11.0"
49-
jiter = { version = "0.7", features = ["python"] }
49+
jiter = { version = "0.7.1", features = ["python"] }
5050
hex = "0.4.3"
5151

5252
[lib]

benches/main.rs

Lines changed: 51 additions & 51 deletions
Large diffs are not rendered by default.

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,6 @@ require_change_file = false
109109
[tool.pyright]
110110
include = ['pydantic_core', 'tests/test_typing.py']
111111
reportUnnecessaryTypeIgnoreComment = true
112+
113+
[tool.inline-snapshot.shortcuts]
114+
fix = ["create", "fix"]

python/pydantic_core/_pydantic_core.pyi

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class SchemaValidator:
9696
from_attributes: bool | None = None,
9797
context: Any | None = None,
9898
self_instance: Any | None = None,
99-
allow_partial: bool = False,
99+
allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False,
100100
) -> Any:
101101
"""
102102
Validate a Python object against the schema and return the validated object.
@@ -113,6 +113,7 @@ class SchemaValidator:
113113
validation from the `__init__` method of a model.
114114
allow_partial: Whether to allow partial validation; if `True` errors in the last element of sequences
115115
and mappings are ignored.
116+
`'trailing-strings'` means any final unfinished JSON string is included in the result.
116117
117118
Raises:
118119
ValidationError: If validation fails.
@@ -146,7 +147,7 @@ class SchemaValidator:
146147
strict: bool | None = None,
147148
context: Any | None = None,
148149
self_instance: Any | None = None,
149-
allow_partial: bool = False,
150+
allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False,
150151
) -> Any:
151152
"""
152153
Validate JSON data directly against the schema and return the validated Python object.
@@ -166,6 +167,7 @@ class SchemaValidator:
166167
self_instance: An instance of a model set attributes on from validation.
167168
allow_partial: Whether to allow partial validation; if `True` incomplete JSON will be parsed successfully
168169
and errors in the last element of sequences and mappings are ignored.
170+
`'trailing-strings'` means any final unfinished JSON string is included in the result.
169171
170172
Raises:
171173
ValidationError: If validation fails or if the JSON data is invalid.
@@ -180,7 +182,7 @@ class SchemaValidator:
180182
*,
181183
strict: bool | None = None,
182184
context: Any | None = None,
183-
allow_partial: bool = False,
185+
allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False,
184186
) -> Any:
185187
"""
186188
Validate a string against the schema and return the validated Python object.
@@ -196,6 +198,7 @@ class SchemaValidator:
196198
[`info.context`][pydantic_core.core_schema.ValidationInfo.context].
197199
allow_partial: Whether to allow partial validation; if `True` errors in the last element of sequences
198200
and mappings are ignored.
201+
`'trailing-strings'` means any final unfinished JSON string is included in the result.
199202
200203
Raises:
201204
ValidationError: If validation fails or if the JSON data is invalid.
@@ -433,6 +436,7 @@ def from_json(
433436
`all/True` means cache all strings, `keys` means cache only dict keys, `none/False` means no caching.
434437
allow_partial: Whether to allow partial deserialization, if `True` JSON data is returned if the end of the
435438
input is reached before the full object is deserialized, e.g. `["aa", "bb", "c` would return `['aa', 'bb']`.
439+
`'trailing-strings'` means any final unfinished JSON string is included in the result.
436440
437441
Raises:
438442
ValueError: If deserialization fails.

src/input/return_enums.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::cmp::Ordering;
33
use std::ops::Rem;
44
use std::str::FromStr;
55

6-
use jiter::{JsonArray, JsonValue, StringCacheMode};
6+
use jiter::{JsonArray, JsonValue, PartialMode, StringCacheMode};
77
use num_bigint::BigInt;
88

99
use pyo3::exceptions::PyTypeError;
@@ -128,9 +128,13 @@ pub(crate) fn validate_iter_to_vec<'py>(
128128
) -> ValResult<Vec<PyObject>> {
129129
let mut output: Vec<PyObject> = Vec::with_capacity(capacity);
130130
let mut errors: Vec<ValLineError> = Vec::new();
131+
let allow_partial = state.allow_partial;
131132

132133
for (index, is_last_partial, item_result) in state.enumerate_last_partial(iter) {
133-
state.allow_partial = is_last_partial;
134+
state.allow_partial = match is_last_partial {
135+
true => allow_partial,
136+
false => PartialMode::Off,
137+
};
134138
let item = item_result.map_err(|e| any_next_error!(py, e, max_length_check.input, index))?;
135139
match validator.validate(py, item.borrow_input(), state) {
136140
Ok(item) => {
@@ -202,8 +206,13 @@ pub(crate) fn validate_iter_to_set<'py>(
202206
) -> ValResult<()> {
203207
let mut errors: Vec<ValLineError> = Vec::new();
204208

209+
let allow_partial = state.allow_partial;
210+
205211
for (index, is_last_partial, item_result) in state.enumerate_last_partial(iter) {
206-
state.allow_partial = is_last_partial;
212+
state.allow_partial = match is_last_partial {
213+
true => allow_partial,
214+
false => PartialMode::Off,
215+
};
207216
let item = item_result.map_err(|e| any_next_error!(py, e, input, index))?;
208217
match validator.validate(py, item.borrow_input(), state) {
209218
Ok(item) => {

src/url.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ impl PyUrl {
4545
pub fn py_new(py: Python, url: &Bound<'_, PyAny>) -> PyResult<Self> {
4646
let schema_obj = SCHEMA_DEFINITION_URL
4747
.get_or_init(py, || build_schema_validator(py, "url"))
48-
.validate_python(py, url, None, None, None, None, false)?;
48+
.validate_python(py, url, None, None, None, None, false.into())?;
4949
schema_obj.extract(py)
5050
}
5151

@@ -225,7 +225,7 @@ impl PyMultiHostUrl {
225225
pub fn py_new(py: Python, url: &Bound<'_, PyAny>) -> PyResult<Self> {
226226
let schema_obj = SCHEMA_DEFINITION_MULTI_HOST_URL
227227
.get_or_init(py, || build_schema_validator(py, "multi-host-url"))
228-
.validate_python(py, url, None, None, None, None, false)?;
228+
.validate_python(py, url, None, None, None, None, false.into())?;
229229
schema_obj.extract(py)
230230
}
231231

src/validators/arguments.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use crate::build_tools::{schema_or_config_same, ExtraBehavior};
1111
use crate::errors::{ErrorTypeDefaults, ValError, ValLineError, ValResult};
1212
use crate::input::{Arguments, BorrowInput, Input, KeywordArgs, PositionalArgs, ValidationMatch};
1313
use crate::lookup_key::LookupKey;
14-
1514
use crate::tools::SchemaDict;
1615

1716
use super::validation_state::ValidationState;
@@ -189,7 +188,7 @@ impl Validator for ArgumentsValidator {
189188
state: &mut ValidationState<'_, 'py>,
190189
) -> ValResult<PyObject> {
191190
// this validator does not yet support partial validation, disable it to avoid incorrect results
192-
state.allow_partial = false;
191+
state.allow_partial = false.into();
193192

194193
let args = input.validate_args()?;
195194

src/validators/dataclass.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ impl Validator for DataclassArgsValidator {
146146
state: &mut ValidationState<'_, 'py>,
147147
) -> ValResult<PyObject> {
148148
// this validator does not yet support partial validation, disable it to avoid incorrect results
149-
state.allow_partial = false;
149+
state.allow_partial = false.into();
150150

151151
let args = input.validate_dataclass_args(&self.dataclass_name)?;
152152

0 commit comments

Comments
 (0)