diff --git a/sqlmodel/main.py b/sqlmodel/main.py index 300031de8b..ad3422a19a 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -3,6 +3,7 @@ import builtins import ipaddress import uuid +import warnings import weakref from collections.abc import Callable, Mapping, Sequence, Set from dataclasses import dataclass @@ -12,6 +13,7 @@ from pathlib import Path from typing import ( TYPE_CHECKING, + Annotated, Any, ClassVar, Literal, @@ -90,6 +92,13 @@ ) OnDeleteType = Literal["CASCADE", "SET NULL", "RESTRICT"] +MIN_ITEMS_DEPRECATION_MSG = ( + "`min_items` is deprecated and will be removed, use `min_length` instead" +) +MAX_ITEMS_DEPRECATION_MSG = ( + "`max_items` is deprecated and will be removed, use `max_length` instead" +) + def __dataclass_transform__( *, @@ -254,8 +263,14 @@ def Field( multiple_of: float | None = None, max_digits: int | None = None, decimal_places: int | None = None, - min_items: int | None = None, - max_items: int | None = None, + min_items: Annotated[ + int | None, + deprecated(MIN_ITEMS_DEPRECATION_MSG), + ] = None, + max_items: Annotated[ + int | None, + deprecated(MAX_ITEMS_DEPRECATION_MSG), + ] = None, unique_items: bool | None = None, min_length: int | None = None, max_length: int | None = None, @@ -297,8 +312,14 @@ def Field( multiple_of: float | None = None, max_digits: int | None = None, decimal_places: int | None = None, - min_items: int | None = None, - max_items: int | None = None, + min_items: Annotated[ + int | None, + deprecated(MIN_ITEMS_DEPRECATION_MSG), + ] = None, + max_items: Annotated[ + int | None, + deprecated(MAX_ITEMS_DEPRECATION_MSG), + ] = None, unique_items: bool | None = None, min_length: int | None = None, max_length: int | None = None, @@ -349,8 +370,14 @@ def Field( multiple_of: float | None = None, max_digits: int | None = None, decimal_places: int | None = None, - min_items: int | None = None, - max_items: int | None = None, + min_items: Annotated[ + int | None, + deprecated(MIN_ITEMS_DEPRECATION_MSG), + ] = None, + max_items: Annotated[ + int | None, + deprecated(MAX_ITEMS_DEPRECATION_MSG), + ] = None, unique_items: bool | None = None, min_length: int | None = None, max_length: int | None = None, @@ -382,8 +409,14 @@ def Field( multiple_of: float | None = None, max_digits: int | None = None, decimal_places: int | None = None, - min_items: int | None = None, - max_items: int | None = None, + min_items: Annotated[ + int | None, + deprecated(MIN_ITEMS_DEPRECATION_MSG), + ] = None, + max_items: Annotated[ + int | None, + deprecated(MAX_ITEMS_DEPRECATION_MSG), + ] = None, unique_items: bool | None = None, min_length: int | None = None, max_length: int | None = None, @@ -404,6 +437,16 @@ def Field( schema_extra: dict[str, Any] | None = None, ) -> Any: current_schema_extra = schema_extra or {} + + if min_items is not None: + warnings.warn(MIN_ITEMS_DEPRECATION_MSG, DeprecationWarning, stacklevel=2) + if min_length is None: + min_length = min_items + if max_items is not None: + warnings.warn(MAX_ITEMS_DEPRECATION_MSG, DeprecationWarning, stacklevel=2) + if max_length is None: + max_length = max_items + # Extract possible alias settings from schema_extra so we can control precedence schema_validation_alias = current_schema_extra.pop("validation_alias", None) schema_serialization_alias = current_schema_extra.pop("serialization_alias", None) @@ -421,8 +464,6 @@ def Field( "multiple_of": multiple_of, "max_digits": max_digits, "decimal_places": decimal_places, - "min_items": min_items, - "max_items": max_items, "unique_items": unique_items, "min_length": min_length, "max_length": max_length, diff --git a/tests/test_pydantic/test_field.py b/tests/test_pydantic/test_field.py index 11f4150d98..29fc1763cf 100644 --- a/tests/test_pydantic/test_field.py +++ b/tests/test_pydantic/test_field.py @@ -54,3 +54,37 @@ class Model(SQLModel): instance = Model(id=123, foo="bar") assert "foo=" not in repr(instance) + + +def test_min_items(): + with pytest.warns( + DeprecationWarning, + match="`min_items` is deprecated and will be removed, use `min_length` instead", + ): + + class Model(SQLModel): + items: list[int] = Field(min_items=2) + + Model(items=[1, 2]) + + with pytest.raises(ValidationError) as exc_info: + Model(items=[1]) + assert len(exc_info.value.errors()) == 1 + assert exc_info.value.errors()[0]["type"] == "too_short" + + +def test_max_items(): + with pytest.warns( + DeprecationWarning, + match="`max_items` is deprecated and will be removed, use `max_length` instead", + ): + + class Model(SQLModel): + items: list[int] = Field(max_items=2) + + Model(items=[1, 2]) + + with pytest.raises(ValidationError) as exc_info: + Model(items=[1, 2, 3]) + assert len(exc_info.value.errors()) == 1 + assert exc_info.value.errors()[0]["type"] == "too_long"