From 6cc9989a80a11c7ea4a42c1992e5f9776144809b Mon Sep 17 00:00:00 2001 From: Neev Cohen Date: Tue, 26 Mar 2024 20:46:53 +0100 Subject: [PATCH] Add coerce_numbers_to_str option in str_schema --- python/pydantic_core/core_schema.py | 3 +++ src/validators/string.rs | 9 +++------ tests/validators/test_string.py | 31 +++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/python/pydantic_core/core_schema.py b/python/pydantic_core/core_schema.py index 1b1ecc7fa..b5c4ae203 100644 --- a/python/pydantic_core/core_schema.py +++ b/python/pydantic_core/core_schema.py @@ -764,6 +764,7 @@ def str_schema( to_upper: bool | None = None, regex_engine: Literal['rust-regex', 'python-re'] | None = None, strict: bool | None = None, + coerce_numbers_to_str: bool | None = None, ref: str | None = None, metadata: Any = None, serialization: SerSchema | None = None, @@ -793,6 +794,7 @@ def str_schema( - `python-re` use the [`re`](https://docs.python.org/3/library/re.html) module, which supports all regex features, but may be slower. strict: Whether the value should be a string or a value that can be converted to a string + coerce_numbers_to_str: Whether to enable coercion of any `Number` type to `str` (not applicable in `strict` mode). ref: optional unique identifier of the schema, used to reference the schema in other places metadata: Any other information you want to include with the schema, not used by pydantic-core serialization: Custom serialization schema @@ -807,6 +809,7 @@ def str_schema( to_upper=to_upper, regex_engine=regex_engine, strict=strict, + coerce_numbers_to_str=coerce_numbers_to_str, ref=ref, metadata=metadata, serialization=serialization, diff --git a/src/validators/string.rs b/src/validators/string.rs index 645217edd..de7597947 100644 --- a/src/validators/string.rs +++ b/src/validators/string.rs @@ -3,6 +3,7 @@ use pyo3::prelude::*; use pyo3::types::{PyDict, PyString}; use regex::Regex; +use crate::build_tools::schema_or_config_same; use crate::build_tools::{is_strict, py_schema_error_type, schema_or_config}; use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::Input; @@ -184,12 +185,8 @@ impl StrConstrainedValidator { let to_upper: bool = schema_or_config(schema, config, intern!(py, "to_upper"), intern!(py, "str_to_upper"))?.unwrap_or(false); - let coerce_numbers_to_str = match config { - Some(c) => c - .get_item("coerce_numbers_to_str")? - .map_or(Ok(false), |any| any.is_truthy())?, - None => false, - }; + let coerce_numbers_to_str: bool = + schema_or_config_same(schema, config, intern!(py, "coerce_numbers_to_str"))?.unwrap_or(false); Ok(Self { strict: is_strict(schema, config)?, diff --git a/tests/validators/test_string.py b/tests/validators/test_string.py index 22bcd5445..203c17b1e 100644 --- a/tests/validators/test_string.py +++ b/tests/validators/test_string.py @@ -367,3 +367,34 @@ def test_backtracking_regex_python(mode) -> None: with pytest.raises(ValidationError): # not a valid match for the pattern v.validate_python('r#"#') + + +@pytest.mark.parametrize('number', (42, 443, 10242)) +def test_coerce_numbers_to_str_schema(number: int): + v = SchemaValidator(core_schema.str_schema(coerce_numbers_to_str=True)) + assert v.validate_python(number) == str(number) + assert v.validate_json(str(number)) == str(number) + + +@pytest.mark.parametrize('number', (42, 443, 10242)) +def test_coerce_numbers_to_str_schema_precedence(number: int): + config = core_schema.CoreConfig(coerce_numbers_to_str=False) + v = SchemaValidator(core_schema.str_schema(coerce_numbers_to_str=True), config=config) + assert v.validate_python(number) == str(number) + assert v.validate_json(str(number)) == str(number) + + config = core_schema.CoreConfig(coerce_numbers_to_str=True) + v = SchemaValidator(core_schema.str_schema(coerce_numbers_to_str=False), config=config) + with pytest.raises(ValidationError): + v.validate_python(number) + with pytest.raises(ValidationError): + v.validate_json(str(number)) + + +@pytest.mark.parametrize('number', (42, 443, 10242)) +def test_coerce_numbers_to_str_schema_with_strict_mode(number: int): + v = SchemaValidator(core_schema.str_schema(coerce_numbers_to_str=True, strict=True)) + with pytest.raises(ValidationError): + v.validate_python(number) + with pytest.raises(ValidationError): + v.validate_json(str(number))