From 3d2ebef8f76625d6f82e6bf417682fd08ac66296 Mon Sep 17 00:00:00 2001 From: Maxim Martynov Date: Mon, 18 Dec 2023 18:56:47 +0300 Subject: [PATCH] Fix ImportString documentation about default value validation (#8386) --- pydantic/types.py | 34 +++++++++++++++++++++++++++------- tests/test_types.py | 18 ++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/pydantic/types.py b/pydantic/types.py index 58878b737d..f34a3b6e0c 100644 --- a/pydantic/types.py +++ b/pydantic/types.py @@ -887,15 +887,11 @@ class ImportString: On model instantiation, pointers will be evaluated and imported. There is some nuance to this behavior, demonstrated in the examples below. - > A known limitation: setting a default value to a string - > won't result in validation (thus evaluation). This is actively - > being worked on. - **Good behavior:** ```py from math import cos - from pydantic import BaseModel, ImportString, ValidationError + from pydantic import BaseModel, Field, ImportString, ValidationError class ImportThings(BaseModel): @@ -925,7 +921,31 @@ class ImportThings(BaseModel): # Actual python objects can be assigned as well my_cos = ImportThings(obj=cos) my_cos_2 = ImportThings(obj='math.cos') - assert my_cos == my_cos_2 + my_cos_3 = ImportThings(obj='math:cos') + assert my_cos == my_cos_2 == my_cos_3 + + + # You can set default field value either as Python object: + class ImportThingsDefaultPyObj(BaseModel): + obj: ImportString = math.cos + + + # or as a string value (but only if used with `validate_default=True`) + class ImportThingsDefaultString(BaseModel): + obj: ImportString = Field(default='math.cos', validate_default=True) + + + my_cos_default1 = ImportThingsDefaultPyObj() + my_cos_default2 = ImportThingsDefaultString() + assert my_cos_default1.obj == my_cos_default2.obj == math.cos + + + # note: this will not work! + class ImportThingsMissingValidateDefault(BaseModel): + obj: ImportString = 'math.cos' + + my_cos_default3 = ImportThingsMissingValidateDefault() + assert my_cos_default3.obj == 'math.cos' # just string, not evaluated ``` Serializing an `ImportString` type to json is also possible. @@ -939,7 +959,7 @@ class ImportThings(BaseModel): # Create an instance - m = ImportThings(obj='math:cos') + m = ImportThings(obj='math.cos') print(m) #> obj= print(m.model_dump_json()) diff --git a/tests/test_types.py b/tests/test_types.py index 780a681a7f..4d095a1eed 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -877,6 +877,8 @@ class PyObjectModel(BaseModel): [ ('math:cos', 'math.cos', 'json'), ('math:cos', math.cos, 'python'), + ('math.cos', 'math.cos', 'json'), + ('math.cos', math.cos, 'python'), pytest.param( 'os.path', 'posixpath', 'json', marks=pytest.mark.skipif(sys.platform == 'win32', reason='different output') ), @@ -903,6 +905,22 @@ class PyObjectModel(BaseModel): assert PyObjectModel(thing=value).model_dump(mode=mode) == {'thing': expected} +@pytest.mark.parametrize( + ('value', 'validate_default', 'expected'), + [ + (math.cos, True, math.cos), + ('math:cos', True, math.cos), + (math.cos, False, math.cos), + ('math:cos', False, 'math:cos'), + ], +) +def test_string_import_default_value(value: Any, validate_default: bool, expected: Any): + class PyObjectModel(BaseModel): + thing: ImportString = Field(default=value, validate_default=validate_default) + + assert PyObjectModel().thing == expected + + @pytest.mark.parametrize('value', ['oss', 'os.os', f'{__name__}.x']) def test_string_import_any_expected_failure(value: Any): """Ensure importString correctly fails to instantiate when it's supposed to"""