Coverage for opt/mealie/lib/python3.12/site-packages/mealie/schema/cookbook/cookbook.py: 96%

68 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-11-25 17:29 +0000

1from typing import Annotated 1a

2 

3import sqlalchemy as sa 1a

4from pydantic import UUID4, ConfigDict, Field, ValidationInfo, field_validator 1a

5from slugify import slugify 1a

6from sqlalchemy.orm import joinedload 1a

7from sqlalchemy.orm.interfaces import LoaderOption 1a

8 

9from mealie.core.root_logger import get_logger 1a

10from mealie.db.models.household.cookbook import CookBook 1a

11from mealie.db.models.recipe import RecipeModel 1a

12from mealie.schema._mealie import MealieModel 1a

13from mealie.schema.response.pagination import PaginationBase 1a

14from mealie.schema.response.query_filter import QueryFilterBuilder, QueryFilterJSON 1a

15 

16logger = get_logger() 1a

17 

18 

19class CookbookHousehold(MealieModel): 1a

20 id: UUID4 1a

21 name: str 1a

22 model_config = ConfigDict(from_attributes=True) 1a

23 

24 

25class CreateCookBook(MealieModel): 1a

26 name: str 1a

27 description: str = "" 1a

28 slug: Annotated[str | None, Field(validate_default=True)] = None 1a

29 position: int = 1 1a

30 public: Annotated[bool, Field(validate_default=True)] = False 1a

31 query_filter_string: str = "" 1a

32 

33 @field_validator("public", mode="before") 1a

34 def validate_public(public: bool | None) -> bool: 1a

35 return False if public is None else public 1yABdnEoebfghijpqrCskzHtluDFmGxvwc

36 

37 @field_validator("name") 1a

38 def validate_name(name: str) -> str: 1a

39 name = name.strip() 1yABdnEoebfghijpqrCskzHtluDFmGxvwc

40 

41 # we calculate the slug later leveraging the database, 

42 # but we still need to validate the name can be slugified 

43 possible_slug = slugify(name) 1yABdnEoebfghijpqrCskzHtluDFmGxvwc

44 if not (name and possible_slug): 1yABdnEoebfghijpqrCskzHtluDFmGxvwc

45 raise ValueError("Name cannot be empty") 1yBdEebfghijskzHlmvwc

46 

47 return name 1yABdnEoebfghijpqrCskztluDFmGxvwc

48 

49 @field_validator("query_filter_string") 1a

50 def validate_query_filter_string(value: str) -> str: 1a

51 # The query filter builder does additional validations while building the 

52 # database query, so we make sure constructing the query is successful 

53 builder = QueryFilterBuilder(value) 1yBdnEoebfghijpqrsktluFmGxvwc

54 

55 try: 1yBdnEoebfghijpqrsktluFmGxvwc

56 builder.filter_query(sa.select(RecipeModel), RecipeModel) 1yBdnEoebfghijpqrsktluFmGxvwc

57 except Exception as e: 1ybc

58 raise ValueError("Invalid query filter string") from e 1ybc

59 

60 return value 1yBdnEoebfghijpqrsktluFmGxvwc

61 

62 

63class SaveCookBook(CreateCookBook): 1a

64 group_id: UUID4 1a

65 household_id: UUID4 1a

66 

67 

68class UpdateCookBook(SaveCookBook): 1a

69 id: UUID4 1a

70 

71 

72class ReadCookBook(UpdateCookBook): 1a

73 query_filter: Annotated[QueryFilterJSON, Field(validate_default=True)] = None # type: ignore 1a

74 household: CookbookHousehold | None = None 1a

75 

76 model_config = ConfigDict(from_attributes=True) 1a

77 

78 @field_validator("query_filter_string") 1a

79 def validate_query_filter_string(value: str) -> str: 1a

80 # Skip validation since we are not updating the query filter string 

81 return value 1AdnoebfghijpqrCskztluDmxvwc

82 

83 @field_validator("query_filter", mode="before") 1a

84 def validate_query_filter(cls, _, info: ValidationInfo) -> QueryFilterJSON: 1a

85 try: 1AdnoebfghijpqrCskztluDmxvwc

86 query_filter_string: str = info.data.get("query_filter_string") or "" 1AdnoebfghijpqrCskztluDmxvwc

87 builder = QueryFilterBuilder(query_filter_string) 1AdnoebfghijpqrCskztluDmxvwc

88 return builder.as_json_model() 1AdnoebfghijpqrCskztluDmxvwc

89 except Exception: 

90 logger.exception(f"Invalid query filter string: {query_filter_string}") 

91 return QueryFilterJSON() 

92 

93 @classmethod 1a

94 def loader_options(cls) -> list[LoaderOption]: 1a

95 return [joinedload(CookBook.household)] 1IJABdnoebfghijpqrCkztluDKmc

96 

97 

98class CookBookPagination(PaginationBase): 1a

99 items: list[ReadCookBook] 1a