diff --git a/Cargo.lock b/Cargo.lock index 05b27a3ed..1ee8fec8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -298,8 +298,7 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jiter" version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07f69a121b68af57bc10f151f3f67444a64d1d3a0eb48b042801ea917a38dd25" +source = "git+https://github.com/pydantic/jiter?branch=dh/pyo3-0.23#ee8a90ed848b1c9cfded61c0323d47d62ea0c962" dependencies = [ "ahash", "bitvec", @@ -451,9 +450,8 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51" +version = "0.23.0" +source = "git+https://github.com/pyo3/pyo3?branch=release-0.23#694e8ccfd96534a5f9ba0d790ecd724d31d8f8db" dependencies = [ "cfg-if", "indoc", @@ -470,9 +468,8 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc38c5feeb496c8321091edf3d63e9a6829eab4b863b4a6a65f26f3e9cc6b179" +version = "0.23.0" +source = "git+https://github.com/pyo3/pyo3?branch=release-0.23#694e8ccfd96534a5f9ba0d790ecd724d31d8f8db" dependencies = [ "once_cell", "python3-dll-a", @@ -481,9 +478,8 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94845622d88ae274d2729fcefc850e63d7a3ddff5e3ce11bd88486db9f1d357d" +version = "0.23.0" +source = "git+https://github.com/pyo3/pyo3?branch=release-0.23#694e8ccfd96534a5f9ba0d790ecd724d31d8f8db" dependencies = [ "libc", "pyo3-build-config", @@ -491,9 +487,8 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e655aad15e09b94ffdb3ce3d217acf652e26bbc37697ef012f5e5e348c716e5e" +version = "0.23.0" +source = "git+https://github.com/pyo3/pyo3?branch=release-0.23#694e8ccfd96534a5f9ba0d790ecd724d31d8f8db" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -503,9 +498,8 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1e3f09eecd94618f60a455a23def79f79eba4dc561a97324bf9ac8c6df30ce" +version = "0.23.0" +source = "git+https://github.com/pyo3/pyo3?branch=release-0.23#694e8ccfd96534a5f9ba0d790ecd724d31d8f8db" dependencies = [ "heck", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 25dbe6932..bd7958a77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ rust-version = "1.75" [dependencies] # TODO it would be very nice to remove the "py-clone" feature as it can panic, # but needs a bit of work to make sure it's not used in the codebase -pyo3 = { version = "0.22.5", features = ["generate-import-lib", "num-bigint", "py-clone"] } +pyo3 = { git = "https://github.com/pyo3/pyo3", branch = "release-0.23", features = ["generate-import-lib", "num-bigint", "py-clone"] } regex = "1.11.1" strum = { version = "0.26.3", features = ["derive"] } strum_macros = "0.26.4" @@ -46,7 +46,7 @@ base64 = "0.22.1" num-bigint = "0.4.6" python3-dll-a = "0.2.10" uuid = "1.11.0" -jiter = { version = "0.7.1", features = ["python"] } +jiter = { git = "https://github.com/pydantic/jiter", branch = "dh/pyo3-0.23", features = ["python"] } hex = "0.4.3" [lib] @@ -74,12 +74,12 @@ debug = true strip = false [dev-dependencies] -pyo3 = { version = "0.22.5", features = ["auto-initialize"] } +pyo3 = { git = "https://github.com/pyo3/pyo3", branch = "release-0.23", features = ["auto-initialize"] } [build-dependencies] version_check = "0.9.5" # used where logic has to be version/distribution specific, e.g. pypy -pyo3-build-config = { version = "0.22.0" } +pyo3-build-config = { git = "https://github.com/pyo3/pyo3", branch = "release-0.23" } [lints.clippy] dbg_macro = "warn" @@ -111,3 +111,5 @@ too_many_lines = "allow" unnecessary_wraps = "allow" unused_self = "allow" used_underscore_binding = "allow" +# FIXME https://github.com/PyO3/pyo3/issues/4701 +wildcard_imports = "allow" diff --git a/benches/main.rs b/benches/main.rs index 69157e2d3..bbdcf8334 100644 --- a/benches/main.rs +++ b/benches/main.rs @@ -2,6 +2,8 @@ extern crate test; +use std::ffi::{CStr, CString}; + use test::{black_box, Bencher}; use pyo3::prelude::*; @@ -9,24 +11,28 @@ use pyo3::types::{PyDict, PyString}; use _pydantic_core::{validate_core_schema, SchemaValidator}; -fn build_schema_validator_with_globals(py: Python, code: &str, globals: Option<&Bound<'_, PyDict>>) -> SchemaValidator { - let mut schema = py.eval_bound(code, globals, None).unwrap().extract().unwrap(); +fn build_schema_validator_with_globals( + py: Python, + code: &CStr, + globals: Option<&Bound<'_, PyDict>>, +) -> SchemaValidator { + let mut schema = py.eval(code, globals, None).unwrap().extract().unwrap(); schema = validate_core_schema(&schema, None).unwrap().extract().unwrap(); SchemaValidator::py_new(py, &schema, None).unwrap() } -fn build_schema_validator(py: Python, code: &str) -> SchemaValidator { +fn build_schema_validator(py: Python, code: &CStr) -> SchemaValidator { build_schema_validator_with_globals(py, code, None) } -fn json<'a>(py: Python<'a>, code: &'a str) -> Bound<'a, PyAny> { - black_box(PyString::new_bound(py, code).into_any()) +fn json<'a>(py: Python<'a>, data: &'a str) -> Bound<'a, PyAny> { + black_box(PyString::new(py, data).into_any()) } #[bench] fn ints_json(bench: &mut Bencher) { Python::with_gil(|py| { - let validator = build_schema_validator(py, "{'type': 'int'}"); + let validator = build_schema_validator(py, c"{'type': 'int'}"); let result = validator .validate_json(py, &json(py, "123"), None, None, None, false.into()) @@ -47,9 +53,9 @@ fn ints_json(bench: &mut Bencher) { #[bench] fn ints_python(bench: &mut Bencher) { Python::with_gil(|py| { - let validator = build_schema_validator(py, "{'type': 'int'}"); + let validator = build_schema_validator(py, c"{'type': 'int'}"); - let input = 123_i64.into_py(py).into_bound(py); + let input = 123_i64.into_pyobject(py).unwrap(); let result = validator .validate_python(py, &input, None, None, None, None, false.into()) .unwrap(); @@ -70,7 +76,7 @@ fn ints_python(bench: &mut Bencher) { #[bench] fn list_int_json(bench: &mut Bencher) { Python::with_gil(|py| { - let validator = build_schema_validator(py, "{'type': 'list', 'items_schema': {'type': 'int'}}"); + let validator = build_schema_validator(py, c"{'type': 'list', 'items_schema': {'type': 'int'}}"); let code = format!( "[{}]", (0..100).map(|x| x.to_string()).collect::>().join(",") @@ -87,14 +93,15 @@ fn list_int_json(bench: &mut Bencher) { } fn list_int_input(py: Python<'_>) -> (SchemaValidator, PyObject) { - let validator = build_schema_validator(py, "{'type': 'list', 'items_schema': {'type': 'int'}}"); - let code = format!( + let validator = build_schema_validator(py, c"{'type': 'list', 'items_schema': {'type': 'int'}}"); + let code = CString::new(format!( "[{}]", (0..100).map(|x| x.to_string()).collect::>().join(",") - ); + )) + .unwrap(); - let input = py.eval_bound(&code, None, None).unwrap(); - (validator, input.to_object(py)) + let input = py.eval(&code, None, None).unwrap(); + (validator, input.unbind()) } #[bench] @@ -129,7 +136,7 @@ fn list_int_python_isinstance(bench: &mut Bencher) { #[bench] fn list_error_json(bench: &mut Bencher) { Python::with_gil(|py| { - let validator = build_schema_validator(py, "{'type': 'list', 'items_schema': {'type': 'int'}}"); + let validator = build_schema_validator(py, c"{'type': 'list', 'items_schema': {'type': 'int'}}"); let code = format!( "[{}]", (0..100) @@ -141,7 +148,7 @@ fn list_error_json(bench: &mut Bencher) { match validator.validate_json(py, &json(py, &code), None, None, None, false.into()) { Ok(_) => panic!("unexpectedly valid"), Err(e) => { - let v = e.value_bound(py); + let v = e.value(py); // println!("error: {}", v.to_string()); assert_eq!(v.getattr("title").unwrap().to_string(), "list[int]"); let error_count: i64 = v.call_method0("error_count").unwrap().extract().unwrap(); @@ -159,28 +166,29 @@ fn list_error_json(bench: &mut Bencher) { } fn list_error_python_input(py: Python<'_>) -> (SchemaValidator, PyObject) { - let validator = build_schema_validator(py, "{'type': 'list', 'items_schema': {'type': 'int'}}"); - let code = format!( + let validator = build_schema_validator(py, c"{'type': 'list', 'items_schema': {'type': 'int'}}"); + let code = CString::new(format!( "[{}]", (0..100) .map(|v| format!(r#""{}""#, as_str(v))) .collect::>() .join(", ") - ); + )) + .unwrap(); - let input = py.eval_bound(&code, None, None).unwrap().extract().unwrap(); + let input = py.eval(&code, None, None).unwrap().extract().unwrap(); match validator.validate_python(py, &input, None, None, None, None, false.into()) { Ok(_) => panic!("unexpectedly valid"), Err(e) => { - let v = e.value_bound(py); + let v = e.value(py); // println!("error: {}", v.to_string()); assert_eq!(v.getattr("title").unwrap().to_string(), "list[int]"); let error_count: i64 = v.call_method0("error_count").unwrap().extract().unwrap(); assert_eq!(error_count, 100); } }; - (validator, input.to_object(py)) + (validator, input.unbind()) } #[bench] @@ -217,7 +225,7 @@ fn list_error_python_isinstance(bench: &mut Bencher) { #[bench] fn list_any_json(bench: &mut Bencher) { Python::with_gil(|py| { - let validator = build_schema_validator(py, "{'type': 'list'}"); + let validator = build_schema_validator(py, c"{'type': 'list'}"); let code = format!( "[{}]", (0..100).map(|x| x.to_string()).collect::>().join(",") @@ -236,13 +244,14 @@ fn list_any_json(bench: &mut Bencher) { #[bench] fn list_any_python(bench: &mut Bencher) { Python::with_gil(|py| { - let validator = build_schema_validator(py, "{'type': 'list'}"); - let code = format!( + let validator = build_schema_validator(py, c"{'type': 'list'}"); + let code = CString::new(format!( "[{}]", (0..100).map(|x| x.to_string()).collect::>().join(",") - ); - let input = py.eval_bound(&code, None, None).unwrap().to_object(py); - let input = black_box(input.bind(py)); + )) + .unwrap(); + let input = py.eval(&code, None, None).unwrap(); + let input = black_box(&input); bench.iter(|| { let v = validator .validate_python(py, &input, None, None, None, None, false.into()) @@ -265,7 +274,7 @@ fn dict_json(bench: &mut Bencher) { Python::with_gil(|py| { let validator = build_schema_validator( py, - "{'type': 'dict', 'keys_schema': {'type': 'str'}, 'values_schema': {'type': 'int'}}", + c"{'type': 'dict', 'keys_schema': {'type': 'str'}, 'values_schema': {'type': 'int'}}", ); let code = format!( @@ -291,18 +300,19 @@ fn dict_python(bench: &mut Bencher) { Python::with_gil(|py| { let validator = build_schema_validator( py, - "{'type': 'dict', 'keys_schema': {'type': 'str'}, 'values_schema': {'type': 'int'}}", + c"{'type': 'dict', 'keys_schema': {'type': 'str'}, 'values_schema': {'type': 'int'}}", ); - let code = format!( + let code = CString::new(format!( "{{{}}}", (0..100_u8) .map(|i| format!(r#""{}{}": {i}"#, as_char(i / 26), as_char(i))) .collect::>() .join(", ") - ); - let input = py.eval_bound(&code, None, None).unwrap().to_object(py); - let input = black_box(input.bind(py)); + )) + .unwrap(); + let input = py.eval(&code, None, None).unwrap(); + let input = black_box(&input); bench.iter(|| { let v = validator .validate_python(py, &input, None, None, None, None, false.into()) @@ -317,27 +327,28 @@ fn dict_value_error(bench: &mut Bencher) { Python::with_gil(|py| { let validator = build_schema_validator( py, - r"{ + cr"{ 'type': 'dict', 'keys_schema': {'type': 'str'}, 'values_schema': {'type': 'int', 'lt': 0}, }", ); - let code = format!( + let code = CString::new(format!( "{{{}}}", (0..100_u8) .map(|i| format!(r#""{}": {i}"#, as_str(i))) .collect::>() .join(", ") - ); + )) + .unwrap(); - let input = py.eval_bound(&code, None, None).unwrap().to_object(py).into_bound(py); + let input = py.eval(&code, None, None).unwrap(); match validator.validate_python(py, &input, None, None, None, None, false.into()) { Ok(_) => panic!("unexpectedly valid"), Err(e) => { - let v = e.value_bound(py); + let v = e.value(py); // println!("error: {}", v.to_string()); assert_eq!(v.getattr("title").unwrap().to_string(), "dict[str,constrained-int]"); let error_count: i64 = v.call_method0("error_count").unwrap().extract().unwrap(); @@ -362,7 +373,7 @@ fn typed_dict_json(bench: &mut Bencher) { Python::with_gil(|py| { let validator = build_schema_validator( py, - r"{ + cr"{ 'type': 'typed-dict', 'extra_behavior': 'ignore', 'fields': { @@ -397,7 +408,7 @@ fn typed_dict_python(bench: &mut Bencher) { Python::with_gil(|py| { let validator = build_schema_validator( py, - r"{ + cr"{ 'type': 'typed-dict', 'extra_behavior': 'ignore', 'fields': { @@ -415,9 +426,9 @@ fn typed_dict_python(bench: &mut Bencher) { }", ); - let code = r#"{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5, "f": 6, "g": 7, "h": 8, "i": 9, "j": 0}"#.to_string(); - let input = py.eval_bound(&code, None, None).unwrap().to_object(py); - let input = black_box(input.bind(py)); + let code = cr#"{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5, "f": 6, "g": 7, "h": 8, "i": 9, "j": 0}"#; + let input = py.eval(&code, None, None).unwrap(); + let input = black_box(&input); bench.iter(|| { let v = validator .validate_python(py, &input, None, None, None, None, false.into()) @@ -432,7 +443,7 @@ fn typed_dict_deep_error(bench: &mut Bencher) { Python::with_gil(|py| { let validator = build_schema_validator( py, - r"{ + cr"{ 'type': 'typed-dict', 'fields': { 'field_a': {'type': 'typed-dict-field', 'schema': {'type': 'str'}}, @@ -456,15 +467,15 @@ fn typed_dict_deep_error(bench: &mut Bencher) { }", ); - let code = "{'field_a': '1', 'field_b': {'field_c': '2', 'field_d': {'field_e': '4', 'field_f': 'xx'}}}"; + let code = c"{'field_a': '1', 'field_b': {'field_c': '2', 'field_d': {'field_e': '4', 'field_f': 'xx'}}}"; - let input = py.eval_bound(code, None, None).unwrap().to_object(py); - let input = black_box(input.bind(py)); + let input = py.eval(code, None, None).unwrap(); + let input = black_box(&input); match validator.validate_python(py, &input, None, None, None, None, false.into()) { Ok(_) => panic!("unexpectedly valid"), Err(e) => { - let v = e.value_bound(py); + let v = e.value(py); // println!("error: {}", v.to_string()); assert_eq!(v.getattr("title").unwrap().to_string(), "typed-dict"); let error_count: i64 = v.call_method0("error_count").unwrap().extract().unwrap(); @@ -486,10 +497,10 @@ fn typed_dict_deep_error(bench: &mut Bencher) { #[bench] fn complete_model(bench: &mut Bencher) { Python::with_gil(|py| { - let sys_path = py.import_bound("sys").unwrap().getattr("path").unwrap(); + let sys_path = py.import("sys").unwrap().getattr("path").unwrap(); sys_path.call_method1("append", ("./tests/benchmarks/",)).unwrap(); - let complete_schema = py.import_bound("complete_schema").unwrap(); + let complete_schema = py.import("complete_schema").unwrap(); let mut schema = complete_schema.call_method0("schema").unwrap(); schema = validate_core_schema(&schema, None).unwrap().extract().unwrap(); let validator = SchemaValidator::py_new(py, &schema, None).unwrap(); @@ -510,10 +521,10 @@ fn complete_model(bench: &mut Bencher) { #[bench] fn nested_model_using_definitions(bench: &mut Bencher) { Python::with_gil(|py| { - let sys_path = py.import_bound("sys").unwrap().getattr("path").unwrap(); + let sys_path = py.import("sys").unwrap().getattr("path").unwrap(); sys_path.call_method1("append", ("./tests/benchmarks/",)).unwrap(); - let complete_schema = py.import_bound("nested_schema").unwrap(); + let complete_schema = py.import("nested_schema").unwrap(); let mut schema = complete_schema.call_method0("schema_using_defs").unwrap(); schema = validate_core_schema(&schema, None).unwrap().extract().unwrap(); let validator = SchemaValidator::py_new(py, &schema, None).unwrap(); @@ -538,10 +549,10 @@ fn nested_model_using_definitions(bench: &mut Bencher) { #[bench] fn nested_model_inlined(bench: &mut Bencher) { Python::with_gil(|py| { - let sys_path = py.import_bound("sys").unwrap().getattr("path").unwrap(); + let sys_path = py.import("sys").unwrap().getattr("path").unwrap(); sys_path.call_method1("append", ("./tests/benchmarks/",)).unwrap(); - let complete_schema = py.import_bound("nested_schema").unwrap(); + let complete_schema = py.import("nested_schema").unwrap(); let mut schema = complete_schema.call_method0("inlined_schema").unwrap(); schema = validate_core_schema(&schema, None).unwrap().extract().unwrap(); let validator = SchemaValidator::py_new(py, &schema, None).unwrap(); @@ -566,10 +577,9 @@ fn nested_model_inlined(bench: &mut Bencher) { #[bench] fn literal_ints_few_python(bench: &mut Bencher) { Python::with_gil(|py| { - let validator = build_schema_validator(py, "{'type': 'literal', 'expected': list(range(5))}"); + let validator = build_schema_validator(py, c"{'type': 'literal', 'expected': list(range(5))}"); - let input = 4_i64.into_py(py); - let input = input.bind(py); + let input = 4_i64.into_pyobject(py).unwrap(); let result = validator .validate_python(py, &input, None, None, None, None, false.into()) .unwrap(); @@ -590,10 +600,9 @@ fn literal_ints_few_python(bench: &mut Bencher) { #[bench] fn literal_strings_few_small_python(bench: &mut Bencher) { Python::with_gil(|py| { - let validator = build_schema_validator(py, "{'type': 'literal', 'expected': [f'{idx}' for idx in range(5)]}"); + let validator = build_schema_validator(py, c"{'type': 'literal', 'expected': [f'{idx}' for idx in range(5)]}"); - let input = py.eval_bound("'4'", None, None).unwrap(); - let input = input.to_object(py).into_bound(py); + let input = py.eval(c"'4'", None, None).unwrap(); let input_str: String = input.extract().unwrap(); let result = validator .validate_python(py, &input, None, None, None, None, false.into()) @@ -617,11 +626,10 @@ fn literal_strings_few_large_python(bench: &mut Bencher) { Python::with_gil(|py| { let validator = build_schema_validator( py, - "{'type': 'literal', 'expected': ['a' * 25 + f'{idx}' for idx in range(5)]}", + c"{'type': 'literal', 'expected': ['a' * 25 + f'{idx}' for idx in range(5)]}", ); - let input = py.eval_bound("'a' * 25 + '4'", None, None).unwrap(); - let input = input.to_object(py).into_bound(py); + let input = py.eval(c"'a' * 25 + '4'", None, None).unwrap(); let input_str: String = input.extract().unwrap(); let result = validator .validate_python(py, &input, None, None, None, None, false.into()) @@ -643,9 +651,9 @@ fn literal_strings_few_large_python(bench: &mut Bencher) { #[bench] fn literal_enums_few_python(bench: &mut Bencher) { Python::with_gil(|py| { - let globals = PyDict::new_bound(py); - py.run_bound( - r" + let globals = PyDict::new(py); + py.run( + cr" from enum import Enum class Foo(Enum): @@ -661,12 +669,11 @@ class Foo(Enum): let validator = build_schema_validator_with_globals( py, - "{'type': 'literal', 'expected': [Foo.v1, Foo.v2, Foo.v3, Foo.v4]}", + c"{'type': 'literal', 'expected': [Foo.v1, Foo.v2, Foo.v3, Foo.v4]}", Some(&globals), ); - let input = py.eval_bound("Foo.v4", Some(&globals), None).unwrap(); - let input = input.to_object(py).into_bound(py); + let input = py.eval(c"Foo.v4", Some(&globals), None).unwrap(); let result = validator .validate_python(py, &input, None, None, None, None, false.into()) .unwrap(); @@ -686,9 +693,9 @@ class Foo(Enum): #[bench] fn literal_ints_many_python(bench: &mut Bencher) { Python::with_gil(|py| { - let validator = build_schema_validator(py, "{'type': 'literal', 'expected': list(range(100))}"); + let validator = build_schema_validator(py, c"{'type': 'literal', 'expected': list(range(100))}"); - let input = 99_i64.into_py(py).into_bound(py); + let input = 99_i64.into_pyobject(py).unwrap(); let result = validator .validate_python(py, &input, None, None, None, None, false.into()) .unwrap(); @@ -709,10 +716,10 @@ fn literal_ints_many_python(bench: &mut Bencher) { #[bench] fn literal_strings_many_small_python(bench: &mut Bencher) { Python::with_gil(|py| { - let validator = build_schema_validator(py, "{'type': 'literal', 'expected': [f'{idx}' for idx in range(100)]}"); + let validator = + build_schema_validator(py, c"{'type': 'literal', 'expected': [f'{idx}' for idx in range(100)]}"); - let input = py.eval_bound("'99'", None, None).unwrap(); - let input = input.to_object(py).into_bound(py); + let input = py.eval(c"'99'", None, None).unwrap(); let input_str: String = input.extract().unwrap(); let result = validator .validate_python(py, &input, None, None, None, None, false.into()) @@ -736,11 +743,10 @@ fn literal_strings_many_large_python(bench: &mut Bencher) { Python::with_gil(|py| { let validator = build_schema_validator( py, - "{'type': 'literal', 'expected': ['a' * 25 + f'{idx}' for idx in range(100)]}", + c"{'type': 'literal', 'expected': ['a' * 25 + f'{idx}' for idx in range(100)]}", ); - let input = py.eval_bound("'a' * 25 + '99'", None, None).unwrap(); - let input = input.to_object(py).into_bound(py); + let input = py.eval(c"'a' * 25 + '99'", None, None).unwrap(); let input_str: String = input.extract().unwrap(); let result = validator .validate_python(py, &input, None, None, None, None, false.into()) @@ -762,10 +768,9 @@ fn literal_strings_many_large_python(bench: &mut Bencher) { #[bench] fn literal_ints_many_json(bench: &mut Bencher) { Python::with_gil(|py| { - let validator = build_schema_validator(py, "{'type': 'literal', 'expected': list(range(100))}"); + let validator = build_schema_validator(py, c"{'type': 'literal', 'expected': list(range(100))}"); - let input_json = py.eval_bound("'99'", None, None).unwrap(); - let input_json = input_json.to_object(py).into_bound(py); + let input_json = py.eval(c"'99'", None, None).unwrap(); let result = validator .validate_json(py, &input_json, None, None, None, false.into()) .unwrap(); @@ -788,13 +793,11 @@ fn literal_strings_many_large_json(bench: &mut Bencher) { Python::with_gil(|py| { let validator = build_schema_validator( py, - "{'type': 'literal', 'expected': ['a' * 25 + f'{idx}' for idx in range(100)]}", + c"{'type': 'literal', 'expected': ['a' * 25 + f'{idx}' for idx in range(100)]}", ); - let input = py.eval_bound("'a' * 25 + '99'", None, None).unwrap(); - let input = input.to_object(py).into_bound(py); - let input_json = py.eval_bound("'\"' + 'a' * 25 + '99' + '\"'", None, None).unwrap(); - let input_json = input_json.to_object(py).into_bound(py); + let input = py.eval(c"'a' * 25 + '99'", None, None).unwrap(); + let input_json = py.eval(c"'\"' + 'a' * 25 + '99' + '\"'", None, None).unwrap(); let input_str: String = input.extract().unwrap(); let result = validator .validate_json(py, &input_json, None, None, None, false.into()) @@ -816,9 +819,9 @@ fn literal_strings_many_large_json(bench: &mut Bencher) { #[bench] fn literal_mixed_few_python(bench: &mut Bencher) { Python::with_gil(|py| { - let globals = PyDict::new_bound(py); - py.run_bound( - r" + let globals = PyDict::new(py); + py.run( + cr" from enum import Enum class Foo(Enum): @@ -833,14 +836,13 @@ class Foo(Enum): .unwrap(); let validator = build_schema_validator_with_globals( py, - "{'type': 'literal', 'expected': [None, 'null', -1, Foo.v4]}", + c"{'type': 'literal', 'expected': [None, 'null', -1, Foo.v4]}", Some(&globals), ); // String { - let input = py.eval_bound("'null'", None, None).unwrap(); - let input = input.to_object(py).into_bound(py); + let input = py.eval(c"'null'", None, None).unwrap(); let input_str: String = input.extract().unwrap(); let result = validator .validate_python(py, &input, None, None, None, None, false.into()) @@ -860,8 +862,7 @@ class Foo(Enum): // Int { - let input = py.eval_bound("-1", None, None).unwrap(); - let input = input.to_object(py).into_bound(py); + let input = py.eval(c"-1", None, None).unwrap(); let input_int: i64 = input.extract().unwrap(); let result = validator .validate_python(py, &input, None, None, None, None, false.into()) @@ -881,8 +882,7 @@ class Foo(Enum): // None { - let input = py.eval_bound("None", None, None).unwrap(); - let input = input.to_object(py).into_bound(py); + let input = py.eval(c"None", None, None).unwrap(); let result = validator .validate_python(py, &input, None, None, None, None, false.into()) .unwrap(); @@ -900,8 +900,7 @@ class Foo(Enum): // Enum { - let input = py.eval_bound("Foo.v4", Some(&globals), None).unwrap(); - let input = input.to_object(py).into_bound(py); + let input = py.eval(c"Foo.v4", Some(&globals), None).unwrap(); let result = validator .validate_python(py, &input, None, None, None, None, false.into()) .unwrap(); diff --git a/src/argument_markers.rs b/src/argument_markers.rs index 23cd74f41..ba620b317 100644 --- a/src/argument_markers.rs +++ b/src/argument_markers.rs @@ -33,7 +33,7 @@ impl ArgsKwargs { #[pyo3(signature = (args, kwargs = None))] fn py_new(py: Python, args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>) -> Self { Self { - args: args.into_py(py), + args: args.into_pyobject(py), kwargs: match kwargs { Some(d) if !d.is_empty() => Some(d.to_owned().unbind()), _ => None, @@ -44,12 +44,12 @@ impl ArgsKwargs { fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> PyObject { match op { CompareOp::Eq => match self.eq(py, other) { - Ok(b) => b.into_py(py), - Err(e) => e.into_py(py), + Ok(b) => b.into_pyobject(py), + Err(e) => e.into_pyobject(py), }, CompareOp::Ne => match self.eq(py, other) { - Ok(b) => (!b).into_py(py), - Err(e) => e.into_py(py), + Ok(b) => (!b).into_pyobject(py), + Err(e) => e.into_pyobject(py), }, _ => py.NotImplemented(), } @@ -82,7 +82,7 @@ impl PydanticUndefinedType { #[staticmethod] pub fn new(py: Python) -> Py { UNDEFINED_CELL - .get_or_init(py, || PydanticUndefinedType {}.into_py(py).extract(py).unwrap()) + .get_or_init(py, || PydanticUndefinedType {}.into_pyobject(py).extract(py).unwrap()) .clone() } diff --git a/src/build_tools.rs b/src/build_tools.rs index 3d7e8ccc8..be67e76d9 100644 --- a/src/build_tools.rs +++ b/src/build_tools.rs @@ -6,7 +6,7 @@ use pyo3::prelude::*; use pyo3::types::{PyDict, PyList, PyString}; use pyo3::{intern, FromPyObject, PyErrArguments}; -use crate::errors::ValError; +use crate::errors::{PyLineError, ValError}; use crate::input::InputType; use crate::tools::SchemaDict; use crate::ValidationError; @@ -85,7 +85,14 @@ impl SchemaError { pub fn from_val_error(py: Python, error: ValError) -> PyErr { match error { ValError::LineErrors(raw_errors) => { - let line_errors = raw_errors.into_iter().map(|e| e.into_py(py)).collect(); + let line_errors = raw_errors + .into_iter() + .map(|e| PyLineError::from_val_line_error(e, py)) + .collect::>(); + let line_errors = match line_errors { + Ok(line_errors) => line_errors, + Err(err) => return err, + }; let validation_error = ValidationError::new(line_errors, "Schema".to_object(py), InputType::Python, false); let schema_error = SchemaError(SchemaErrorEnum::ValidationError(validation_error)); diff --git a/src/common/union.rs b/src/common/union.rs index 17fe9ad90..5aeb4c926 100644 --- a/src/common/union.rs +++ b/src/common/union.rs @@ -15,7 +15,7 @@ pub enum Discriminator { impl Discriminator { pub fn new(py: Python, raw: &Bound<'_, PyAny>) -> PyResult { if raw.is_callable() { - return Ok(Self::Function(raw.to_object(py))); + return Ok(Self::Function(raw.clone().unbind())); } let lookup_key = LookupKey::from_py(py, raw, None)?; diff --git a/src/errors/line_error.rs b/src/errors/line_error.rs index 03e770cc3..86033214a 100644 --- a/src/errors/line_error.rs +++ b/src/errors/line_error.rs @@ -1,3 +1,5 @@ +use std::convert::Infallible; + use pyo3::exceptions::PyTypeError; use pyo3::prelude::*; use pyo3::DowncastError; @@ -61,6 +63,12 @@ impl From> for ValError { } } +impl From for ValError { + fn from(infallible: Infallible) -> Self { + match infallible {} + } +} + impl ValError { pub fn new(error_type: ErrorType, input: impl ToErrorValue) -> ValError { Self::LineErrors(vec![ValLineError::new(error_type, input)]) @@ -156,17 +164,8 @@ impl ValLineError { } #[cfg_attr(debug_assertions, derive(Debug))] -#[derive(Clone)] +#[derive(Clone, IntoPyObject)] pub enum InputValue { Python(PyObject), Json(JsonValue<'static>), } - -impl ToPyObject for InputValue { - fn to_object(&self, py: Python) -> PyObject { - match self { - Self::Python(input) => input.clone_ref(py), - Self::Json(input) => input.to_object(py), - } - } -} diff --git a/src/errors/location.rs b/src/errors/location.rs index e9e20ab5d..6e5a73af3 100644 --- a/src/errors/location.rs +++ b/src/errors/location.rs @@ -12,7 +12,7 @@ use crate::lookup_key::{LookupPath, PathItem}; /// Used to store individual items of the error location, e.g. a string for key/field names /// or a number for array indices. -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone, Eq, PartialEq, IntoPyObjectRef)] #[cfg_attr(debug_assertions, derive(Debug))] pub enum LocItem { /// string type key, used to identify items from a dict or anything that implements `__getitem__` @@ -85,15 +85,6 @@ impl From for LocItem { } } -impl ToPyObject for LocItem { - fn to_object(&self, py: Python<'_>) -> PyObject { - match self { - Self::S(val) => val.to_object(py), - Self::I(val) => val.to_object(py), - } - } -} - impl Serialize for LocItem { fn serialize(&self, serializer: S) -> Result where @@ -128,15 +119,20 @@ impl Default for Location { } } -static EMPTY_TUPLE: GILOnceCell = GILOnceCell::new(); +static EMPTY_TUPLE: GILOnceCell> = GILOnceCell::new(); + +impl<'py> IntoPyObject<'py> for &'_ Location { + type Target = PyTuple; + type Output = Bound<'py, PyTuple>; + type Error = PyErr; -impl ToPyObject for Location { - fn to_object(&self, py: Python<'_>) -> PyObject { + fn into_pyobject(self, py: Python<'py>) -> PyResult> { match self { - Self::List(loc) => PyTuple::new_bound(py, loc.iter().rev()).to_object(py), - Self::Empty => EMPTY_TUPLE - .get_or_init(py, || PyTuple::empty_bound(py).to_object(py)) - .clone_ref(py), + Location::List(loc) => PyTuple::new(py, loc.iter().rev()), + Location::Empty => Ok(EMPTY_TUPLE + .get_or_init(py, || PyTuple::empty(py).unbind()) + .bind(py) + .clone()), } } } diff --git a/src/errors/mod.rs b/src/errors/mod.rs index ffdda90e3..c80f402bb 100644 --- a/src/errors/mod.rs +++ b/src/errors/mod.rs @@ -9,11 +9,11 @@ mod value_exception; pub use self::line_error::{InputValue, ToErrorValue, ValError, ValLineError, ValResult}; pub use self::location::LocItem; pub use self::types::{list_all_errors, ErrorType, ErrorTypeDefaults, Number}; -pub use self::validation_exception::ValidationError; +pub use self::validation_exception::{PyLineError, ValidationError}; pub use self::value_exception::{PydanticCustomError, PydanticKnownError, PydanticOmit, PydanticUseDefault}; pub fn py_err_string(py: Python, err: PyErr) -> String { - let value = err.value_bound(py); + let value = err.value(py); match value.get_type().qualname() { Ok(type_name) => match value.str() { Ok(py_str) => { diff --git a/src/errors/types.rs b/src/errors/types.rs index 07186003d..f3abb6704 100644 --- a/src/errors/types.rs +++ b/src/errors/types.rs @@ -22,7 +22,7 @@ pub fn list_all_errors(py: Python) -> PyResult> { let mut errors: Vec> = Vec::with_capacity(100); for error_type in ErrorType::iter() { if !matches!(error_type, ErrorType::CustomError { .. }) { - let d = PyDict::new_bound(py); + let d = PyDict::new(py); d.set_item("type", error_type.to_string())?; let message_template_python = error_type.message_template_python(); d.set_item("message_template_python", message_template_python)?; @@ -39,7 +39,7 @@ pub fn list_all_errors(py: Python) -> PyResult> { errors.push(d); } } - Ok(PyList::new_bound(py, errors)) + PyList::new(py, errors) } fn field_from_context<'py, T: FromPyObject<'py>>( @@ -124,7 +124,7 @@ macro_rules! error_types { $( Self::$item { context, $($key,)* } => { $( - dict.set_item::<&str, Py>(stringify!($key), $key.to_object(py))?; + dict.set_item(stringify!($key), $key)?; )* if let Some(ctx) = context { dict.update(ctx.bind(py).downcast::()?)?; @@ -745,7 +745,7 @@ impl ErrorType { } pub fn py_dict(&self, py: Python) -> PyResult>> { - let dict = PyDict::new_bound(py); + let dict = PyDict::new(py); let custom_ctx_used = self.py_dict_update_ctx(py, &dict)?; if let Self::CustomError { .. } = self { @@ -766,7 +766,7 @@ impl ErrorType { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, FromPyObject, IntoPyObject, IntoPyObjectRef)] pub enum Number { Int(i64), BigInt(BigInt), @@ -800,20 +800,6 @@ impl From for Number { } } -impl FromPyObject<'_> for Number { - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { - if let Some(int) = extract_i64(obj) { - Ok(Number::Int(int)) - } else if let Ok(float) = obj.extract::() { - Ok(Number::Float(float)) - } else if let Ok(string) = obj.extract::() { - Ok(Number::String(string)) - } else { - py_err!(PyTypeError; "Expected int or float or String, got {}", obj.get_type()) - } - } -} - impl fmt::Display for Number { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -824,13 +810,3 @@ impl fmt::Display for Number { } } } -impl ToPyObject for Number { - fn to_object(&self, py: Python<'_>) -> PyObject { - match self { - Self::Int(i) => i.into_py(py), - Self::BigInt(i) => i.clone().into_py(py), - Self::Float(f) => f.into_py(py), - Self::String(s) => s.into_py(py), - } - } -} diff --git a/src/errors/validation_exception.rs b/src/errors/validation_exception.rs index 6fc0e96bd..f4290b1b4 100644 --- a/src/errors/validation_exception.rs +++ b/src/errors/validation_exception.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::fmt; use std::fmt::{Display, Write}; use std::str::from_utf8; @@ -7,7 +8,7 @@ use pyo3::ffi; use pyo3::intern; use pyo3::prelude::*; use pyo3::sync::GILOnceCell; -use pyo3::types::{PyDict, PyList, PyString, PyType}; +use pyo3::types::{PyDict, PyList, PyString, PyTuple, PyType}; use serde::ser::{Error, SerializeMap, SerializeSeq}; use serde::{Serialize, Serializer}; @@ -57,23 +58,30 @@ impl ValidationError { ) -> PyErr { match error { ValError::LineErrors(raw_errors) => { - let line_errors: Vec = match outer_location { + let line_errors: PyResult> = match outer_location { Some(outer_location) => raw_errors .into_iter() - .map(|e| e.with_outer_location(outer_location.clone()).into_py(py)) + .map(|e| PyLineError::from_val_line_error(e.with_outer_location(outer_location.clone()), py)) .collect(), - None => raw_errors.into_iter().map(|e| e.into_py(py)).collect(), + None => raw_errors + .into_iter() + .map(|e| PyLineError::from_val_line_error(e, py)) + .collect(), + }; + let line_errors = match line_errors { + Ok(line_errors) => line_errors, + Err(err) => return err, }; let validation_error = Self::new(line_errors, title, input_type, hide_input); - match Py::new(py, validation_error) { + match Bound::new(py, validation_error) { Ok(err) => { if validation_error_cause { // Will return an import error if the backport was needed and not installed: - if let Some(cause_problem) = ValidationError::maybe_add_cause(err.borrow(py), py) { + if let Some(cause_problem) = ValidationError::maybe_add_cause(err.borrow(), py) { return cause_problem; } } - PyErr::from_value_bound(err.into_bound(py).into_any()) + PyErr::from_value(err.into_any()) } Err(err) => err, } @@ -117,15 +125,14 @@ impl ValidationError { context: _, } = &line_error.error_type { - let note: PyObject = if let Location::Empty = &line_error.location { - "Pydantic: cause of loc: root".into_py(py) + let note = if let Location::Empty = &line_error.location { + Cow::Borrowed("Pydantic: cause of loc: root") } else { - format!( + Cow::Owned(format!( "Pydantic: cause of loc: {}", // Location formats with a newline at the end, hence the trim() line_error.location.to_string().trim() - ) - .into_py(py) + )) }; // Notes only support 3.11 upwards: @@ -145,7 +152,7 @@ impl ValidationError { use pyo3::exceptions::PyUserWarning; let wrapped = PyUserWarning::new_err((note,)); - wrapped.set_cause(py, Some(PyErr::from_value_bound(err.clone_ref(py).into_bound(py)))); + wrapped.set_cause(py, Some(PyErr::from_value(err.clone_ref(py).into_bound(py)))); user_py_errs.push(wrapped); } } @@ -159,7 +166,7 @@ impl ValidationError { #[cfg(Py_3_11)] let cause = { use pyo3::exceptions::PyBaseExceptionGroup; - Some(PyBaseExceptionGroup::new_err((title, user_py_errs)).into_py(py)) + Some(PyBaseExceptionGroup::new_err((title, user_py_errs)).into_value(py)) }; // Pre 3.11 ExceptionGroup support, use the python backport instead: @@ -167,10 +174,10 @@ impl ValidationError { #[cfg(not(Py_3_11))] let cause = { use pyo3::exceptions::PyImportError; - match py.import_bound("exceptiongroup") { + match py.import("exceptiongroup") { Ok(py_mod) => match py_mod.getattr("ExceptionGroup") { Ok(group_cls) => match group_cls.call1((title, user_py_errs)) { - Ok(group_instance) => Some(group_instance.into_py(py)), + Ok(group_instance) => Some(group_instance), Err(_) => None, }, Err(_) => None, @@ -202,10 +209,10 @@ fn include_url_env(py: Python) -> bool { match std::env::var_os("PYDANTIC_ERRORS_OMIT_URL") { Some(val) => { // We don't care whether warning succeeded or not, hence the assignment - let _ = PyErr::warn_bound( + let _ = PyErr::warn( py, - &py.get_type_bound::(), - "PYDANTIC_ERRORS_OMIT_URL is deprecated, use PYDANTIC_ERRORS_INCLUDE_URL instead", + &py.get_type::(), + c"PYDANTIC_ERRORS_OMIT_URL is deprecated, use PYDANTIC_ERRORS_INCLUDE_URL instead", 1, ); // If OMIT_URL exists but is empty, we include the URL: @@ -298,7 +305,7 @@ impl ValidationError { ) -> PyResult> { let url_prefix = get_url_prefix(py, include_url); let mut iteration_error = None; - let list = PyList::new_bound( + let list = PyList::new( py, // PyList::new takes ExactSizeIterator, so if an error occurs during iteration we // fill the list with None before returning the error; the list will then be thrown @@ -313,11 +320,11 @@ impl ValidationError { py.None() }) }), - ); + )?; if let Some(err) = iteration_error { Err(err) } else { - Ok(list.into()) + Ok(list.unbind()) } } @@ -368,7 +375,7 @@ impl ValidationError { } }; let s = from_utf8(&bytes).map_err(json_py_err)?; - Ok(PyString::new_bound(py, s)) + Ok(PyString::new(py, s)) } fn __repr__(&self, py: Python) -> String { @@ -379,17 +386,17 @@ impl ValidationError { self.__repr__(py) } - fn __reduce__<'py>(slf: &Bound<'py, Self>) -> PyResult<(Bound<'py, PyAny>, PyObject)> { + fn __reduce__<'py>(slf: &Bound<'py, Self>) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyTuple>)> { let py = slf.py(); let callable = slf.getattr("from_exception_data")?; let borrow = slf.try_borrow()?; let args = ( - borrow.title.bind(py), + &borrow.title, borrow.errors(py, include_url_env(py), true, true)?, - borrow.input_type.into_py(py), + borrow.input_type, borrow.hide_input, ) - .into_py(slf.py()); + .into_pyobject(py)?; Ok((callable, args)) } } @@ -418,16 +425,6 @@ pub struct PyLineError { input_value: PyObject, } -impl IntoPy for ValLineError { - fn into_py(self, py: Python<'_>) -> PyLineError { - PyLineError { - error_type: self.error_type, - location: self.location, - input_value: self.input_value.to_object(py), - } - } -} - impl From for ValLineError { /// Used to extract line errors from a validation error for wrap functions fn from(other: PyLineError) -> ValLineError { @@ -464,7 +461,7 @@ impl TryFrom<&Bound<'_, PyAny>> for PyLineError { let location = Location::try_from(dict.get_item("loc")?.as_ref())?; let input_value = match dict.get_item("input")? { - Some(i) => i.into_py(py), + Some(i) => i.unbind(), None => py.None(), }; @@ -477,6 +474,14 @@ impl TryFrom<&Bound<'_, PyAny>> for PyLineError { } impl PyLineError { + pub fn from_val_line_error(line_error: ValLineError, py: Python<'_>) -> PyResult { + Ok(PyLineError { + error_type: line_error.error_type, + location: line_error.location, + input_value: line_error.input_value.into_pyobject(py)?.unbind(), + }) + } + fn get_error_url(&self, url_prefix: &str) -> String { format!("{url_prefix}{}", self.error_type.type_string()) } @@ -489,9 +494,9 @@ impl PyLineError { input_type: InputType, include_input: bool, ) -> PyResult { - let dict = PyDict::new_bound(py); + let dict = PyDict::new(py); dict.set_item("type", self.error_type.type_string())?; - dict.set_item("loc", self.location.to_object(py))?; + dict.set_item("loc", &self.location)?; dict.set_item("msg", self.error_type.render_message(py, input_type)?)?; if include_input { dict.set_item("input", &self.input_value)?; @@ -511,7 +516,7 @@ impl PyLineError { } } } - Ok(dict.into_py(py)) + Ok(dict.into()) } fn pretty( diff --git a/src/input/datetime.rs b/src/input/datetime.rs index 577a91014..e276e0eab 100644 --- a/src/input/datetime.rs +++ b/src/input/datetime.rs @@ -53,12 +53,12 @@ impl<'a> EitherDate<'a> { } } - pub fn try_into_py(self, py: Python<'_>) -> PyResult { + pub fn try_into_pyobject(self, py: Python<'_>) -> PyResult { let date = match self { Self::Py(date) => Ok(date), - Self::Raw(date) => PyDate::new_bound(py, date.year.into(), date.month, date.day), + Self::Raw(date) => PyDate::new(py, date.year.into(), date.month, date.day), }?; - Ok(date.into_py(py)) + Ok(date.into_pyobject(py)) } } @@ -102,7 +102,7 @@ impl<'a> EitherTimedelta<'a> { } } - pub fn try_into_py(&self, py: Python<'a>) -> PyResult> { + pub fn try_into_pyobject(&self, py: Python<'a>) -> PyResult> { match self { Self::PyExact(timedelta) => Ok(timedelta.clone()), Self::PySubclass(timedelta) => Ok(timedelta.clone()), @@ -165,7 +165,7 @@ pub fn pytimedelta_subclass_as_duration(py_timedelta: &Bound<'_, PyDelta>) -> Py pub fn duration_as_pytimedelta<'py>(py: Python<'py>, duration: &Duration) -> PyResult> { let sign = if duration.positive { 1 } else { -1 }; - PyDelta::new_bound( + PyDelta::new( py, sign * duration.day as i32, sign * duration.second as i32, @@ -208,10 +208,10 @@ impl<'a> EitherTime<'a> { } } - pub fn try_into_py(self, py: Python<'_>) -> PyResult { + pub fn try_into_pyobject(self, py: Python<'_>) -> PyResult { let time = match self { Self::Py(time) => Ok(time), - Self::Raw(time) => PyTime::new_bound( + Self::Raw(time) => PyTime::new( py, time.hour, time.minute, @@ -220,7 +220,7 @@ impl<'a> EitherTime<'a> { time_as_tzinfo(py, &time)?.as_ref(), ), }?; - Ok(time.into_py(py)) + Ok(time.into()) } } @@ -267,9 +267,9 @@ impl<'a> EitherDateTime<'a> { } } - pub fn try_into_py(self, py: Python<'a>) -> PyResult { + pub fn try_into_pyobject(self, py: Python<'a>) -> PyResult { let dt = match self { - Self::Raw(datetime) => PyDateTime::new_bound( + Self::Raw(datetime) => PyDateTime::new( py, datetime.date.year.into(), datetime.date.month, @@ -282,7 +282,7 @@ impl<'a> EitherDateTime<'a> { )?, Self::Py(dt) => dt.clone(), }; - Ok(dt.into_py(py)) + Ok(dt.into()) } } @@ -393,7 +393,7 @@ pub fn float_as_datetime<'py>(input: &(impl Input<'py> + ?Sized), timestamp: f64 pub fn date_as_datetime<'py>(date: &Bound<'py, PyDate>) -> PyResult> { let py = date.py(); - let dt = PyDateTime::new_bound( + let dt = PyDateTime::new( py, date.getattr(intern!(py, "year"))?.extract()?, date.getattr(intern!(py, "month"))?.extract()?, @@ -502,7 +502,7 @@ pub fn float_as_duration(input: impl ToErrorValue, total_seconds: f64) -> ValRes .map_err(|err| map_timedelta_err(input, err)) } -#[pyclass(module = "pydantic_core._pydantic_core", extends = PyTzInfo)] +#[pyclass(module = "pydantic_core._pydantic_core", extends = PyTzInfo, frozen)] #[derive(Clone)] #[cfg_attr(debug_assertions, derive(Debug))] pub struct TzInfo { @@ -518,7 +518,7 @@ impl TzInfo { #[allow(unused_variables)] fn utcoffset<'py>(&self, py: Python<'py>, dt: &Bound<'_, PyAny>) -> PyResult> { - PyDelta::new_bound(py, 0, self.seconds, 0, true) + PyDelta::new(py, 0, self.seconds, 0, true) } #[allow(unused_variables)] @@ -527,7 +527,7 @@ impl TzInfo { } #[allow(unused_variables)] - fn dst(&self, dt: &Bound<'_, PyAny>) -> Option<&PyDelta> { + fn dst(&self, dt: &Bound<'_, PyAny>) -> Option<&Bound<'_, PyDelta>> { None } @@ -575,7 +575,11 @@ impl TzInfo { } let offset_seconds: f64 = offset_delta.call_method0(intern!(py, "total_seconds"))?.extract()?; let offset = offset_seconds.round() as i32; - Ok(op.matches(self.seconds.cmp(&offset)).into_py(py)) + Ok(op + .matches(self.seconds.cmp(&offset)) + .into_pyobject(py)? + .to_owned() + .into()) } else { Ok(py.NotImplemented()) } @@ -585,10 +589,10 @@ impl TzInfo { Py::new(py, self.clone()) } - pub fn __reduce__(&self, py: Python) -> PyResult { - let args = (self.seconds,); - let cls = Py::new(py, self.clone())?.getattr(py, "__class__")?; - Ok((cls, args).into_py(py)) + pub fn __reduce__<'py>(slf: &Bound<'py, Self>) -> Bound<'py, PyTuple> { + let cls = slf.get_type(); + let args = (slf.get().seconds,); + PyTuple::new((cls, args)) } } diff --git a/src/input/input_abstract.rs b/src/input/input_abstract.rs index 05473c702..45556952b 100644 --- a/src/input/input_abstract.rs +++ b/src/input/input_abstract.rs @@ -1,8 +1,9 @@ +use std::convert::Infallible; use std::fmt; use pyo3::exceptions::PyValueError; -use pyo3::types::{PyDict, PyList}; -use pyo3::{intern, prelude::*}; +use pyo3::types::{PyDict, PyList, PyString}; +use pyo3::{intern, prelude::*, BoundObject}; use crate::errors::{ErrorTypeDefaults, InputValue, LocItem, ValError, ValResult}; use crate::lookup_key::{LookupKey, LookupPath}; @@ -20,13 +21,18 @@ pub enum InputType { String, } -impl IntoPy for InputType { - fn into_py(self, py: Python<'_>) -> PyObject { - match self { - Self::Json => intern!(py, "json").into_py(py), - Self::Python => intern!(py, "python").into_py(py), - Self::String => intern!(py, "string").into_py(py), - } +impl<'py> IntoPyObject<'py> for InputType { + type Target = PyString; + type Output = Borrowed<'py, 'py, PyString>; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result, Infallible> { + let text = match self { + Self::Json => intern!(py, "json"), + Self::Python => intern!(py, "python"), + Self::String => intern!(py, "string"), + }; + Ok(text.as_borrowed()) } } @@ -49,7 +55,23 @@ pub type ValMatch = ValResult>; /// the convention is to either implement: /// * `strict_*` & `lax_*` if they have different behavior /// * or, `validate_*` and `strict_*` to just call `validate_*` if the behavior for strict and lax is the same -pub trait Input<'py>: fmt::Debug + ToPyObject { +pub trait Input<'py>: fmt::Debug { + type PyConverter<'a>: IntoPyObject<'py> + where + Self: 'a; + + fn py_converter(&self) -> Self::PyConverter<'_>; + + #[inline] + fn to_object<'a>(&'a self, py: Python<'py>) -> PyResult> { + Ok(self + .py_converter() + .into_pyobject(py) + .map_err(Into::into)? + .into_bound() + .into_any()) + } + fn as_error_value(&self) -> InputValue; fn is_none(&self) -> bool { @@ -203,7 +225,7 @@ pub trait KeywordArgs<'py> { type Key<'a>: BorrowInput<'py> + Clone + Into where Self: 'a; - type Item<'a>: BorrowInput<'py> + ToPyObject + type Item<'a>: BorrowInput<'py> where Self: 'a; fn len(&self) -> usize; diff --git a/src/input/input_json.rs b/src/input/input_json.rs index d69874613..e45f85cb6 100644 --- a/src/input/input_json.rs +++ b/src/input/input_json.rs @@ -44,6 +44,13 @@ impl From> for LocItem { } impl<'py, 'data> Input<'py> for JsonValue<'data> { + type PyConverter<'a> = &'a Self where Self: 'a; + + #[inline] + fn py_converter(&self) -> &Self { + self + } + fn as_error_value(&self) -> InputValue { // cloning JsonValue is cheap due to use of Arc InputValue::Json(self.to_static()) @@ -56,9 +63,9 @@ impl<'py, 'data> Input<'py> for JsonValue<'data> { fn as_kwargs(&self, py: Python<'py>) -> Option> { match self { JsonValue::Object(object) => { - let dict = PyDict::new_bound(py); + let dict = PyDict::new(py); for (k, v) in LazyIndexMap::iter(object) { - dict.set_item(k, v.to_object(py)).unwrap(); + dict.set_item(k, v).unwrap(); } Some(dict) } @@ -170,10 +177,10 @@ impl<'py, 'data> Input<'py> for JsonValue<'data> { fn validate_decimal(&self, _strict: bool, py: Python<'py>) -> ValMatch> { match self { JsonValue::Float(f) => { - create_decimal(&PyString::new_bound(py, &f.to_string()), self).map(ValidationMatch::strict) + create_decimal(&PyString::new(py, &f.to_string()), self).map(ValidationMatch::strict) } JsonValue::Str(..) | JsonValue::Int(..) | JsonValue::BigInt(..) => { - create_decimal(self.to_object(py).bind(py), self).map(ValidationMatch::strict) + create_decimal(&self.into_pyobject(py)?, self).map(ValidationMatch::strict) } _ => Err(ValError::new(ErrorTypeDefaults::DecimalType, self)), } @@ -311,7 +318,7 @@ impl<'py, 'data> Input<'py> for JsonValue<'data> { fn validate_complex(&self, strict: bool, py: Python<'py>) -> ValResult>> { match self { JsonValue::Str(s) => Ok(ValidationMatch::strict(EitherComplex::Py(string_to_complex( - &PyString::new_bound(py, s), + &PyString::new(py, s), self, )?))), JsonValue::Float(f) => { @@ -335,6 +342,13 @@ impl<'py, 'data> Input<'py> for JsonValue<'data> { /// Required for JSON Object keys so the string can behave like an Input impl<'py> Input<'py> for str { + type PyConverter<'a> = &'a Self; + + #[inline] + fn py_converter(&self) -> &Self { + self + } + fn as_error_value(&self) -> InputValue { // Justification for the clone: this is on the error pathway and we are generally ok // with errors having a performance penalty @@ -401,7 +415,7 @@ impl<'py> Input<'py> for str { } fn validate_decimal(&self, _strict: bool, py: Python<'py>) -> ValMatch> { - create_decimal(self.to_object(py).bind(py), self).map(ValidationMatch::lax) + create_decimal(self.into_pyobject(py)?.as_any(), self).map(ValidationMatch::lax) } type Dict<'a> = Never; @@ -470,7 +484,7 @@ impl<'py> Input<'py> for str { fn validate_complex(&self, _strict: bool, py: Python<'py>) -> ValResult>> { Ok(ValidationMatch::strict(EitherComplex::Py(string_to_complex( - self.to_object(py).downcast_bound::(py)?, + &self.into_pyobject(py)?, self, )?))) } diff --git a/src/input/input_python.rs b/src/input/input_python.rs index 4fb88e99c..f0f52aa24 100644 --- a/src/input/input_python.rs +++ b/src/input/input_python.rs @@ -75,6 +75,13 @@ impl From> for LocItem { } impl<'py> Input<'py> for Bound<'py, PyAny> { + type PyConverter<'a> = &'a Self where Self: 'a; + + #[inline] + fn py_converter(&self) -> &Self { + self + } + fn as_error_value(&self) -> InputValue { InputValue::Python(self.clone().into()) } diff --git a/src/input/input_string.rs b/src/input/input_string.rs index 2ac218658..d62c12587 100644 --- a/src/input/input_string.rs +++ b/src/input/input_string.rs @@ -22,7 +22,7 @@ use super::{ KeywordArgs, ValidatedDict, ValidationMatch, }; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, IntoPyObject, IntoPyObjectRef)] pub enum StringMapping<'py> { String(Bound<'py, PyString>), Mapping(Bound<'py, PyDict>), @@ -72,6 +72,13 @@ impl From> for LocItem { } impl<'py> Input<'py> for StringMapping<'py> { + type PyConverter<'a> = &'a Self where Self: 'a; + + #[inline] + fn py_converter(&self) -> &Self { + self + } + fn as_error_value(&self) -> InputValue { match self { Self::String(s) => s.as_error_value(), diff --git a/src/input/return_enums.rs b/src/input/return_enums.rs index c91a6e2fd..a685ebb2c 100644 --- a/src/input/return_enums.rs +++ b/src/input/return_enums.rs @@ -264,7 +264,7 @@ pub(crate) fn no_validator_iter_to_vec<'py>( .map(|(index, result)| { let v = result.map_err(|e| any_next_error!(py, e, input, index))?; max_length_check.incr()?; - Ok(v.borrow_input().to_object(py)) + Ok(v.borrow_input().to_object(py)?.unbind()) }) .collect() } @@ -278,9 +278,8 @@ pub(crate) fn iterate_mapping_items<'a, 'py>( .items() .map_err(|e| mapping_err(e, py, input))? .iter() - .map_err(|e| mapping_err(e, py, input))? - .map(move |item| match item { - Ok(item) => item.extract().map_err(|_| { + .map(move |item| { + item.extract().map_err(|_| { ValError::new( ErrorType::MappingType { error: MAPPING_TUPLE_ERROR.into(), @@ -288,8 +287,7 @@ pub(crate) fn iterate_mapping_items<'a, 'py>( }, input, ) - }), - Err(e) => Err(mapping_err(e, py, input)), + }) }); Ok(iterator) } @@ -548,10 +546,10 @@ impl EitherBytes<'_, '_> { } impl IntoPy for EitherBytes<'_, '_> { - fn into_py(self, py: Python<'_>) -> PyObject { + fn into_pyobject(self, py: Python<'_>) -> PyObject { match self { - EitherBytes::Cow(bytes) => PyBytes::new_bound(py, &bytes).into_py(py), - EitherBytes::Py(py_bytes) => py_bytes.into_py(py), + EitherBytes::Cow(bytes) => PyBytes::new(py, &bytes).into_pyobject(py), + EitherBytes::Py(py_bytes) => py_bytes.into_pyobject(py), } } } @@ -580,13 +578,16 @@ impl<'a> EitherInt<'a> { EitherInt::I64(i) => Ok(i), EitherInt::U64(u) => match i64::try_from(u) { Ok(u) => Ok(u), - Err(_) => Err(ValError::new(ErrorTypeDefaults::IntParsingSize, u.into_py(py).bind(py))), + Err(_) => Err(ValError::new( + ErrorTypeDefaults::IntParsingSize, + u.into_pyobject(py).bind(py), + )), }, EitherInt::BigInt(u) => match i64::try_from(u) { Ok(u) => Ok(u), Err(e) => Err(ValError::new( ErrorTypeDefaults::IntParsingSize, - e.into_original().into_py(py).bind(py), + e.into_original().into_pyobject(py).bind(py), )), }, EitherInt::Py(i) => i @@ -636,12 +637,12 @@ impl<'a> EitherInt<'a> { } impl<'a> IntoPy for EitherInt<'a> { - fn into_py(self, py: Python<'_>) -> PyObject { + fn into_pyobject(self, py: Python<'_>) -> PyObject { match self { - Self::I64(int) => int.into_py(py), - Self::U64(int) => int.into_py(py), - Self::BigInt(int) => int.into_py(py), - Self::Py(int) => int.into_py(py), + Self::I64(int) => int.into_pyobject(py), + Self::U64(int) => int.into_pyobject(py), + Self::BigInt(int) => int.into_pyobject(py), + Self::Py(int) => int.into_pyobject(py), } } } @@ -663,10 +664,10 @@ impl<'a> EitherFloat<'a> { } impl<'a> IntoPy for EitherFloat<'a> { - fn into_py(self, py: Python<'_>) -> PyObject { + fn into_pyobject(self, py: Python<'_>) -> PyObject { match self { - Self::F64(float) => float.into_py(py), - Self::Py(float) => float.into_py(py), + Self::F64(float) => float.into_pyobject(py), + Self::Py(float) => float.into_pyobject(py), } } } @@ -680,10 +681,10 @@ pub enum Int { } impl IntoPy for Int { - fn into_py(self, py: Python<'_>) -> PyObject { + fn into_pyobject(self, py: Python<'_>) -> PyObject { match self { - Self::I64(i) => i.into_py(py), - Self::Big(big_i) => big_i.into_py(py), + Self::I64(i) => i.into_pyobject(py), + Self::Big(big_i) => big_i.into_pyobject(py), } } } @@ -755,10 +756,10 @@ pub enum EitherComplex<'a> { } impl<'a> IntoPy for EitherComplex<'a> { - fn into_py(self, py: Python<'_>) -> PyObject { + fn into_pyobject(self, py: Python<'_>) -> PyObject { match self { - Self::Complex(c) => PyComplex::from_doubles_bound(py, c[0], c[1]).into_py(py), - Self::Py(c) => c.into_py(py), + Self::Complex(c) => PyComplex::from_doubles_bound(py, c[0], c[1]).into_pyobject(py), + Self::Py(c) => c.into_pyobject(py), } } } diff --git a/src/lookup_key.rs b/src/lookup_key.rs index 7415d0942..3ac023c17 100644 --- a/src/lookup_key.rs +++ b/src/lookup_key.rs @@ -62,7 +62,7 @@ impl LookupKey { py_key1: alias_py.clone().into(), path1, key2: alt_alias.to_string(), - py_key2: PyString::new_bound(py, alt_alias).into(), + py_key2: PyString::new(py, alt_alias).into(), path2: LookupPath::from_str(py, alt_alias, None), }), None => Ok(Self::simple(py, &alias, Some(alias_py.clone()))), @@ -96,7 +96,7 @@ impl LookupKey { fn simple(py: Python, key: &str, opt_py_key: Option>) -> Self { let py_key = match &opt_py_key { Some(py_key) => py_key.clone(), - None => PyString::new_bound(py, key), + None => PyString::new(py, key), }; Self::Simple { key: key.to_string(), @@ -345,7 +345,7 @@ impl LookupPath { fn from_str(py: Python, key: &str, py_key: Option>) -> Self { let py_key = match py_key { Some(py_key) => py_key, - None => PyString::new_bound(py, key), + None => PyString::new(py, key), }; Self(vec![PathItem::S(key.to_string(), py_key.into())]) } @@ -448,7 +448,7 @@ impl PathItem { None } else { // otherwise, blindly try getitem on v since no better logic is realistic - py_any.get_item(self).ok() + py_any.get_item(self.to_object(py_any.py())).ok() } } diff --git a/src/serializers/computed_fields.rs b/src/serializers/computed_fields.rs index 0cfa2d4d1..79880539d 100644 --- a/src/serializers/computed_fields.rs +++ b/src/serializers/computed_fields.rs @@ -135,10 +135,10 @@ impl ComputedField { .unwrap_or_else(|| property_name.clone()); Ok(Self { property_name: property_name.extract()?, - property_name_py: property_name.into_py(py), + property_name_py: property_name.into_pyobject(py), serializer, alias: alias_py.extract()?, - alias_py: alias_py.into_py(py), + alias_py: alias_py.into_pyobject(py), }) } diff --git a/src/serializers/config.rs b/src/serializers/config.rs index 13a833176..f3b9da549 100644 --- a/src/serializers/config.rs +++ b/src/serializers/config.rs @@ -116,14 +116,14 @@ impl TimedeltaMode { match self { Self::Iso8601 => { let d = either_delta.to_duration()?; - Ok(d.to_string().into_py(py)) + Ok(d.to_string().into_pyobject(py)) } Self::Float => { // convert to int via a py timedelta not duration since we know this this case the input would have // been a py timedelta - let py_timedelta = either_delta.try_into_py(py)?; + let py_timedelta = either_delta.try_into_pyobject(py)?; let seconds = Self::total_seconds(&py_timedelta)?; - Ok(seconds.into_py(py)) + Ok(seconds.into_pyobject(py)) } } } @@ -135,7 +135,7 @@ impl TimedeltaMode { Ok(d.to_string().into()) } Self::Float => { - let py_timedelta = either_delta.try_into_py(py)?; + let py_timedelta = either_delta.try_into_pyobject(py)?; let seconds: f64 = Self::total_seconds(&py_timedelta)?.extract()?; Ok(seconds.to_string().into()) } @@ -154,7 +154,7 @@ impl TimedeltaMode { serializer.serialize_str(&d.to_string()) } Self::Float => { - let py_timedelta = either_delta.try_into_py(py).map_err(py_err_se_err)?; + let py_timedelta = either_delta.try_into_pyobject(py).map_err(py_err_se_err)?; let seconds = Self::total_seconds(&py_timedelta).map_err(py_err_se_err)?; let seconds: f64 = seconds.extract().map_err(py_err_se_err)?; serializer.serialize_f64(seconds) diff --git a/src/serializers/extra.rs b/src/serializers/extra.rs index 28aeea133..23461366d 100644 --- a/src/serializers/extra.rs +++ b/src/serializers/extra.rs @@ -1,5 +1,5 @@ -use std::cell::RefCell; use std::fmt; +use std::sync::Mutex; use pyo3::exceptions::{PyTypeError, PyValueError}; use pyo3::intern; @@ -370,18 +370,27 @@ impl From for WarningsMode { } } -#[derive(Clone)] #[cfg_attr(debug_assertions, derive(Debug))] pub(crate) struct CollectWarnings { mode: WarningsMode, - warnings: RefCell>>, + // FIXME: mutex is to satisfy PyO3 0.23, we should be able to refactor this away + warnings: Mutex>, +} + +impl Clone for CollectWarnings { + fn clone(&self) -> Self { + Self { + mode: self.mode, + warnings: Mutex::new(self.warnings.lock().expect("lock poisoned").clone()), + } + } } impl CollectWarnings { pub(crate) fn new(mode: WarningsMode) -> Self { Self { mode, - warnings: RefCell::new(None), + warnings: Mutex::new(Vec::new()), } } @@ -399,7 +408,7 @@ impl CollectWarnings { let type_name = value .get_type() .qualname() - .unwrap_or_else(|_| PyString::new_bound(value.py(), "")); + .unwrap_or_else(|_| PyString::new(value.py(), "")); let value_str = truncate_safe_repr(value, None); Err(PydanticSerializationUnexpectedValue::new_err(Some(format!( @@ -436,7 +445,7 @@ impl CollectWarnings { let type_name = value .get_type() .qualname() - .unwrap_or_else(|_| PyString::new_bound(value.py(), "")); + .unwrap_or_else(|_| PyString::new(value.py(), "")); let value_str = truncate_safe_repr(value, None); @@ -447,41 +456,46 @@ impl CollectWarnings { } fn add_warning(&self, message: String) { - let mut op_warnings = self.warnings.borrow_mut(); - if let Some(ref mut warnings) = *op_warnings { - warnings.push(message); - } else { - *op_warnings = Some(vec![message]); - } + self.warnings.lock().expect("lock poisoned").push(message); } pub fn final_check(&self, py: Python) -> PyResult<()> { if self.mode == WarningsMode::None { return Ok(()); } - match *self.warnings.borrow() { - Some(ref warnings) => { - let message = format!("Pydantic serializer warnings:\n {}", warnings.join("\n ")); - if self.mode == WarningsMode::Warn { - let user_warning_type = py.import_bound("builtins")?.getattr("UserWarning")?; - PyErr::warn_bound(py, &user_warning_type, &message, 0) - } else { - Err(PydanticSerializationError::new_err(message)) - } - } - _ => Ok(()), + let warnings = self.warnings.lock().expect("lock poisoned"); + + if warnings.is_empty() { + return Ok(()); + } + + let message = format!("Pydantic serializer warnings:\n {}", warnings.join("\n ")); + if self.mode == WarningsMode::Warn { + let user_warning_type = py.import_bound("builtins")?.getattr("UserWarning")?; + PyErr::warn_bound(py, &user_warning_type, &message, 0) + } else { + Err(PydanticSerializationError::new_err(message)) } } } -#[derive(Default, Clone)] +#[derive(Default)] #[cfg_attr(debug_assertions, derive(Debug))] pub struct SerRecursionState { - guard: RefCell, + // FIXME: mutex is to satisfy PyO3 0.23, we should be able to refactor this away + guard: Mutex, +} + +impl Clone for SerRecursionState { + fn clone(&self) -> Self { + Self { + guard: Mutex::new(self.guard.lock().expect("lock poisoned").clone()), + } + } } impl ContainsRecursionState for &'_ Extra<'_> { fn access_recursion_state(&mut self, f: impl FnOnce(&mut RecursionState) -> R) -> R { - f(&mut self.rec_guard.guard.borrow_mut()) + f(&mut self.rec_guard.guard.lock().expect("lock poisoned")) } } diff --git a/src/serializers/fields.rs b/src/serializers/fields.rs index 4498d8fa7..9e5f080b2 100644 --- a/src/serializers/fields.rs +++ b/src/serializers/fields.rs @@ -41,9 +41,7 @@ impl SerField { serializer: Option, required: bool, ) -> Self { - let alias_py = alias - .as_ref() - .map(|alias| PyString::new_bound(py, alias.as_str()).into()); + let alias_py = alias.as_ref().map(|alias| PyString::new(py, alias.as_str()).into()); Self { key_py, alias, @@ -153,7 +151,7 @@ impl GeneralFieldsSerializer { exclude: Option<&Bound<'py, PyAny>>, extra: Extra, ) -> PyResult> { - let output_dict = PyDict::new_bound(py); + let output_dict = PyDict::new(py); let mut used_req_fields: usize = 0; // NOTE! we maintain the order of the input dict assuming that's right @@ -385,7 +383,7 @@ impl TypeSerializer for GeneralFieldsSerializer { } } self.add_computed_fields_python(model, &output_dict, include, exclude, extra)?; - Ok(output_dict.into_py(py)) + Ok(output_dict.into_pyobject(py)) } fn json_key<'a>(&self, key: &'a Bound<'_, PyAny>, extra: &Extra) -> PyResult> { diff --git a/src/serializers/filter.rs b/src/serializers/filter.rs index efeba7d25..5ffad2fc5 100644 --- a/src/serializers/filter.rs +++ b/src/serializers/filter.rs @@ -40,7 +40,7 @@ fn map_negative_indices<'py>( ) -> PyResult> { let py = include_or_exclude.py(); if let Ok(exclude_dict) = include_or_exclude.downcast::() { - let out = PyDict::new_bound(py); + let out = PyDict::new(py); for (k, v) in exclude_dict.iter() { out.set_item(map_negative_index(&k, len)?, v)?; } @@ -50,7 +50,7 @@ fn map_negative_indices<'py>( for v in exclude_set.iter() { values.push(map_negative_index(&v, len)?); } - Ok(PySet::new_bound(py, &values)?.into_any()) + Ok(PySet::new(py, &values)?.into_any()) } else { // return as is and deal with the error later Ok(include_or_exclude.clone()) @@ -173,7 +173,9 @@ trait FilterLogic { next_exclude = Some(exc_value); } } else if let Ok(exclude_set) = exclude.downcast::() { - if exclude_set.contains(py_key)? || exclude_set.contains(intern!(exclude_set.py(), "__all__"))? { + if exclude_set.contains(py_key.to_object(exclude_set.py()))? + || exclude_set.contains(intern!(exclude_set.py(), "__all__"))? + { // index is in the exclude set, we return Ok(None) to omit this index return Ok(None); } @@ -205,7 +207,9 @@ trait FilterLogic { return Ok(None); } } else if let Ok(include_set) = include.downcast::() { - if include_set.contains(py_key)? || include_set.contains(intern!(include_set.py(), "__all__"))? { + if include_set.contains(py_key.to_object(include_set.py()))? + || include_set.contains(intern!(include_set.py(), "__all__"))? + { return Ok(Some((None, next_exclude))); } else if !self.explicit_include(int_key) { // if the index is not in include, include exists, AND it's not in schema include, @@ -332,7 +336,7 @@ fn merge_all_value<'py>( dict: &Bound<'py, PyDict>, py_key: impl ToPyObject + Copy, ) -> PyResult>> { - let op_item_value = dict.get_item(py_key)?; + let op_item_value = dict.get_item(py_key.to_object(dict.py()))?; let op_all_value = dict.get_item(intern!(dict.py(), "__all__"))?; match (op_item_value, op_all_value) { @@ -356,7 +360,7 @@ fn as_dict<'py>(value: &Bound<'py, PyAny>) -> PyResult> { dict.copy() } else if let Ok(set) = value.downcast::() { let py = value.py(); - let dict = PyDict::new_bound(py); + let dict = PyDict::new(py); for item in set.iter() { dict.set_item(item, py.Ellipsis())?; } diff --git a/src/serializers/infer.rs b/src/serializers/infer.rs index d358682ff..e9d4e6dea 100644 --- a/src/serializers/infer.rs +++ b/src/serializers/infer.rs @@ -52,7 +52,7 @@ pub(crate) fn infer_to_python_known( return match mode { SerMode::Json => Err(e), // if recursion is detected by we're serializing to python, we just return the value - _ => Ok(value.into_py(py)), + _ => Ok(value.into_pyobject(py)), }; } }; @@ -114,11 +114,11 @@ pub(crate) fn infer_to_python_known( let value = match extra.mode { SerMode::Json => match ob_type { // `bool` and `None` can't be subclasses, `ObType::Int`, `ObType::Float`, `ObType::Str` refer to exact types - ObType::None | ObType::Bool | ObType::Int | ObType::Str => value.into_py(py), + ObType::None | ObType::Bool | ObType::Int | ObType::Str => value.into_pyobject(py)?.to_owned().into(), // have to do this to make sure subclasses of for example str are upcast to `str` ObType::IntSubclass => { if let Some(i) = extract_int(value) { - i.into_py(py) + i.into_pyobject(py) } else { return py_err!(PyTypeError; "Expected int, got {}", safe_repr(value)); } @@ -126,17 +126,17 @@ pub(crate) fn infer_to_python_known( ObType::Float | ObType::FloatSubclass => { let v = value.extract::()?; if (v.is_nan() || v.is_infinite()) && extra.config.inf_nan_mode == InfNanMode::Null { - return Ok(py.None().into_py(py)); + return Ok(py.None().into_pyobject(py)); } - v.into_py(py) + v.into_pyobject(py) } - ObType::Decimal => value.to_string().into_py(py), - ObType::StrSubclass => value.downcast::()?.to_str()?.into_py(py), + ObType::Decimal => value.to_string().into_pyobject(py), + ObType::StrSubclass => value.downcast::()?.to_str()?.into_pyobject(py), ObType::Bytes => extra .config .bytes_mode .bytes_to_string(py, value.downcast::()?.as_bytes()) - .map(|s| s.into_py(py))?, + .map(|s| s.into_pyobject(py))?, ObType::Bytearray => { let py_byte_array = value.downcast::()?; // Safety: the GIL is held while bytes_to_string is running; it doesn't run @@ -146,41 +146,41 @@ pub(crate) fn infer_to_python_known( .config .bytes_mode .bytes_to_string(py, bytes) - .map(|s| s.into_py(py))? + .map(|s| s.into_pyobject(py))? } ObType::Tuple => { let elements = serialize_seq_filter!(PyTuple); - PyList::new_bound(py, elements).into_py(py) + PyList::new(py, elements).into_pyobject(py) } ObType::List => { let elements = serialize_seq_filter!(PyList); - PyList::new_bound(py, elements).into_py(py) + PyList::new(py, elements).into_pyobject(py) } ObType::Set => { let elements = serialize_seq!(PySet); - PyList::new_bound(py, elements).into_py(py) + PyList::new(py, elements).into_pyobject(py) } ObType::Frozenset => { let elements = serialize_seq!(PyFrozenSet); - PyList::new_bound(py, elements).into_py(py) + PyList::new(py, elements).into_pyobject(py) } ObType::Dict => { let dict = value.downcast::()?; serialize_pairs_python(py, dict.iter().map(Ok), include, exclude, extra, |k| { - Ok(PyString::new_bound(py, &infer_json_key(&k, extra)?).into_any()) + Ok(PyString::new(py, &infer_json_key(&k, extra)?).into_any()) })? } ObType::Datetime => { let iso_dt = super::type_serializers::datetime_etc::datetime_to_string(value.downcast()?)?; - iso_dt.into_py(py) + iso_dt.into_pyobject(py) } ObType::Date => { let iso_date = super::type_serializers::datetime_etc::date_to_string(value.downcast()?)?; - iso_date.into_py(py) + iso_date.into_pyobject(py) } ObType::Time => { let iso_time = super::type_serializers::datetime_etc::time_to_string(value.downcast()?)?; - iso_time.into_py(py) + iso_time.into_pyobject(py) } ObType::Timedelta => { let either_delta = EitherTimedelta::try_from(value)?; @@ -191,25 +191,25 @@ pub(crate) fn infer_to_python_known( } ObType::Url => { let py_url: PyUrl = value.extract()?; - py_url.__str__().into_py(py) + py_url.__str__().into_pyobject(py) } ObType::MultiHostUrl => { let py_url: PyMultiHostUrl = value.extract()?; - py_url.__str__().into_py(py) + py_url.__str__().into_pyobject(py) } ObType::Uuid => { let uuid = super::type_serializers::uuid::uuid_to_string(value)?; - uuid.into_py(py) + uuid.into_pyobject(py) } ObType::PydanticSerializable => serialize_with_serializer()?, ObType::Dataclass => { serialize_pairs_python(py, any_dataclass_iter(value)?.0, include, exclude, extra, |k| { - Ok(PyString::new_bound(py, &infer_json_key(&k, extra)?).into_any()) + Ok(PyString::new(py, &infer_json_key(&k, extra)?).into_any()) })? } ObType::Enum => { let v = value.getattr(intern!(py, "value"))?; - infer_to_python(&v, include, exclude, extra)?.into_py(py) + infer_to_python(&v, include, exclude, extra)?.into_pyobject(py) } ObType::Generator => { let py_seq = value.downcast::()?; @@ -228,22 +228,22 @@ pub(crate) fn infer_to_python_known( )?); } } - PyList::new_bound(py, items).into_py(py) + PyList::new(py, items).into_pyobject(py) } ObType::Complex => { let v = value.downcast::()?; let complex_str = type_serializers::complex::complex_to_str(v); - complex_str.into_py(py) + complex_str.into_pyobject(py) } - ObType::Path => value.str()?.into_py(py), - ObType::Pattern => value.getattr(intern!(py, "pattern"))?.into_py(py), + ObType::Path => value.str()?.into_pyobject(py), + ObType::Pattern => value.getattr(intern!(py, "pattern"))?.into_pyobject(py), ObType::Unknown => { if let Some(fallback) = extra.fallback { let next_value = fallback.call1((value,))?; let next_result = infer_to_python(&next_value, include, exclude, extra); return next_result; } else if extra.serialize_unknown { - serialize_unknown(value).into_py(py) + serialize_unknown(value).into_pyobject(py) } else { return Err(unknown_type_error(value)); } @@ -252,19 +252,19 @@ pub(crate) fn infer_to_python_known( _ => match ob_type { ObType::Tuple => { let elements = serialize_seq_filter!(PyTuple); - PyTuple::new_bound(py, elements).into_py(py) + PyTuple::new(py, elements).into_pyobject(py) } ObType::List => { let elements = serialize_seq_filter!(PyList); - PyList::new_bound(py, elements).into_py(py) + PyList::new(py, elements).into_pyobject(py) } ObType::Set => { let elements = serialize_seq!(PySet); - PySet::new_bound(py, &elements)?.into_py(py) + PySet::new(py, &elements)?.into_pyobject(py) } ObType::Frozenset => { let elements = serialize_seq!(PyFrozenSet); - PyFrozenSet::new_bound(py, &elements)?.into_py(py) + PyFrozenSet::new(py, &elements)?.into_pyobject(py) } ObType::Dict => { let dict = value.downcast::()?; @@ -281,12 +281,12 @@ pub(crate) fn infer_to_python_known( exclude, extra, ); - iter.into_py(py) + iter.into_pyobject(py) } ObType::Complex => { let v = value.downcast::()?; let complex_str = type_serializers::complex::complex_to_str(v); - complex_str.into_py(py) + complex_str.into_pyobject(py) } ObType::Unknown => { if let Some(fallback) = extra.fallback { @@ -294,9 +294,9 @@ pub(crate) fn infer_to_python_known( let next_result = infer_to_python(&next_value, include, exclude, extra); return next_result; } - value.into_py(py) + value.into_pyobject(py) } - _ => value.into_py(py), + _ => value.into_pyobject(py), }, }; Ok(value) @@ -714,7 +714,7 @@ fn serialize_pairs_python<'py>( extra: &Extra, key_transform: impl Fn(Bound<'py, PyAny>) -> PyResult>, ) -> PyResult { - let new_dict = PyDict::new_bound(py); + let new_dict = PyDict::new(py); let filter = AnyFilter::new(); for result in pairs_iter { @@ -726,7 +726,7 @@ fn serialize_pairs_python<'py>( new_dict.set_item(k, v)?; } } - Ok(new_dict.into_py(py)) + Ok(new_dict.into_pyobject(py)) } fn serialize_pairs_json<'py, S: Serializer>( diff --git a/src/serializers/mod.rs b/src/serializers/mod.rs index 1a0405e2c..5e5e7b382 100644 --- a/src/serializers/mod.rs +++ b/src/serializers/mod.rs @@ -209,7 +209,7 @@ impl SchemaSerializer { warnings.final_check(py)?; self.expected_json_size.store(bytes.len(), Ordering::Relaxed); - let py_bytes = PyBytes::new_bound(py, &bytes); + let py_bytes = PyBytes::new(py, &bytes); Ok(py_bytes.into()) } @@ -278,7 +278,7 @@ pub fn to_json( let serializer = type_serializers::any::AnySerializer.into(); let bytes = to_json_bytes(value, &serializer, include, exclude, &extra, indent, 1024)?; state.final_check(py)?; - let py_bytes = PyBytes::new_bound(py, &bytes); + let py_bytes = PyBytes::new(py, &bytes); Ok(py_bytes.into()) } diff --git a/src/serializers/shared.rs b/src/serializers/shared.rs index 8eb54c837..2ea1f6117 100644 --- a/src/serializers/shared.rs +++ b/src/serializers/shared.rs @@ -402,7 +402,9 @@ static DC_FIELD_MARKER: GILOnceCell = GILOnceCell::new(); /// needed to match the logic from dataclasses.fields `tuple(f for f in fields.values() if f._field_type is _FIELD)` fn get_field_marker(py: Python<'_>) -> PyResult> { let field_type_marker_obj = DC_FIELD_MARKER.get_or_try_init(py, || { - py.import_bound("dataclasses")?.getattr("_FIELD").map(|f| f.into_py(py)) + py.import_bound("dataclasses")? + .getattr("_FIELD") + .map(|f| f.into_pyobject(py)) })?; Ok(field_type_marker_obj.bind(py).clone()) } diff --git a/src/serializers/type_serializers/bytes.rs b/src/serializers/type_serializers/bytes.rs index fa17b941a..df25bd725 100644 --- a/src/serializers/type_serializers/bytes.rs +++ b/src/serializers/type_serializers/bytes.rs @@ -45,8 +45,8 @@ impl TypeSerializer for BytesSerializer { SerMode::Json => self .bytes_mode .bytes_to_string(py, py_bytes.as_bytes()) - .map(|s| s.into_py(py)), - _ => Ok(value.into_py(py)), + .map(|s| s.into_pyobject(py)), + _ => Ok(value.into_pyobject(py)), }, Err(_) => { extra.warnings.on_fallback_py(self.get_name(), value, extra)?; diff --git a/src/serializers/type_serializers/complex.rs b/src/serializers/type_serializers/complex.rs index 57c5ba069..ce636e5d4 100644 --- a/src/serializers/type_serializers/complex.rs +++ b/src/serializers/type_serializers/complex.rs @@ -34,8 +34,8 @@ impl TypeSerializer for ComplexSerializer { let py = value.py(); match value.downcast::() { Ok(py_complex) => Ok(match extra.mode { - SerMode::Json => complex_to_str(py_complex).into_py(py), - _ => value.into_py(py), + SerMode::Json => complex_to_str(py_complex).into_pyobject(py), + _ => value.into_pyobject(py), }), Err(_) => { extra.warnings.on_fallback_py(self.get_name(), value, extra)?; diff --git a/src/serializers/type_serializers/dataclass.rs b/src/serializers/type_serializers/dataclass.rs index ffe71adb9..c6dbb7f0c 100644 --- a/src/serializers/type_serializers/dataclass.rs +++ b/src/serializers/type_serializers/dataclass.rs @@ -41,7 +41,7 @@ impl BuildSerializer for DataclassArgsBuilder { let field_info = item.downcast::()?; let name: String = field_info.get_as_req(intern!(py, "name"))?; - let key_py: Py = PyString::new_bound(py, &name).into(); + let key_py: Py = PyString::new(py, &name).into(); if field_info.get_as(intern!(py, "serialization_exclude"))? == Some(true) { fields.insert(name, SerField::new(py, key_py, None, None, true)); @@ -89,7 +89,7 @@ impl BuildSerializer for DataclassSerializer { let fields = schema .get_as_req::>(intern!(py, "fields"))? .iter() - .map(|s| Ok(s.downcast::()?.into_py(py))) + .map(|s| Ok(s.downcast::()?.into_pyobject(py))) .collect::>>()?; Ok(Self { @@ -113,7 +113,7 @@ impl DataclassSerializer { fn get_inner_value<'py>(&self, value: &Bound<'py, PyAny>) -> PyResult> { let py = value.py(); - let dict = PyDict::new_bound(py); + let dict = PyDict::new(py); for field_name in &self.fields { let field_name = field_name.bind(py); @@ -155,7 +155,7 @@ impl TypeSerializer for DataclassSerializer { )?; fields_serializer.add_computed_fields_python(model, &output_dict, include, exclude, extra)?; - Ok(output_dict.into_py(py)) + Ok(output_dict.into_pyobject(py)) } else { let inner_value = self.get_inner_value(value)?; self.serializer.to_python(&inner_value, include, exclude, &dc_extra) diff --git a/src/serializers/type_serializers/datetime_etc.rs b/src/serializers/type_serializers/datetime_etc.rs index 957fbcf29..f298bb40e 100644 --- a/src/serializers/type_serializers/datetime_etc.rs +++ b/src/serializers/type_serializers/datetime_etc.rs @@ -68,9 +68,9 @@ macro_rules! build_serializer { Ok(py_value) => match extra.mode { SerMode::Json => { let s = $convert_func(py_value)?; - Ok(s.into_py(py)) + Ok(s.into_pyobject(py)) } - _ => Ok(value.into_py(py)), + _ => Ok(value.into_pyobject(py)), }, Err(_) => { extra.warnings.on_fallback_py(self.get_name(), value, extra)?; diff --git a/src/serializers/type_serializers/dict.rs b/src/serializers/type_serializers/dict.rs index ec750faed..fb84f1212 100644 --- a/src/serializers/type_serializers/dict.rs +++ b/src/serializers/type_serializers/dict.rs @@ -83,12 +83,12 @@ impl TypeSerializer for DictSerializer { Ok(py_dict) => { let value_serializer = self.value_serializer.as_ref(); - let new_dict = PyDict::new_bound(py); + let new_dict = PyDict::new(py); for (key, value) in py_dict.iter() { let op_next = self.filter.key_filter(&key, include, exclude)?; if let Some((next_include, next_exclude)) = op_next { let key = match extra.mode { - SerMode::Json => self.key_serializer.json_key(&key, extra)?.into_py(py), + SerMode::Json => self.key_serializer.json_key(&key, extra)?.into_pyobject(py), _ => self.key_serializer.to_python(&key, None, None, extra)?, }; let value = @@ -96,7 +96,7 @@ impl TypeSerializer for DictSerializer { new_dict.set_item(key, value)?; } } - Ok(new_dict.into_py(py)) + Ok(new_dict.into_pyobject(py)) } Err(_) => { extra.warnings.on_fallback_py(self.get_name(), value, extra)?; diff --git a/src/serializers/type_serializers/enum_.rs b/src/serializers/type_serializers/enum_.rs index 8a604e772..9085854d9 100644 --- a/src/serializers/type_serializers/enum_.rs +++ b/src/serializers/type_serializers/enum_.rs @@ -67,7 +67,7 @@ impl TypeSerializer for EnumSerializer { } } else { // if we're not in JSON mode, we assume the value is safe to return directly - Ok(value.into_py(py)) + Ok(value.into_pyobject(py)) } } else { extra.warnings.on_fallback_py(self.get_name(), value, extra)?; diff --git a/src/serializers/type_serializers/float.rs b/src/serializers/type_serializers/float.rs index c6f80c2ca..f66ef310b 100644 --- a/src/serializers/type_serializers/float.rs +++ b/src/serializers/type_serializers/float.rs @@ -73,7 +73,7 @@ impl TypeSerializer for FloatSerializer { ) -> PyResult { let py = value.py(); match extra.ob_type_lookup.is_type(value, ObType::Float) { - IsType::Exact => Ok(value.into_py(py)), + IsType::Exact => Ok(value.into_pyobject(py)), IsType::Subclass => match extra.check { SerCheck::Strict => Err(PydanticSerializationUnexpectedValue::new_err(None)), SerCheck::Lax | SerCheck::None => match extra.mode { diff --git a/src/serializers/type_serializers/format.rs b/src/serializers/type_serializers/format.rs index cf785c1cc..e8399f500 100644 --- a/src/serializers/type_serializers/format.rs +++ b/src/serializers/type_serializers/format.rs @@ -78,8 +78,8 @@ impl BuildSerializer for FormatSerializer { format_func: py .import_bound(intern!(py, "builtins"))? .getattr(intern!(py, "format"))? - .into_py(py), - formatting_string: PyString::new_bound(py, formatting_string).into(), + .into_pyobject(py), + formatting_string: PyString::new(py, formatting_string).into(), when_used: WhenUsed::new(schema, WhenUsed::JsonUnlessNone)?, } .into()) @@ -118,7 +118,7 @@ impl TypeSerializer for FormatSerializer { if self.when_used.should_use(value, extra) { self.call(value).map_err(PydanticSerializationError::new_err) } else { - Ok(value.into_py(value.py())) + Ok(value.into_pyobject(value.py())) } } @@ -192,9 +192,9 @@ impl TypeSerializer for ToStringSerializer { extra: &Extra, ) -> PyResult { if self.when_used.should_use(value, extra) { - value.str().map(|s| s.into_py(value.py())) + value.str().map(|s| s.into_pyobject(value.py())) } else { - Ok(value.into_py(value.py())) + Ok(value.into_pyobject(value.py())) } } diff --git a/src/serializers/type_serializers/function.rs b/src/serializers/type_serializers/function.rs index f01b0c556..d082d3cde 100644 --- a/src/serializers/type_serializers/function.rs +++ b/src/serializers/type_serializers/function.rs @@ -127,7 +127,7 @@ impl BuildSerializer for FunctionPlainSerializer { let name = format!("plain_function[{function_name}]"); Ok(Self { - func: function.into_py(py), + func: function.into_pyobject(py), function_name, name, return_serializer, @@ -169,7 +169,7 @@ impl FunctionPlainSerializer { }; Ok((true, v)) } else { - Ok((false, value.into_py(py))) + Ok((false, value.into_pyobject(py))) } } @@ -371,7 +371,7 @@ impl BuildSerializer for FunctionWrapSerializer { let name = format!("wrap_function[{function_name}, {}]", serializer.get_name()); Ok(Self { serializer: Arc::new(serializer), - func: function.into_py(py), + func: function.into_pyobject(py), function_name, name, return_serializer: Arc::new(return_serializer), @@ -413,7 +413,7 @@ impl FunctionWrapSerializer { }; Ok((true, v)) } else { - Ok((false, value.into_py(py))) + Ok((false, value.into_pyobject(py))) } } @@ -456,8 +456,8 @@ impl SerializationCallable { serializer: serializer.clone(), extra_owned: ExtraOwned::new(extra), filter: AnyFilter::new(), - include: include.map(|v| v.into_py(py)), - exclude: exclude.map(|v| v.into_py(py)), + include: include.map(|v| v.into_pyobject(py)), + exclude: exclude.map(|v| v.into_pyobject(py)), } } @@ -572,9 +572,9 @@ impl SerializationInfo { if is_field_serializer { match extra.field_name { Some(field_name) => Ok(Self { - include: include.map(|i| i.into_py(py)), - exclude: exclude.map(|e| e.into_py(py)), - context: extra.context.map(|c| c.into_py(py)), + include: include.map(|i| i.into_pyobject(py)), + exclude: exclude.map(|e| e.into_pyobject(py)), + context: extra.context.map(|c| c.into_pyobject(py)), _mode: extra.mode.clone(), by_alias: extra.by_alias, exclude_unset: extra.exclude_unset, @@ -590,9 +590,9 @@ impl SerializationInfo { } } else { Ok(Self { - include: include.map(|i| i.into_py(py)), - exclude: exclude.map(|e| e.into_py(py)), - context: extra.context.map(|c| c.into_py(py)), + include: include.map(|i| i.into_pyobject(py)), + exclude: exclude.map(|e| e.into_pyobject(py)), + context: extra.context.map(|c| c.into_pyobject(py)), _mode: extra.mode.clone(), by_alias: extra.by_alias, exclude_unset: extra.exclude_unset, @@ -638,7 +638,7 @@ impl SerializationInfo { #[getter] fn __dict__<'py>(&'py self, py: Python<'py>) -> PyResult> { - let d = PyDict::new_bound(py); + let d = PyDict::new(py); if let Some(ref include) = self.include { d.set_item("include", include)?; } @@ -689,7 +689,7 @@ impl SerializationInfo { #[getter] fn get_field_name<'py>(&self, py: Python<'py>) -> PyResult> { match self.field_name { - Some(ref field_name) => Ok(PyString::new_bound(py, field_name)), + Some(ref field_name) => Ok(PyString::new(py, field_name)), None => Err(PyAttributeError::new_err("No attribute named 'field_name'")), } } diff --git a/src/serializers/type_serializers/generator.rs b/src/serializers/type_serializers/generator.rs index c85cd0f72..5c4d2043a 100644 --- a/src/serializers/type_serializers/generator.rs +++ b/src/serializers/type_serializers/generator.rs @@ -78,7 +78,7 @@ impl TypeSerializer for GeneratorSerializer { )?); } } - Ok(items.into_py(py)) + Ok(items.into_pyobject(py)) } _ => { let iter = SerializationIterator::new( @@ -89,7 +89,7 @@ impl TypeSerializer for GeneratorSerializer { exclude, extra, ); - Ok(iter.into_py(py)) + Ok(iter.into_pyobject(py)) } } } diff --git a/src/serializers/type_serializers/list.rs b/src/serializers/type_serializers/list.rs index adec6c13c..2a72857d2 100644 --- a/src/serializers/type_serializers/list.rs +++ b/src/serializers/type_serializers/list.rs @@ -72,7 +72,7 @@ impl TypeSerializer for ListSerializer { )?); } } - Ok(items.into_py(py)) + Ok(items.into_pyobject(py)) } Err(_) => { extra.warnings.on_fallback_py(self.get_name(), value, extra)?; diff --git a/src/serializers/type_serializers/literal.rs b/src/serializers/type_serializers/literal.rs index ce353dc03..864f6bcb6 100644 --- a/src/serializers/type_serializers/literal.rs +++ b/src/serializers/type_serializers/literal.rs @@ -89,7 +89,7 @@ impl LiteralSerializer { if let Ok(py_str) = value.downcast::() { let s = py_str.to_str()?; if self.expected_str.contains(s) { - return Ok(OutputValue::OkStr(PyString::new_bound(value.py(), s))); + return Ok(OutputValue::OkStr(PyString::new(value.py(), s))); } } } diff --git a/src/serializers/type_serializers/nullable.rs b/src/serializers/type_serializers/nullable.rs index fb63384d1..c5f557390 100644 --- a/src/serializers/type_serializers/nullable.rs +++ b/src/serializers/type_serializers/nullable.rs @@ -42,7 +42,7 @@ impl TypeSerializer for NullableSerializer { ) -> PyResult { let py = value.py(); match extra.ob_type_lookup.is_type(value, ObType::None) { - IsType::Exact => Ok(py.None().into_py(py)), + IsType::Exact => Ok(py.None().into_pyobject(py)), // I don't think subclasses of None can exist _ => self.serializer.to_python(value, include, exclude, extra), } diff --git a/src/serializers/type_serializers/set_frozenset.rs b/src/serializers/type_serializers/set_frozenset.rs index 7373c286d..925e08369 100644 --- a/src/serializers/type_serializers/set_frozenset.rs +++ b/src/serializers/type_serializers/set_frozenset.rs @@ -65,8 +65,8 @@ macro_rules! build_serializer { items.push(item_serializer.to_python(&element, include, exclude, extra)?); } match extra.mode { - SerMode::Json => Ok(PyList::new_bound(py, items).into_py(py)), - _ => Ok(<$py_type>::new_bound(py, &items)?.into_py(py)), + SerMode::Json => Ok(PyList::new(py, items)?.into()), + _ => Ok(<$py_type>::new(py, &items)?.into()), } } Err(_) => { diff --git a/src/serializers/type_serializers/simple.rs b/src/serializers/type_serializers/simple.rs index 5df8ec071..62857e62c 100644 --- a/src/serializers/type_serializers/simple.rs +++ b/src/serializers/type_serializers/simple.rs @@ -44,7 +44,7 @@ impl TypeSerializer for NoneSerializer { ) -> PyResult { let py = value.py(); match extra.ob_type_lookup.is_type(value, ObType::None) { - IsType::Exact => Ok(py.None().into_py(py)), + IsType::Exact => Ok(py.None().into_pyobject(py)), // I don't think subclasses of None can exist _ => { extra.warnings.on_fallback_py(self.get_name(), value, extra)?; @@ -120,7 +120,7 @@ macro_rules! build_simple_serializer { ) -> PyResult { let py = value.py(); match extra.ob_type_lookup.is_type(value, $ob_type) { - IsType::Exact => Ok(value.into_py(py)), + IsType::Exact => Ok(value.into_pyobject(py)), IsType::Subclass => match extra.check { SerCheck::Strict => Err(PydanticSerializationUnexpectedValue::new_err(None)), SerCheck::Lax | SerCheck::None => match extra.mode { diff --git a/src/serializers/type_serializers/string.rs b/src/serializers/type_serializers/string.rs index 9e9a7162e..6e0fa234a 100644 --- a/src/serializers/type_serializers/string.rs +++ b/src/serializers/type_serializers/string.rs @@ -43,10 +43,10 @@ impl TypeSerializer for StrSerializer { ) -> PyResult { let py = value.py(); match extra.ob_type_lookup.is_type(value, ObType::Str) { - IsType::Exact => Ok(value.into_py(py)), + IsType::Exact => Ok(value.into_pyobject(py)), IsType::Subclass => match extra.mode { - SerMode::Json => Ok(value.downcast::()?.to_str()?.into_py(py)), - _ => Ok(value.into_py(py)), + SerMode::Json => Ok(value.downcast::()?.to_str()?.into_pyobject(py)), + _ => Ok(value.into_pyobject(py)), }, IsType::False => { extra.warnings.on_fallback_py(self.get_name(), value, extra)?; diff --git a/src/serializers/type_serializers/tuple.rs b/src/serializers/type_serializers/tuple.rs index 07196bf43..1d8057475 100644 --- a/src/serializers/type_serializers/tuple.rs +++ b/src/serializers/type_serializers/tuple.rs @@ -82,8 +82,8 @@ impl TypeSerializer for TupleSerializer { })??; match extra.mode { - SerMode::Json => Ok(PyList::new_bound(py, items).into_py(py)), - _ => Ok(PyTuple::new_bound(py, items).into_py(py)), + SerMode::Json => Ok(PyList::new(py, items)?.into()), + _ => Ok(PyTuple::new(py, items)?.into()), } } Err(_) => { diff --git a/src/serializers/type_serializers/url.rs b/src/serializers/type_serializers/url.rs index 2ed2af739..5420db21f 100644 --- a/src/serializers/type_serializers/url.rs +++ b/src/serializers/type_serializers/url.rs @@ -42,8 +42,8 @@ macro_rules! build_serializer { let py = value.py(); match value.extract::<$extract>() { Ok(py_url) => match extra.mode { - SerMode::Json => Ok(py_url.__str__().into_py(py)), - _ => Ok(value.into_py(py)), + SerMode::Json => Ok(py_url.__str__().into_pyobject(py)), + _ => Ok(value.into_pyobject(py)), }, Err(_) => { extra.warnings.on_fallback_py(self.get_name(), value, extra)?; diff --git a/src/serializers/type_serializers/uuid.rs b/src/serializers/type_serializers/uuid.rs index 01627c27c..b2fd43bba 100644 --- a/src/serializers/type_serializers/uuid.rs +++ b/src/serializers/type_serializers/uuid.rs @@ -46,8 +46,8 @@ impl TypeSerializer for UuidSerializer { let py = value.py(); match extra.ob_type_lookup.is_type(value, ObType::Uuid) { IsType::Exact | IsType::Subclass => match extra.mode { - SerMode::Json => Ok(uuid_to_string(value)?.into_py(py)), - _ => Ok(value.into_py(py)), + SerMode::Json => Ok(uuid_to_string(value)?.into_pyobject(py)), + _ => Ok(value.into_pyobject(py)), }, IsType::False => { extra.warnings.on_fallback_py(self.get_name(), value, extra)?; diff --git a/src/tools.rs b/src/tools.rs index 231a94df6..21631b451 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -11,17 +11,17 @@ use crate::input::Int; use jiter::{cached_py_string, pystring_fast_new, StringCacheMode}; pub trait SchemaDict<'py> { - fn get_as(&self, key: &Bound<'_, PyString>) -> PyResult> + fn get_as(&self, key: &Bound<'py, PyString>) -> PyResult> where T: FromPyObject<'py>; - fn get_as_req(&self, key: &Bound<'_, PyString>) -> PyResult + fn get_as_req(&self, key: &Bound<'py, PyString>) -> PyResult where T: FromPyObject<'py>; } impl<'py> SchemaDict<'py> for Bound<'py, PyDict> { - fn get_as(&self, key: &Bound<'_, PyString>) -> PyResult> + fn get_as(&self, key: &Bound<'py, PyString>) -> PyResult> where T: FromPyObject<'py>, { @@ -31,7 +31,7 @@ impl<'py> SchemaDict<'py> for Bound<'py, PyDict> { } } - fn get_as_req(&self, key: &Bound<'_, PyString>) -> PyResult + fn get_as_req(&self, key: &Bound<'py, PyString>) -> PyResult where T: FromPyObject<'py>, { @@ -43,7 +43,7 @@ impl<'py> SchemaDict<'py> for Bound<'py, PyDict> { } impl<'py> SchemaDict<'py> for Option<&Bound<'py, PyDict>> { - fn get_as(&self, key: &Bound<'_, PyString>) -> PyResult> + fn get_as(&self, key: &Bound<'py, PyString>) -> PyResult> where T: FromPyObject<'py>, { @@ -54,7 +54,7 @@ impl<'py> SchemaDict<'py> for Option<&Bound<'py, PyDict>> { } #[cfg_attr(has_coverage_attribute, coverage(off))] - fn get_as_req(&self, key: &Bound<'_, PyString>) -> PyResult + fn get_as_req(&self, key: &Bound<'py, PyString>) -> PyResult where T: FromPyObject<'py>, { diff --git a/src/url.rs b/src/url.rs index dda583af5..41647d120 100644 --- a/src/url.rs +++ b/src/url.rs @@ -34,7 +34,7 @@ impl PyUrl { } fn build_schema_validator(py: Python, schema_type: &str) -> SchemaValidator { - let schema = PyDict::new_bound(py); + let schema = PyDict::new(py); schema.set_item("type", schema_type).unwrap(); SchemaValidator::py_new(py, &schema, None).unwrap() } @@ -102,9 +102,9 @@ impl PyUrl { // `query_pairs` is a pure iterator, so can't implement `ExactSizeIterator`, hence we need the temporary `Vec` self.lib_url .query_pairs() - .map(|(key, value)| (key, value).into_py(py)) + .map(|(key, value)| (key, value).into_pyobject(py)) .collect::>() - .into_py(py) + .into_pyobject(py) } #[getter] @@ -148,7 +148,7 @@ impl PyUrl { #[pyo3(signature = (_memo, /))] pub fn __deepcopy__(&self, py: Python, _memo: Bound<'_, PyAny>) -> Py { - self.clone().into_py(py) + self.clone().into_pyobject(py) } fn __getnewargs__(&self) -> (&str,) { @@ -351,7 +351,7 @@ impl PyMultiHostUrl { } pub fn __deepcopy__(&self, py: Python, _memo: &Bound<'_, PyDict>) -> Py { - self.clone().into_py(py) + self.clone().into_pyobject(py) } fn __getnewargs__(&self) -> (String,) { @@ -468,7 +468,7 @@ impl fmt::Display for UrlHostParts { } fn host_to_dict<'a>(py: Python<'a>, lib_url: &Url) -> PyResult> { - let dict = PyDict::new_bound(py); + let dict = PyDict::new(py); dict.set_item( "username", match lib_url.username() { diff --git a/src/validators/any.rs b/src/validators/any.rs index d254cb31b..c19092942 100644 --- a/src/validators/any.rs +++ b/src/validators/any.rs @@ -35,7 +35,7 @@ impl Validator for AnyValidator { ) -> ValResult { // in a union, Any should be preferred to doing lax coercions state.floor_exactness(Exactness::Strict); - Ok(input.to_object(py)) + Ok(input.to_object(py)?.unbind()) } fn get_name(&self) -> &str { diff --git a/src/validators/arguments.rs b/src/validators/arguments.rs index 2ffd49102..f3f00e9bc 100644 --- a/src/validators/arguments.rs +++ b/src/validators/arguments.rs @@ -106,7 +106,7 @@ impl BuildValidator for ArgumentsValidator { } None => Some(LookupKey::from_string(py, &name)), }; - kwarg_key = Some(py_name.into_py(py)); + kwarg_key = Some(py_name.into_pyobject(py)); } let schema = arg.get_as_req(intern!(py, "schema"))?; @@ -142,7 +142,7 @@ impl BuildValidator for ArgumentsValidator { let py_var_kwargs_mode: Bound = schema .get_as(intern!(py, "var_kwargs_mode"))? - .unwrap_or_else(|| PyString::new_bound(py, "uniform")); + .unwrap_or_else(|| PyString::new(py, "uniform")); let var_kwargs_mode = VarKwargsMode::from_str(py_var_kwargs_mode.to_str()?)?; let var_kwargs_validator = match schema.get_item(intern!(py, "var_kwargs_schema"))? { @@ -193,7 +193,7 @@ impl Validator for ArgumentsValidator { let args = input.validate_args()?; let mut output_args: Vec = Vec::with_capacity(self.positional_params_count); - let output_kwargs = PyDict::new_bound(py); + let output_kwargs = PyDict::new(py); let mut errors: Vec = Vec::new(); let mut used_kwargs: AHashSet<&str> = AHashSet::with_capacity(self.parameters.len()); @@ -296,7 +296,7 @@ impl Validator for ArgumentsValidator { } } - let remaining_kwargs = PyDict::new_bound(py); + let remaining_kwargs = PyDict::new(py); // if there are kwargs check any that haven't been processed yet if let Some(kwargs) = args.kwargs() { @@ -347,7 +347,8 @@ impl Validator for ArgumentsValidator { }, VarKwargsMode::UnpackedTypedDict => { // Save to the remaining kwargs, we will validate as a single dict: - remaining_kwargs.set_item(either_str.as_py_string(py, state.cache_str()), value)?; + remaining_kwargs + .set_item(either_str.as_py_string(py, state.cache_str()), value.to_object(py))?; } } } @@ -376,7 +377,7 @@ impl Validator for ArgumentsValidator { if !errors.is_empty() { Err(ValError::LineErrors(errors)) } else { - Ok((PyTuple::new_bound(py, output_args), output_kwargs).to_object(py)) + Ok((PyTuple::new(py, output_args), output_kwargs).to_object(py)) } } diff --git a/src/validators/bool.rs b/src/validators/bool.rs index d09fbd5f0..12bb2f374 100644 --- a/src/validators/bool.rs +++ b/src/validators/bool.rs @@ -40,7 +40,7 @@ impl Validator for BoolValidator { // and back again, might be worth profiling? input .validate_bool(state.strict_or(self.strict)) - .map(|val_match| val_match.unpack(state).into_py(py)) + .map(|val_match| val_match.unpack(state).into_pyobject(py)) } fn get_name(&self) -> &str { diff --git a/src/validators/bytes.rs b/src/validators/bytes.rs index c51d77171..49f3bc835 100644 --- a/src/validators/bytes.rs +++ b/src/validators/bytes.rs @@ -51,7 +51,7 @@ impl Validator for BytesValidator { ) -> ValResult { input .validate_bytes(state.strict_or(self.strict), self.bytes_mode) - .map(|m| m.unpack(state).into_py(py)) + .map(|m| m.unpack(state).into_pyobject(py)) } fn get_name(&self) -> &str { @@ -103,7 +103,7 @@ impl Validator for BytesConstrainedValidator { )); } } - Ok(either_bytes.into_py(py)) + Ok(either_bytes.into_pyobject(py)) } fn get_name(&self) -> &str { diff --git a/src/validators/call.rs b/src/validators/call.rs index 6783a5eda..40bbd064a 100644 --- a/src/validators/call.rs +++ b/src/validators/call.rs @@ -59,7 +59,7 @@ impl BuildValidator for CallValidator { let name = format!("{}[{function_name}]", Self::EXPECTED_TYPE); Ok(Self { - function: function.to_object(py), + function: function.unbind(), arguments_validator, return_validator, name, diff --git a/src/validators/callable.rs b/src/validators/callable.rs index 1019d5c1e..8c16a3b9a 100644 --- a/src/validators/callable.rs +++ b/src/validators/callable.rs @@ -27,15 +27,17 @@ impl_py_gc_traverse!(CallableValidator {}); impl Validator for CallableValidator { fn validate<'py>( &self, - py: Python<'py>, + _py: Python<'py>, input: &(impl Input<'py> + ?Sized), state: &mut ValidationState<'_, 'py>, ) -> ValResult { state.floor_exactness(Exactness::Lax); - match input.as_python().is_some_and(PyAnyMethods::is_callable) { - true => Ok(input.to_object(py)), - false => Err(ValError::new(ErrorTypeDefaults::CallableType, input)), + if let Some(py_input) = input.as_python() { + if py_input.is_callable() { + return Ok(py_input.clone().unbind()); + } } + Err(ValError::new(ErrorTypeDefaults::CallableType, input)) } fn get_name(&self) -> &str { diff --git a/src/validators/complex.rs b/src/validators/complex.rs index d1d9f6c35..048e3948b 100644 --- a/src/validators/complex.rs +++ b/src/validators/complex.rs @@ -46,7 +46,7 @@ impl Validator for ComplexValidator { state: &mut ValidationState<'_, 'py>, ) -> ValResult { let res = input.validate_complex(self.strict, py)?.unpack(state); - Ok(res.into_py(py)) + Ok(res.into_pyobject(py)) } fn get_name(&self) -> &str { diff --git a/src/validators/dataclass.rs b/src/validators/dataclass.rs index b41de429f..22a07c54b 100644 --- a/src/validators/dataclass.rs +++ b/src/validators/dataclass.rs @@ -150,7 +150,7 @@ impl Validator for DataclassArgsValidator { let args = input.validate_dataclass_args(&self.dataclass_name)?; - let output_dict = PyDict::new_bound(py); + let output_dict = PyDict::new(py); let mut init_only_args = self.init_only_count.map(Vec::with_capacity); let mut errors: Vec = Vec::new(); @@ -326,8 +326,10 @@ impl Validator for DataclassArgsValidator { Err(err) => return Err(err), } } else { - output_dict - .set_item(either_str.as_py_string(py, state.cache_str()), value)?; + output_dict.set_item( + either_str.as_py_string(py, state.cache_str()), + value.to_object(py), + )?; } } } @@ -351,7 +353,7 @@ impl Validator for DataclassArgsValidator { if errors.is_empty() { if let Some(init_only_args) = init_only_args { - Ok((output_dict, PyTuple::new_bound(py, init_only_args)).to_object(py)) + Ok((output_dict, PyTuple::new(py, init_only_args)).to_object(py)) } else { Ok((output_dict, py.None()).to_object(py)) } @@ -376,7 +378,7 @@ impl Validator for DataclassArgsValidator { // which doesn't make much sense in this context but we need to put something there // so that function validators that sit between DataclassValidator and DataclassArgsValidator // always get called the same shape of data. - Ok(PyTuple::new_bound(py, vec![dict.to_object(py), py.None()]).into_py(py)) + Ok(PyTuple::new(py, vec![dict.to_object(py), py.None()]).into_pyobject(py)) }; if let Some(field) = self.fields.iter().find(|f| f.name == field_name) { @@ -391,7 +393,7 @@ impl Validator for DataclassArgsValidator { let data_dict = dict.copy()?; if let Err(err) = data_dict.del_item(field_name) { // KeyError is fine here as the field might not be in the dict - if !err.get_type_bound(py).is(&PyType::new_bound::(py)) { + if !err.get_type_bound(py).is(&PyType::new::(py)) { return Err(err.into()); } } @@ -418,7 +420,7 @@ impl Validator for DataclassArgsValidator { match self.extra_behavior { // For dataclasses we allow assigning unknown fields // to match stdlib dataclass behavior - ExtraBehavior::Allow => ok(field_value.to_object(py)), + ExtraBehavior::Allow => ok(field_value.clone().unbind()), _ => Err(ValError::new_with_loc( ErrorType::NoSuchAttribute { attribute: field_name.to_string(), @@ -474,7 +476,7 @@ impl BuildValidator for DataclassValidator { let validator = build_validator(&sub_schema, config, definitions)?; let post_init = if schema.get_as::(intern!(py, "post_init"))?.unwrap_or(false) { - Some(intern!(py, "__post_init__").into_py(py)) + Some(intern!(py, "__post_init__").into_pyobject(py)) } else { None }; @@ -550,7 +552,7 @@ impl Validator for DataclassValidator { self.set_dict_call(py, &dc, val_output, input)?; Ok(dc.into()) } else { - Ok(input.to_object(py)) + Ok(input.to_object(py)?.unbind()) } } else if state.strict_or(self.strict) && state.extra().input_type == InputType::Python { Err(ValError::new( @@ -600,7 +602,7 @@ impl Validator for DataclassValidator { force_setattr(py, obj, intern!(py, "__dict__"), dc_dict)?; } - Ok(obj.to_object(py)) + Ok(obj.clone().unbind()) } fn get_name(&self) -> &str { @@ -624,12 +626,12 @@ impl DataclassValidator { self.set_dict_call(py, self_instance, val_output, input)?; - Ok(self_instance.into_py(py)) + Ok(self_instance.into_pyobject(py)) } fn dataclass_to_dict<'py>(&self, dc: &Bound<'py, PyAny>) -> PyResult> { let py = dc.py(); - let dict = PyDict::new_bound(py); + let dict = PyDict::new(py); for field_name in &self.fields { dict.set_item(field_name, dc.getattr(field_name)?)?; diff --git a/src/validators/date.rs b/src/validators/date.rs index 75aefa935..ac0654cdb 100644 --- a/src/validators/date.rs +++ b/src/validators/date.rs @@ -97,7 +97,7 @@ impl Validator for DateValidator { } } } - Ok(date.try_into_py(py)?) + Ok(date.try_into_pyobject(py)?) } fn get_name(&self) -> &str { diff --git a/src/validators/datetime.rs b/src/validators/datetime.rs index a6ccad328..d15a46b41 100644 --- a/src/validators/datetime.rs +++ b/src/validators/datetime.rs @@ -130,7 +130,7 @@ impl Validator for DateTimeValidator { tz_constraint.tz_check(speedate_dt.time.tz_offset, input)?; } } - Ok(datetime.try_into_py(py)?) + Ok(datetime.try_into_pyobject(py)?) } fn get_name(&self) -> &str { @@ -248,7 +248,7 @@ pub struct NowConstraint { static TIME_LOCALTIME: GILOnceCell = GILOnceCell::new(); fn get_localtime(py: Python) -> PyResult { - Ok(py.import_bound("time")?.getattr("localtime")?.into_py(py)) + Ok(py.import_bound("time")?.getattr("localtime")?.into_pyobject(py)) } impl NowConstraint { diff --git a/src/validators/decimal.rs b/src/validators/decimal.rs index 8008d4260..5cad9e0ce 100644 --- a/src/validators/decimal.rs +++ b/src/validators/decimal.rs @@ -279,9 +279,9 @@ pub(crate) fn create_decimal<'py>(arg: &Bound<'py, PyAny>, input: impl ToErrorVa fn handle_decimal_new_error(input: impl ToErrorValue, error: PyErr, decimal_exception: Bound<'_, PyAny>) -> ValError { let py = decimal_exception.py(); - if error.matches(py, decimal_exception) { + if error.matches(py, decimal_exception).unwrap_or(false) { ValError::new(ErrorTypeDefaults::DecimalParsing, input) - } else if error.matches(py, PyTypeError::type_object_bound(py)) { + } else if error.matches(py, PyTypeError::type_object_bound(py)).unwrap_or(false) { ValError::new(ErrorTypeDefaults::DecimalType, input) } else { ValError::InternalErr(error) diff --git a/src/validators/dict.rs b/src/validators/dict.rs index 985fe95b6..b65b38fb1 100644 --- a/src/validators/dict.rs +++ b/src/validators/dict.rs @@ -107,7 +107,7 @@ where { type Output = ValResult; fn consume_iterator(self, iterator: impl Iterator>) -> ValResult { - let output = PyDict::new_bound(self.py); + let output = PyDict::new(self.py); let mut errors: Vec = Vec::new(); let allow_partial = self.state.allow_partial; diff --git a/src/validators/enum_.rs b/src/validators/enum_.rs index 73589d806..39b0cda56 100644 --- a/src/validators/enum_.rs +++ b/src/validators/enum_.rs @@ -103,8 +103,10 @@ impl Validator for EnumValidator { state: &mut ValidationState<'_, 'py>, ) -> ValResult { let class = self.class.bind(py); - if input.as_python().is_some_and(|any| any.is_exact_instance(class)) { - return Ok(input.to_object(py)); + if let Some(py_input) = input.as_python() { + if py_input.is_instance(class)? { + return Ok(py_input.clone().unbind()); + } } let strict = state.strict_or(self.strict); if strict && input.as_python().is_some() { @@ -125,7 +127,7 @@ impl Validator for EnumValidator { } else if let Ok(res) = class.as_unbound().call1(py, (input.as_python(),)) { return Ok(res); } else if let Some(ref missing) = self.missing { - let enum_value = missing.bind(py).call1((input.to_object(py),)).map_err(|_| { + let enum_value = missing.bind(py).call1((input.to_object(py)?,)).map_err(|_| { ValError::new( ErrorType::Enum { expected: self.expected_repr.clone(), diff --git a/src/validators/float.rs b/src/validators/float.rs index 3a79de9fe..69f006d12 100644 --- a/src/validators/float.rs +++ b/src/validators/float.rs @@ -74,7 +74,7 @@ impl Validator for FloatValidator { if !self.allow_inf_nan && !either_float.as_f64().is_finite() { return Err(ValError::new(ErrorTypeDefaults::FiniteNumber, input)); } - Ok(either_float.into_py(py)) + Ok(either_float.into_pyobject(py)) } fn get_name(&self) -> &str { @@ -164,7 +164,7 @@ impl Validator for ConstrainedFloatValidator { )); } } - Ok(either_float.into_py(py)) + Ok(either_float.into_pyobject(py)) } fn get_name(&self) -> &str { diff --git a/src/validators/frozenset.rs b/src/validators/frozenset.rs index 60fe451a7..e28afa88a 100644 --- a/src/validators/frozenset.rs +++ b/src/validators/frozenset.rs @@ -46,7 +46,7 @@ impl Validator for FrozenSetValidator { fail_fast: self.fail_fast, })??; min_length_check!(input, "Frozenset", self.min_length, f_set); - Ok(f_set.into_py(py)) + Ok(f_set.into_pyobject(py)) } fn get_name(&self) -> &str { diff --git a/src/validators/function.rs b/src/validators/function.rs index 286107c92..b0dad0d38 100644 --- a/src/validators/function.rs +++ b/src/validators/function.rs @@ -101,9 +101,9 @@ impl FunctionBeforeValidator { ) -> ValResult { let r = if self.info_arg { let info = ValidationInfo::new(py, state.extra(), &self.config, self.field_name.clone()); - self.func.call1(py, (input.to_object(py), info)) + self.func.call1(py, (input.to_object(py)?, info)) } else { - self.func.call1(py, (input.to_object(py),)) + self.func.call1(py, (input.to_object(py)?,)) }; let value = r.map_err(|e| convert_err(py, e, input))?; call(value.into_bound(py), state) @@ -255,9 +255,9 @@ impl Validator for FunctionPlainValidator { ) -> ValResult { let r = if self.info_arg { let info = ValidationInfo::new(py, state.extra(), &self.config, self.field_name.clone()); - self.func.call1(py, (input.to_object(py), info)) + self.func.call1(py, (input.to_object(py)?, info)) } else { - self.func.call1(py, (input.to_object(py),)) + self.func.call1(py, (input.to_object(py)?,)) }; r.map_err(|e| convert_err(py, e, input)) } @@ -319,9 +319,9 @@ impl FunctionWrapValidator { ) -> ValResult { let r = if self.info_arg { let info = ValidationInfo::new(py, state.extra(), &self.config, self.field_name.clone()); - self.func.call1(py, (input.to_object(py), handler, info)) + self.func.call1(py, (input.to_object(py)?, handler, info)) } else { - self.func.call1(py, (input.to_object(py), handler)) + self.func.call1(py, (input.to_object(py)?, handler)) }; r.map_err(|e| convert_err(py, e, input)) } @@ -374,7 +374,7 @@ impl Validator for FunctionWrapValidator { self.validation_error_cause, ), updated_field_name: field_name.to_string(), - updated_field_value: field_value.to_object(py), + updated_field_value: field_value.clone().into(), }; self._validate(Bound::new(py, handler)?.as_any(), py, obj, state) } @@ -458,7 +458,7 @@ macro_rules! py_err_string { Ok(py_string) => match py_string.to_str() { Ok(_) => ValError::new( ErrorType::$type_member { - error: Some($py_err.into_py($py)), + error: Some($py_err.into_pyobject($py)), context: None, }, $input, diff --git a/src/validators/generator.rs b/src/validators/generator.rs index 1e8d41c85..10be4b9db 100644 --- a/src/validators/generator.rs +++ b/src/validators/generator.rs @@ -89,7 +89,7 @@ impl Validator for GeneratorValidator { hide_input_in_errors: self.hide_input_in_errors, validation_error_cause: self.validation_error_cause, }; - Ok(v_iterator.into_py(py)) + Ok(v_iterator.into_pyobject(py)) } fn get_name(&self) -> &str { @@ -140,7 +140,7 @@ impl ValidatorIterator { ); return Err(ValidationError::from_val_error( py, - "ValidatorIterator".to_object(py), + "ValidatorIterator".into_pyobject(py)?.into(), InputType::Python, val_error, None, @@ -153,7 +153,7 @@ impl ValidatorIterator { .validate(py, next.borrow_input(), Some(index.into())) .map(Some) } - None => Ok(Some(next.to_object(py))), + None => Ok(Some(next.into_pyobject(py)?.unbind())), }, None => { if let Some(min_length) = min_length { @@ -169,7 +169,7 @@ impl ValidatorIterator { ); return Err(ValidationError::from_val_error( py, - "ValidatorIterator".to_object(py), + "ValidatorIterator".into_pyobject(py)?.into(), InputType::Python, val_error, None, @@ -254,8 +254,8 @@ impl InternalValidator { data: extra.data.as_ref().map(|d| d.clone().into()), strict: extra.strict, from_attributes: extra.from_attributes, - context: extra.context.map(|d| d.into_py(py)), - self_instance: extra.self_instance.map(|d| d.into_py(py)), + context: extra.context.map(|d| d.into_pyobject(py)), + self_instance: extra.self_instance.map(|d| d.into_pyobject(py)), recursion_guard: state.recursion_guard.clone(), exactness: state.exactness, validation_mode: extra.input_type, diff --git a/src/validators/int.rs b/src/validators/int.rs index e5b233322..fe73107e1 100644 --- a/src/validators/int.rs +++ b/src/validators/int.rs @@ -51,7 +51,7 @@ impl Validator for IntValidator { ) -> ValResult { input .validate_int(state.strict_or(self.strict)) - .map(|val_match| val_match.unpack(state).into_py(py)) + .map(|val_match| val_match.unpack(state).into_pyobject(py)) } fn get_name(&self) -> &str { @@ -136,7 +136,7 @@ impl Validator for ConstrainedIntValidator { )); } } - Ok(either_int.into_py(py)) + Ok(either_int.into_pyobject(py)) } fn get_name(&self) -> &str { diff --git a/src/validators/list.rs b/src/validators/list.rs index 87600fb81..12fae105c 100644 --- a/src/validators/list.rs +++ b/src/validators/list.rs @@ -143,7 +143,7 @@ impl Validator for ListValidator { if let Some(py_list) = seq.as_py_list() { length_check!(input, "List", self.min_length, self.max_length, py_list); let list_copy = py_list.get_slice(0, usize::MAX); - return Ok(list_copy.into_py(py)); + return Ok(list_copy.into_pyobject(py)); } seq.iterate(ToVec { @@ -156,7 +156,7 @@ impl Validator for ListValidator { } }; min_length_check!(input, "List", self.min_length, output); - Ok(output.into_py(py)) + Ok(output.into_pyobject(py)) } fn get_name(&self) -> &str { diff --git a/src/validators/literal.rs b/src/validators/literal.rs index 9ef0d5f82..0272d4f5f 100644 --- a/src/validators/literal.rs +++ b/src/validators/literal.rs @@ -1,6 +1,7 @@ // Validator for things inside of a typing.Literal[] // which can be an int, a string, bytes or an Enum value (including `class Foo(str, Enum)` type enums) use core::fmt::Debug; +use std::cell::OnceCell; use pyo3::prelude::*; use pyo3::types::{PyDict, PyInt, PyList}; @@ -48,9 +49,9 @@ impl LiteralLookup { let mut expected_bool = BoolLiteral::default(); let mut expected_int = AHashMap::new(); let mut expected_str: AHashMap = AHashMap::new(); - let expected_py_dict = PyDict::new_bound(py); + let expected_py_dict = PyDict::new(py); let mut expected_py_values = Vec::new(); - let expected_py_primitives = PyDict::new_bound(py); + let expected_py_primitives = PyDict::new(py); let mut values = Vec::new(); for (k, v) in expected { let id = values.len(); @@ -139,21 +140,29 @@ impl LiteralLookup { } } // cache py_input if needed, since we might need it for multiple lookups - let mut py_input = None; + let py_input = OnceCell::new(); + let get_py_input = || match py_input.get() { + Some(py_input) => PyResult::<_>::Ok(py_input), + None => { + let _ = py_input.set(input.to_object(py)?); + Ok(py_input.get().unwrap()) + } + }; + if let Some(expected_py_dict) = &self.expected_py_dict { - let py_input = py_input.get_or_insert_with(|| input.to_object(py)); + let py_input = get_py_input()?; // We don't use ? to unpack the result of `get_item` in the next line because unhashable // inputs will produce a TypeError, which in this case we just want to treat equivalently // to a failed lookup - if let Ok(Some(v)) = expected_py_dict.bind(py).get_item(&*py_input) { + if let Ok(Some(v)) = expected_py_dict.bind(py).get_item(py_input) { let id: usize = v.extract().unwrap(); return Ok(Some((input, &self.values[id]))); } }; if let Some(expected_py_values) = &self.expected_py_values { - let py_input = py_input.get_or_insert_with(|| input.to_object(py)); + let py_input = get_py_input()?; for (k, id) in expected_py_values { - if k.bind(py).eq(&*py_input).unwrap_or(false) { + if k.bind(py).eq(py_input).unwrap_or(false) { return Ok(Some((input, &self.values[*id]))); } } @@ -162,11 +171,11 @@ impl LiteralLookup { // this one must be last to avoid conflicts with the other lookups, think of this // almost as a lax fallback if let Some(expected_py_primitives) = &self.expected_py_primitives { - let py_input = py_input.get_or_insert_with(|| input.to_object(py)); + let py_input = get_py_input()?; // We don't use ? to unpack the result of `get_item` in the next line because unhashable // inputs will produce a TypeError, which in this case we just want to treat equivalently // to a failed lookup - if let Ok(Some(v)) = expected_py_primitives.bind(py).get_item(&*py_input) { + if let Ok(Some(v)) = expected_py_primitives.bind(py).get_item(py_input) { let id: usize = v.extract().unwrap(); return Ok(Some((input, &self.values[id]))); } diff --git a/src/validators/mod.rs b/src/validators/mod.rs index 10a4d6e34..ee7e02aa4 100644 --- a/src/validators/mod.rs +++ b/src/validators/mod.rs @@ -97,7 +97,7 @@ impl PySome { #[classattr] fn __match_args__(py: Python) -> Bound<'_, PyTuple> { - PyTuple::new_bound(py, vec![intern!(py, "value")]) + PyTuple::new(py, vec![intern!(py, "value")]) } } @@ -126,7 +126,7 @@ impl SchemaValidator { let validator = build_validator(schema, config, &mut definitions_builder)?; let definitions = definitions_builder.finish()?; - let py_schema = schema.into_py(py); + let py_schema = schema.into_pyobject(py); let py_config = match config { Some(c) if !c.is_empty() => Some(c.clone().into()), _ => None, @@ -136,8 +136,8 @@ impl SchemaValidator { None => None, }; let title = match config_title { - Some(t) => t.into_py(py), - None => validator.get_name().into_py(py), + Some(t) => t.into_pyobject(py), + None => validator.get_name().into_pyobject(py), }; let hide_input_in_errors: bool = config.get_as(intern!(py, "hide_input_in_errors"))?.unwrap_or(false); let validation_error_cause: bool = config.get_as(intern!(py, "validation_error_cause"))?.unwrap_or(false); @@ -310,8 +310,8 @@ impl SchemaValidator { let r = self.validator.default_value(py, None::, &mut state); match r { Ok(maybe_default) => match maybe_default { - Some(v) => Ok(PySome::new(v).into_py(py)), - None => Ok(py.None().into_py(py)), + Some(v) => Ok(PySome::new(v).into_pyobject(py)), + None => Ok(py.None().into_pyobject(py)), }, Err(e) => Err(self.prepare_validation_err(py, e, InputType::Python)), } @@ -440,7 +440,7 @@ impl<'py> SelfValidator<'py> { fn build(py: Python) -> PyResult { let code = include_str!("../self_schema.py"); - let locals = PyDict::new_bound(py); + let locals = PyDict::new(py); py.run_bound(code, None, Some(&locals))?; let self_schema = locals.get_as_req(intern!(py, "self_schema"))?; @@ -456,7 +456,7 @@ impl<'py> SelfValidator<'py> { definitions, py_schema: py.None(), py_config: None, - title: "Self Schema".into_py(py), + title: "Self Schema".into_pyobject(py), hide_input_in_errors: false, validation_error_cause: false, cache_str: true.into(), diff --git a/src/validators/model.rs b/src/validators/model.rs index 1564e7e31..dffe2a9b8 100644 --- a/src/validators/model.rs +++ b/src/validators/model.rs @@ -172,7 +172,7 @@ impl Validator for ModelValidator { self.validate_construct(py, &inner_input, Some(&fields_set), state) } } else { - Ok(input.to_object(py)) + Ok(input.to_object(py)?.unbind()) } } else { // Having to construct a new model is not an exact match @@ -205,7 +205,7 @@ impl Validator for ModelValidator { let output = self.validator.validate(py, field_value, state)?; force_setattr(py, model, intern!(py, ROOT_FIELD), output)?; - Ok(model.into_py(py)) + Ok(model.into_pyobject(py)) }; } let old_dict = model.getattr(intern!(py, DUNDER_DICT))?.downcast_into::()?; @@ -233,14 +233,9 @@ impl Validator for ModelValidator { } } - force_setattr(py, model, intern!(py, DUNDER_DICT), validated_dict.to_object(py))?; - force_setattr( - py, - model, - intern!(py, DUNDER_MODEL_EXTRA_KEY), - validated_extra.to_object(py), - )?; - Ok(model.into_py(py)) + force_setattr(py, model, intern!(py, DUNDER_DICT), validated_dict)?; + force_setattr(py, model, intern!(py, DUNDER_MODEL_EXTRA_KEY), validated_extra)?; + Ok(model.into_pyobject(py)) } fn get_name(&self) -> &str { @@ -263,10 +258,10 @@ impl ModelValidator { let output = self.validator.validate(py, input, state)?; if self.root_model { - let fields_set = if input.to_object(py).is(&self.undefined) { + let fields_set = if input.to_object(py)?.is(&self.undefined) { PySet::empty_bound(py)? } else { - PySet::new_bound(py, [&String::from(ROOT_FIELD)])? + PySet::new(py, [&String::from(ROOT_FIELD)])? }; force_setattr(py, self_instance, intern!(py, DUNDER_FIELDS_SET_KEY), &fields_set)?; force_setattr(py, self_instance, intern!(py, ROOT_FIELD), &output)?; @@ -304,10 +299,10 @@ impl ModelValidator { let instance = create_class(self.class.bind(py))?; if self.root_model { - let fields_set = if input.to_object(py).is(&self.undefined) { + let fields_set = if input.to_object(py)?.is(&self.undefined) { PySet::empty_bound(py)? } else { - PySet::new_bound(py, [&String::from(ROOT_FIELD)])? + PySet::new(py, [&String::from(ROOT_FIELD)])? }; force_setattr(py, &instance, intern!(py, DUNDER_FIELDS_SET_KEY), &fields_set)?; force_setattr(py, &instance, intern!(py, ROOT_FIELD), output)?; diff --git a/src/validators/model_fields.rs b/src/validators/model_fields.rs index 7aba7c8e3..77f7ee8ed 100644 --- a/src/validators/model_fields.rs +++ b/src/validators/model_fields.rs @@ -149,7 +149,7 @@ impl Validator for ModelFieldsValidator { Err(err) => return Err(err), }; - let model_dict = PyDict::new_bound(py); + let model_dict = PyDict::new(py); let mut model_extra_dict_op: Option> = None; let mut errors: Vec = Vec::with_capacity(self.fields.len()); let mut fields_set_vec: Vec> = Vec::with_capacity(self.fields.len()); @@ -251,7 +251,7 @@ impl Validator for ModelFieldsValidator { self, iterator: impl Iterator>, ) -> ValResult> { - let model_extra_dict = PyDict::new_bound(self.py); + let model_extra_dict = PyDict::new(self.py); for item_result in iterator { let (raw_key, value) = item_result?; let either_str = match raw_key @@ -303,7 +303,7 @@ impl Validator for ModelFieldsValidator { Err(err) => return Err(err), } } else { - model_extra_dict.set_item(&py_key, value.to_object(self.py))?; + model_extra_dict.set_item(&py_key, value.to_object(self.py)?)?; self.fields_set_vec.push(py_key.into()); }; } @@ -331,13 +331,13 @@ impl Validator for ModelFieldsValidator { if !errors.is_empty() { Err(ValError::LineErrors(errors)) } else { - let fields_set = PySet::new_bound(py, &fields_set_vec)?; + let fields_set = PySet::new(py, &fields_set_vec)?; state.add_fields_set(fields_set_count); // if we have extra=allow, but we didn't create a dict because we were validating // from attributes, set it now so __pydantic_extra__ is always a dict if extra=allow if matches!(self.extra_behavior, ExtraBehavior::Allow) && model_extra_dict_op.is_none() { - model_extra_dict_op = Some(PyDict::new_bound(py)); + model_extra_dict_op = Some(PyDict::new(py)); }; Ok((model_dict, model_extra_dict_op, fields_set).to_object(py)) @@ -354,13 +354,13 @@ impl Validator for ModelFieldsValidator { ) -> ValResult { let dict = obj.downcast::()?; - let get_updated_dict = |output: PyObject| { + let get_updated_dict = |output: &Bound<'py, PyAny>| { dict.set_item(field_name, output)?; Ok(dict) }; let prepare_result = |result: ValResult| match result { - Ok(output) => get_updated_dict(output), + Ok(output) => get_updated_dict(&output.into_bound(py)), Err(ValError::LineErrors(line_errors)) => { let errors = line_errors .into_iter() @@ -375,7 +375,7 @@ impl Validator for ModelFieldsValidator { let data_dict = dict.copy()?; if let Err(err) = data_dict.del_item(field_name) { // KeyError is fine here as the field might not be in the dict - if !err.get_type_bound(py).is(&PyType::new_bound::(py)) { + if !err.get_type_bound(py).is(&PyType::new::(py)) { return Err(err.into()); } } @@ -402,7 +402,7 @@ impl Validator for ModelFieldsValidator { match self.extra_behavior { ExtraBehavior::Allow => match self.extras_validator { Some(ref validator) => prepare_result(validator.validate(py, field_value, state))?, - None => get_updated_dict(field_value.to_object(py))?, + None => get_updated_dict(field_value)?, }, ExtraBehavior::Forbid | ExtraBehavior::Ignore => { return Err(ValError::new_with_loc( @@ -420,7 +420,7 @@ impl Validator for ModelFieldsValidator { let new_extra = match &self.extra_behavior { ExtraBehavior::Allow => { - let non_extra_data = PyDict::new_bound(py); + let non_extra_data = PyDict::new(py); self.fields.iter().try_for_each(|f| -> PyResult<()> { let Some(popped_value) = new_data.get_item(&f.name)? else { // field not present in __dict__ for some reason; let the rest of the @@ -439,7 +439,7 @@ impl Validator for ModelFieldsValidator { _ => py.None(), }; - let fields_set = PySet::new_bound(py, &[field_name.to_string()])?; + let fields_set = PySet::new(py, &[field_name.to_string()])?; Ok((new_data.to_object(py), new_extra, fields_set.to_object(py)).to_object(py)) } diff --git a/src/validators/set.rs b/src/validators/set.rs index 281cfc37d..612d98a14 100644 --- a/src/validators/set.rs +++ b/src/validators/set.rs @@ -77,7 +77,7 @@ impl Validator for SetValidator { fail_fast: self.fail_fast, })??; min_length_check!(input, "Set", self.min_length, set); - Ok(set.into_py(py)) + Ok(set.into_pyobject(py)) } fn get_name(&self) -> &str { diff --git a/src/validators/string.rs b/src/validators/string.rs index 985aa1068..6473d11f6 100644 --- a/src/validators/string.rs +++ b/src/validators/string.rs @@ -49,7 +49,12 @@ impl Validator for StrValidator { ) -> ValResult { input .validate_str(state.strict_or(self.strict), self.coerce_numbers_to_str) - .map(|val_match| val_match.unpack(state).as_py_string(py, state.cache_str()).into_py(py)) + .map(|val_match| { + val_match + .unpack(state) + .as_py_string(py, state.cache_str()) + .into_pyobject(py) + }) } fn get_name(&self) -> &str { @@ -138,7 +143,7 @@ impl Validator for StrConstrainedValidator { // we haven't modified the string, return the original as it might be a PyString either_str.as_py_string(py, state.cache_str()) }; - Ok(py_string.into_py(py)) + Ok(py_string.into_pyobject(py)) } fn get_name(&self) -> &str { @@ -254,7 +259,7 @@ impl Pattern { // so that any flags, etc. are preserved Ok(Self { pattern: pattern_str, - engine: RegexEngine::PythonRe(pattern.to_object(py)), + engine: RegexEngine::PythonRe(pattern.unbind()), }) } else { let engine = match engine { diff --git a/src/validators/time.rs b/src/validators/time.rs index a9c6ae561..9031fb778 100644 --- a/src/validators/time.rs +++ b/src/validators/time.rs @@ -77,7 +77,7 @@ impl Validator for TimeValidator { tz_constraint.tz_check(raw_time.tz_offset, input)?; } } - Ok(time.try_into_py(py)?) + Ok(time.try_into_pyobject(py)?) } fn get_name(&self) -> &str { diff --git a/src/validators/timedelta.rs b/src/validators/timedelta.rs index 47817662d..ec9f24381 100644 --- a/src/validators/timedelta.rs +++ b/src/validators/timedelta.rs @@ -74,7 +74,7 @@ impl Validator for TimeDeltaValidator { let timedelta = input .validate_timedelta(state.strict_or(self.strict), self.microseconds_precision)? .unpack(state); - let py_timedelta = timedelta.try_into_py(py)?; + let py_timedelta = timedelta.try_into_pyobject(py)?; if let Some(constraints) = &self.constraints { let raw_timedelta = timedelta.to_duration()?; diff --git a/src/validators/tuple.rs b/src/validators/tuple.rs index 60e777e01..dfaa69139 100644 --- a/src/validators/tuple.rs +++ b/src/validators/tuple.rs @@ -306,7 +306,7 @@ impl Validator for TupleValidator { } if errors.is_empty() { - Ok(PyTuple::new_bound(py, output).into_py(py)) + Ok(PyTuple::new(py, output).into_pyobject(py)) } else { Err(ValError::LineErrors(errors)) } diff --git a/src/validators/typed_dict.rs b/src/validators/typed_dict.rs index 0c127b93b..3af722f7a 100644 --- a/src/validators/typed_dict.rs +++ b/src/validators/typed_dict.rs @@ -150,7 +150,7 @@ impl Validator for TypedDictValidator { let strict = state.strict_or(self.strict); let dict = input.validate_dict(strict)?; - let output_dict = PyDict::new_bound(py); + let output_dict = PyDict::new(py); let mut errors: Vec = Vec::with_capacity(self.fields.len()); let partial_last_key = if state.allow_partial.is_active() { @@ -334,7 +334,7 @@ impl Validator for TypedDictValidator { Err(err) => return Err(err), } } else { - self.output_dict.set_item(py_key, value.to_object(self.py))?; + self.output_dict.set_item(py_key, value.to_object(self.py)?)?; }; } } diff --git a/src/validators/union.rs b/src/validators/union.rs index 747f6a0cf..bfe744212 100644 --- a/src/validators/union.rs +++ b/src/validators/union.rs @@ -319,7 +319,7 @@ impl BuildValidator for TaggedUnionValidator { let discriminator = Discriminator::new(py, &schema.get_as_req(intern!(py, "discriminator"))?)?; let discriminator_repr = discriminator.to_string_py(py)?; - let choices = PyDict::new_bound(py); + let choices = PyDict::new(py); let mut tags_repr = String::with_capacity(50); let mut descr = String::with_capacity(50); let mut first = true; @@ -378,10 +378,10 @@ impl Validator for TaggedUnionValidator { Some((_, value)) => value, None => return Err(self.tag_not_found(input)), }; - self.find_call_validator(py, tag.borrow_input().to_object(py).bind(py), input, state) + self.find_call_validator(py, &tag.borrow_input().to_object(py)?, input, state) } Discriminator::Function(func) => { - let tag: Py = func.call1(py, (input.to_object(py),))?; + let tag: Py = func.call1(py, (input.to_object(py)?,))?; if tag.is_none(py) { Err(self.tag_not_found(input)) } else { diff --git a/src/validators/url.rs b/src/validators/url.rs index 46bab20c1..778cd0620 100644 --- a/src/validators/url.rs +++ b/src/validators/url.rs @@ -93,7 +93,7 @@ impl Validator for UrlValidator { Ok(()) => { // Lax rather than strict to preserve V2.4 semantic that str wins over url in union state.floor_exactness(Exactness::Lax); - Ok(either_url.into_py(py)) + Ok(either_url.into_pyobject(py)) } Err(error_type) => Err(ValError::new(error_type, input)), } @@ -156,10 +156,10 @@ enum EitherUrl<'py> { } impl EitherUrl<'_> { - fn into_py(self, py: Python) -> PyObject { + fn into_pyobject(self, py: Python) -> PyObject { match self { - EitherUrl::Py(py_url) => py_url.into_py(py), - EitherUrl::Rust(rust_url) => PyUrl::new(rust_url).into_py(py), + EitherUrl::Py(py_url) => py_url.into_pyobject(py), + EitherUrl::Rust(rust_url) => PyUrl::new(rust_url).into_pyobject(py), } } } @@ -258,7 +258,7 @@ impl Validator for MultiHostUrlValidator { Ok(()) => { // Lax rather than strict to preserve V2.4 semantic that str wins over url in union state.floor_exactness(Exactness::Lax); - Ok(multi_url.into_py(py)) + Ok(multi_url.into_pyobject(py)) } Err(error_type) => Err(ValError::new(error_type, input)), } @@ -325,10 +325,10 @@ enum EitherMultiHostUrl<'py> { } impl EitherMultiHostUrl<'_> { - fn into_py(self, py: Python) -> PyObject { + fn into_pyobject(self, py: Python) -> PyObject { match self { - EitherMultiHostUrl::Py(py_multi_url) => py_multi_url.into_py(py), - EitherMultiHostUrl::Rust(rust_multi_url) => rust_multi_url.into_py(py), + EitherMultiHostUrl::Py(py_multi_url) => py_multi_url.into_pyobject(py), + EitherMultiHostUrl::Rust(rust_multi_url) => rust_multi_url.into_pyobject(py), } } } diff --git a/src/validators/uuid.rs b/src/validators/uuid.rs index e19dfcfd0..9740ae759 100644 --- a/src/validators/uuid.rs +++ b/src/validators/uuid.rs @@ -113,7 +113,7 @@ impl Validator for UuidValidator { )); } } - Ok(py_input.to_object(py)) + Ok(py_input.clone().unbind()) } else if state.strict_or(self.strict) && state.extra().input_type == InputType::Python { Err(ValError::new( ErrorType::IsInstanceOf { diff --git a/src/validators/with_default.rs b/src/validators/with_default.rs index 565c31060..7baa8e0b7 100644 --- a/src/validators/with_default.rs +++ b/src/validators/with_default.rs @@ -18,7 +18,7 @@ use crate::PydanticUndefinedType; static COPY_DEEPCOPY: GILOnceCell = GILOnceCell::new(); fn get_deepcopy(py: Python) -> PyResult { - Ok(py.import_bound("copy")?.getattr("deepcopy")?.into_py(py)) + Ok(py.import_bound("copy")?.getattr("deepcopy")?.into_pyobject(py)) } #[derive(Debug, Clone)] @@ -157,7 +157,7 @@ impl Validator for WithDefaultValidator { input: &(impl Input<'py> + ?Sized), state: &mut ValidationState<'_, 'py>, ) -> ValResult { - if input.to_object(py).is(&self.undefined) { + if input.to_object(py)?.is(&self.undefined) { Ok(self.default_value(py, None::, state)?.unwrap()) } else { match self.validator.validate(py, input, state) { @@ -184,7 +184,7 @@ impl Validator for WithDefaultValidator { Some(stored_dft) => { let dft: Py = if self.copy_default { let deepcopy_func = COPY_DEEPCOPY.get_or_init(py, || get_deepcopy(py).unwrap()); - deepcopy_func.call1(py, (&stored_dft,))?.into_py(py) + deepcopy_func.call1(py, (&stored_dft,))?.into_pyobject(py) } else { stored_dft }; diff --git a/tests/test.rs b/tests/test.rs index dbc7a21e7..68140d159 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -22,7 +22,7 @@ mod tests { // 'type': 'function-wrap', // 'function': lambda: None, // }, - let code = r"{ + let code = cr"{ 'type': 'definitions', 'schema': {'type': 'definition-ref', 'schema_ref': 'C-ref'}, 'definitions': [ @@ -45,7 +45,7 @@ mod tests { }, ] }"; - let schema: Bound<'_, PyDict> = py.eval_bound(code, None, None).unwrap().extract().unwrap(); + let schema: Bound<'_, PyDict> = py.eval(code, None, None).unwrap().extract().unwrap(); SchemaSerializer::py_new(schema, None).unwrap(); }); } @@ -53,7 +53,7 @@ mod tests { #[test] fn test_serialize_computed_fields() { Python::with_gil(|py| { - let code = r#" + let code = cr#" class A: @property def b(self) -> str: @@ -73,8 +73,8 @@ schema = { } a = A() "#; - let locals = PyDict::new_bound(py); - py.run_bound(code, None, Some(&locals)).unwrap(); + let locals = PyDict::new(py); + py.run(code, None, Some(&locals)).unwrap(); let a = locals.get_item("a").unwrap().unwrap(); let schema = locals .get_item("schema") @@ -109,7 +109,7 @@ a = A() #[test] fn test_literal_schema() { Python::with_gil(|py| { - let code = r#" + let code = cr#" schema = { "type": "dict", "keys_schema": { @@ -123,8 +123,8 @@ schema = { } json_input = '{"a": "something"}' "#; - let locals = PyDict::new_bound(py); - py.run_bound(code, None, Some(&locals)).unwrap(); + let locals = PyDict::new(py); + py.run(code, None, Some(&locals)).unwrap(); let schema = locals.get_item("schema").unwrap().unwrap(); let json_input = locals.get_item("json_input").unwrap().unwrap(); let binding = SchemaValidator::py_new(py, &schema, None) @@ -140,7 +140,7 @@ json_input = '{"a": "something"}' #[test] fn test_segfault_for_recursive_schemas() { Python::with_gil(|py| { - let code = r" + let code = cr" schema = { 'type': 'definitions', 'schema': { @@ -176,8 +176,8 @@ schema = { dump_json_input_1 = 1 dump_json_input_2 = {'a': 'something'} "; - let locals = PyDict::new_bound(py); - py.run_bound(code, None, Some(&locals)).unwrap(); + let locals = PyDict::new(py); + py.run(code, None, Some(&locals)).unwrap(); let schema = locals .get_item("schema") .unwrap()