diff --git a/tests/test_main.py b/tests/test_main.py index df08c7ceba2..c3ce4dba649 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1443,45 +1443,63 @@ class M(BaseModel): get_type_hints(M.__config__) +_5 = 5 + + +def _annotated(*args): + return Annotated[args] if Annotated else None + + @pytest.mark.skipif(not Annotated, reason='typing_extensions not installed') @pytest.mark.parametrize( - ['annotated_extras', 'value', 'subclass_ctx', 'empty_init_ctx'], + ['hint', 'value', 'subclass_ctx', 'empty_init_ctx'], [ # Test non-pydantic Annotated uses (random metadata) - ([0], Undefined, nullcontext(), pytest.raises(ValueError, match='field required')), - ([0], 5, nullcontext(), nullcontext()), - ([0], Field(default=5), nullcontext(), nullcontext()), - ([0], Field(default=5, ge=0), nullcontext(), nullcontext()), - # Now, test Field in Annotated - ([Field(...)], Undefined, nullcontext(), pytest.raises(ValueError, match='field required')), - ([Field(default=5)], Undefined, nullcontext(), nullcontext()), - ([Field(default=5, ge=0)], Undefined, nullcontext(), nullcontext()), - # Now, test multiple Field specifications (model errors) + (_annotated(int, 0), Undefined, None, pytest.raises(ValueError, match='field required')), + (_annotated(int, 0), _5, None, None), + (_annotated(int, 0), Field(default=_5), None, None), + (_annotated(int, 0), Field(default=_5, ge=0), None, None), + # Test Field in Annotated + (_annotated(int, Field(...)), Undefined, None, pytest.raises(ValueError, match='field required')), + (_annotated(int, Field(default=_5)), Undefined, None, None), + (_annotated(int, Field(default=_5, ge=0)), Undefined, None, None), + # Test default value configurations + (_annotated(int, Field(...)), _5, None, None), + ( + _annotated(int, Field(_5)), + _5, + pytest.raises(ValueError, match='`Field` default cannot be set in `Annotated`'), + None, + ), + # Test multiple Field specifications (model errors) ( - [Field(...), Field(...)], + _annotated(int, Field(...), Field(...)), Undefined, pytest.raises(ValueError, match='cannot specify multiple `Annotated` `Field`s'), - nullcontext(), + None, ), ( - [Field(...)], + _annotated(int, Field(...)), Field(...), pytest.raises(ValueError, match='cannot specify `Annotated` and value `Field`'), - nullcontext(), + None, ), ], ) -def test_annotated(annotated_extras, value, subclass_ctx, empty_init_ctx): - M, hint = None, Annotated[(int, *annotated_extras)] - with subclass_ctx: +def test_annotated(hint, value, subclass_ctx, empty_init_ctx): + if hint is None: + pytest.skip('typing_extensions not installed') + + M = None + with (subclass_ctx or nullcontext()): class M(BaseModel): x: hint = value if M is None: return - with empty_init_ctx: - assert M().x == 5 # Defaults above are 5 + with (empty_init_ctx or nullcontext()): + assert M().x == _5 assert M(x=10).x == 10 # get_type_hints doesn't recognize typing_extensions.Annotated, so will return the full @@ -1492,3 +1510,33 @@ class M(BaseModel): assert get_type_hints(M, include_extras=True)['x'] == hint else: assert get_type_hints(M)['x'] == hint + + +@pytest.mark.parametrize( + ['hint', 'value', 'subclass_ctx'], + [ + # Test Field value with different calling conventions for default + (int, Field(_5), None), + (int, Field(default=_5), None), + (Annotated[int, Field()], _5, None), + # Test Field default error cases + ( + Annotated[int, Field(_5)], + Undefined, + pytest.raises(ValueError, match='`Field` default cannot be set in `Annotated`'), + ), + ], +) +def test_field_defaults(hint, value, subclass_ctx): + if hint is None: + pytest.skip('typing_extensions not installed') + + M = None + with (subclass_ctx or nullcontext()): + + class M(BaseModel): + x: hint = value + + if M is None: + return + assert M().x == _5