diff --git a/mealie/db/models/_model_base.py b/mealie/db/models/_model_base.py index 00da8ece6..db409b66e 100644 --- a/mealie/db/models/_model_base.py +++ b/mealie/db/models/_model_base.py @@ -1,6 +1,6 @@ import string from datetime import datetime -from typing import Annotated, ClassVar +from typing import Annotated, ClassVar, get_origin from sqlalchemy import Integer from sqlalchemy.orm import DeclarativeBase, Mapped, declared_attr, mapped_column, synonym @@ -19,9 +19,17 @@ class PrivateColumn[T]: """ Drop-in replacement for `Mapped[]` that marks a column as private. Private columns cannot be used in query filter expressions. + + Only valid on scalar column fields. Using it on a relationship type (e.g. `list[Model]`) + will raise a `TypeError` at class definition time. """ def __class_getitem__(cls, item: type) -> type: + if get_origin(item) is list or item is list: + raise TypeError( + f"PrivateColumn cannot be used on relationship fields (got {item!r}). " + "Annotate the related model's scalar column directly instead." + ) return Mapped[Annotated[item, mapped_column(info={"private": True})]] diff --git a/tests/unit_tests/repository_tests/test_query_filter_builder.py b/tests/unit_tests/repository_tests/test_query_filter_builder.py index 0f60d86bd..09799049e 100644 --- a/tests/unit_tests/repository_tests/test_query_filter_builder.py +++ b/tests/unit_tests/repository_tests/test_query_filter_builder.py @@ -1,6 +1,7 @@ import pytest import sqlalchemy as sa +from mealie.db.models._model_base import PrivateColumn from mealie.db.models.recipe.recipe import RecipeModel from mealie.db.models.users.users import LongLiveToken, User from mealie.services.query_filter.builder import ( @@ -87,6 +88,12 @@ def test_query_filter_builder_json_uses_raw_value(): # --------------------------------------------------------------------------- +def test_private_column_rejects_list_type(): + """PrivateColumn[list[X]] must raise TypeError at definition time to prevent misuse on relationships.""" + with pytest.raises(TypeError, match="relationship"): + PrivateColumn[list[User]] + + def test_private_field_user_password_raises(): """Filtering on User.password (PrivateColumn) should raise ValueError.""" with pytest.raises(ValueError, match="private field"):