Skip to content

Commit

Permalink
Merge branch 'main' into add_validated_data_arg_to_default_factory
Browse files Browse the repository at this point in the history
  • Loading branch information
nix010 authored Oct 12, 2024
2 parents 8dd5ec2 + 10374e2 commit ed025e1
Show file tree
Hide file tree
Showing 10 changed files with 44 additions and 21 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ jobs:
# which xfail on a pending release of pydantic-core
- run: pdm run pytest --override-ini=xfail_strict=False
working-directory: pydantic
env:
PYDANTIC_PRIVATE_ALLOW_UNHANDLED_SCHEMA_TYPES: 1

lint:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pydantic-core"
version = "2.24.0"
version = "2.24.1"
edition = "2021"
license = "MIT"
homepage = "https:/pydantic/pydantic-core"
Expand Down
1 change: 1 addition & 0 deletions python/pydantic_core/core_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -3972,6 +3972,7 @@ def definition_reference_schema(
'no_such_attribute',
'json_invalid',
'json_type',
'needs_python_object',
'recursion_loop',
'missing',
'frozen_field',
Expand Down
3 changes: 3 additions & 0 deletions src/errors/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ error_types! {
error: {ctx_type: String, ctx_fn: field_from_context},
},
JsonType {},
NeedsPythonObject { method_name: {ctx_type: String, ctx_fn: field_from_context} },
// ---------------------
// recursion error
RecursionLoop {},
Expand Down Expand Up @@ -477,6 +478,7 @@ impl ErrorType {
Self::NoSuchAttribute {..} => "Object has no attribute '{attribute}'",
Self::JsonInvalid {..} => "Invalid JSON: {error}",
Self::JsonType {..} => "JSON input should be string, bytes or bytearray",
Self::NeedsPythonObject {..} => "Cannot check `{method_name}` when validating from json, use a JsonOrPython validator instead",
Self::RecursionLoop {..} => "Recursion error - cyclic reference detected",
Self::Missing {..} => "Field required",
Self::FrozenField {..} => "Field is frozen",
Expand Down Expand Up @@ -626,6 +628,7 @@ impl ErrorType {
match self {
Self::NoSuchAttribute { attribute, .. } => render!(tmpl, attribute),
Self::JsonInvalid { error, .. } => render!(tmpl, error),
Self::NeedsPythonObject { method_name, .. } => render!(tmpl, method_name),
Self::GetAttributeError { error, .. } => render!(tmpl, error),
Self::ModelType { class_name, .. } => render!(tmpl, class_name),
Self::DataclassType { class_name, .. } => render!(tmpl, class_name),
Expand Down
13 changes: 8 additions & 5 deletions src/validators/is_instance.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use pyo3::exceptions::PyNotImplementedError;
use pyo3::intern;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType};
Expand Down Expand Up @@ -56,10 +55,14 @@ impl Validator for IsInstanceValidator {
_state: &mut ValidationState<'_, 'py>,
) -> ValResult<PyObject> {
let Some(obj) = input.as_python() else {
return Err(ValError::InternalErr(PyNotImplementedError::new_err(
"Cannot check isinstance when validating from json, \
use a JsonOrPython validator instead.",
)));
let method_name = "isinstance".to_string();
return Err(ValError::new(
ErrorType::NeedsPythonObject {
context: None,
method_name,
},
input,
));
};
match obj.is_instance(self.class.bind(py))? {
true => Ok(obj.clone().unbind()),
Expand Down
13 changes: 8 additions & 5 deletions src/validators/is_subclass.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use pyo3::exceptions::PyNotImplementedError;
use pyo3::intern;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType};
Expand Down Expand Up @@ -51,10 +50,14 @@ impl Validator for IsSubclassValidator {
_state: &mut ValidationState<'_, 'py>,
) -> ValResult<PyObject> {
let Some(obj) = input.as_python() else {
return Err(ValError::InternalErr(PyNotImplementedError::new_err(
"Cannot check issubclass when validating from json, \
use a JsonOrPython validator instead.",
)));
let method_name = "issubclass".to_string();
return Err(ValError::new(
ErrorType::NeedsPythonObject {
context: None,
method_name,
},
input,
));
};
match obj.downcast::<PyType>() {
Ok(py_type) if py_type.is_subclass(self.class.bind(py))? => Ok(obj.clone().unbind()),
Expand Down
13 changes: 9 additions & 4 deletions tests/test_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,11 @@ def f(input_value, info):
('no_such_attribute', "Object has no attribute 'wrong_name'", {'attribute': 'wrong_name'}),
('json_invalid', 'Invalid JSON: foobar', {'error': 'foobar'}),
('json_type', 'JSON input should be string, bytes or bytearray', None),
(
'needs_python_object',
'Cannot check `isinstance` when validating from json, use a JsonOrPython validator instead',
{'method_name': 'isinstance'},
),
('recursion_loop', 'Recursion error - cyclic reference detected', None),
('model_type', 'Input should be a valid dictionary or instance of Foobar', {'class_name': 'Foobar'}),
('model_attributes_type', 'Input should be a valid dictionary or object to extract fields from', None),
Expand Down Expand Up @@ -506,10 +511,10 @@ def test_all_errors():
'example_context': None,
},
{
'type': 'recursion_loop',
'message_template_python': 'Recursion error - cyclic reference detected',
'example_message_python': 'Recursion error - cyclic reference detected',
'example_context': None,
'type': 'needs_python_object',
'message_template_python': 'Cannot check `{method_name}` when validating from json, use a JsonOrPython validator instead',
'example_message_python': 'Cannot check `` when validating from json, use a JsonOrPython validator instead',
'example_context': {'method_name': ''},
},
]

Expand Down
9 changes: 4 additions & 5 deletions tests/validators/test_is_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ class Spam:

def test_validate_json() -> None:
v = SchemaValidator({'type': 'is-instance', 'cls': Foo})
with pytest.raises(NotImplementedError, match='use a JsonOrPython validator instead'):
with pytest.raises(ValidationError) as exc_info:
v.validate_json('"foo"')
assert exc_info.value.errors()[0]['type'] == 'needs_python_object'


def test_is_instance():
Expand Down Expand Up @@ -175,11 +176,9 @@ def test_is_instance_json_type_before_validator():
schema = core_schema.is_instance_schema(type)
v = SchemaValidator(schema)

with pytest.raises(
NotImplementedError,
match='Cannot check isinstance when validating from json, use a JsonOrPython validator instead.',
):
with pytest.raises(ValidationError) as exc_info:
v.validate_json('null')
assert exc_info.value.errors()[0]['type'] == 'needs_python_object'

# now wrap in a before validator
def set_type_to_int(input: None) -> type:
Expand Down
7 changes: 7 additions & 0 deletions tests/validators/test_is_subclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,10 @@ def test_custom_repr():
'ctx': {'class': 'Spam'},
}
]


def test_is_subclass_json() -> None:
v = SchemaValidator(core_schema.is_subclass_schema(Foo))
with pytest.raises(ValidationError) as exc_info:
v.validate_json("'Foo'")
assert exc_info.value.errors()[0]['type'] == 'needs_python_object'

0 comments on commit ed025e1

Please sign in to comment.