217 lines
7.6 KiB
Python
217 lines
7.6 KiB
Python
"""Pydantic models for Cookbook app responses."""
|
|
|
|
from typing import List, Optional, Union
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
from .base import BaseResponse, IdResponse, StatusResponse
|
|
|
|
|
|
class Nutrition(BaseModel):
|
|
"""Nutrition information following schema.org/NutritionInformation."""
|
|
|
|
type: str = Field(
|
|
default="NutritionInformation",
|
|
alias="@type",
|
|
description="Schema.org object type",
|
|
)
|
|
calories: Optional[str] = Field(None, description="Calories (e.g., '650 kcal')")
|
|
carbohydrateContent: Optional[str] = Field(
|
|
None, description="Carbohydrates (e.g., '300 g')"
|
|
)
|
|
cholesterolContent: Optional[str] = Field(
|
|
None, description="Cholesterol (e.g., '10 g')"
|
|
)
|
|
fatContent: Optional[str] = Field(None, description="Fat (e.g., '45 g')")
|
|
fiberContent: Optional[str] = Field(None, description="Fiber (e.g., '50 g')")
|
|
proteinContent: Optional[str] = Field(None, description="Protein (e.g., '80 g')")
|
|
saturatedFatContent: Optional[str] = Field(
|
|
None, description="Saturated fat (e.g., '5 g')"
|
|
)
|
|
servingSize: Optional[str] = Field(
|
|
None, description="Serving size description (e.g., 'One plate')"
|
|
)
|
|
sodiumContent: Optional[str] = Field(None, description="Sodium (e.g., '10 mg')")
|
|
sugarContent: Optional[str] = Field(None, description="Sugar (e.g., '5 g')")
|
|
transFatContent: Optional[str] = Field(None, description="Trans fat (e.g., '10 g')")
|
|
unsaturatedFatContent: Optional[str] = Field(
|
|
None, description="Unsaturated fat (e.g., '40 g')"
|
|
)
|
|
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class RecipeStub(BaseModel):
|
|
"""Stub of a recipe with basic information."""
|
|
|
|
id: str = Field(description="Recipe ID as string")
|
|
recipe_id: int = Field(description="Recipe ID as integer (deprecated)")
|
|
name: str = Field(description="Recipe name")
|
|
keywords: Optional[str] = Field(default="", description="Comma-separated keywords")
|
|
dateCreated: str = Field(description="Creation date (ISO8601)")
|
|
dateModified: Optional[str] = Field(
|
|
None, description="Last modified date (ISO8601)"
|
|
)
|
|
imageUrl: str = Field(default="", description="URL of the recipe image")
|
|
imagePlaceholderUrl: str = Field(default="", description="URL of placeholder image")
|
|
|
|
|
|
class Recipe(BaseModel):
|
|
"""Full recipe following schema.org/Recipe specification."""
|
|
|
|
type: str = Field(default="Recipe", alias="@type", description="Schema.org type")
|
|
id: Optional[str] = Field(None, description="Recipe ID")
|
|
name: str = Field(description="Recipe name")
|
|
description: str = Field(default="", description="Recipe description")
|
|
url: str = Field(default="", description="Original recipe URL")
|
|
image: str = Field(default="", description="URL of original recipe image")
|
|
imageUrl: Optional[str] = Field(
|
|
None, description="URL of the recipe image in Nextcloud"
|
|
)
|
|
imagePlaceholderUrl: Optional[str] = Field(
|
|
None, description="URL of placeholder image"
|
|
)
|
|
keywords: str = Field(default="", description="Comma-separated keywords")
|
|
dateCreated: Optional[str] = Field(None, description="Creation date (ISO8601)")
|
|
dateModified: Optional[str] = Field(
|
|
None, description="Last modified date (ISO8601)"
|
|
)
|
|
prepTime: Optional[str] = Field(None, description="Preparation time (ISO8601)")
|
|
cookTime: Optional[str] = Field(None, description="Cooking time (ISO8601)")
|
|
totalTime: Optional[str] = Field(None, description="Total time (ISO8601)")
|
|
recipeYield: Union[int, str] = Field(default=1, description="Number of servings")
|
|
recipeCategory: str = Field(default="", description="Recipe category")
|
|
tool: List[str] = Field(default_factory=list, description="Required tools")
|
|
recipeIngredient: List[str] = Field(
|
|
default_factory=list, description="List of ingredients"
|
|
)
|
|
recipeInstructions: List[str] = Field(
|
|
default_factory=list, description="Cooking instructions"
|
|
)
|
|
nutrition: Optional[Nutrition] = Field(None, description="Nutrition information")
|
|
|
|
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
|
|
|
|
class Category(BaseModel):
|
|
"""A recipe category."""
|
|
|
|
name: str = Field(description="Category name")
|
|
recipe_count: int = Field(description="Number of recipes in category")
|
|
|
|
|
|
class Keyword(BaseModel):
|
|
"""A recipe keyword/tag."""
|
|
|
|
name: str = Field(description="Keyword name")
|
|
recipe_count: int = Field(description="Number of recipes with this keyword")
|
|
|
|
|
|
class VisibleInfoBlocks(BaseModel):
|
|
"""Configuration for visible information blocks in the UI."""
|
|
|
|
preparation_time: Optional[bool] = Field(
|
|
None, alias="preparation-time", description="Show preparation time"
|
|
)
|
|
cooking_time: Optional[bool] = Field(
|
|
None, alias="cooking-time", description="Show cooking time"
|
|
)
|
|
total_time: Optional[bool] = Field(
|
|
None, alias="total-time", description="Show total time"
|
|
)
|
|
nutrition_information: Optional[bool] = Field(
|
|
None, alias="nutrition-information", description="Show nutrition info"
|
|
)
|
|
tools: Optional[bool] = Field(None, description="Show tools list")
|
|
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class CookbookConfig(BaseModel):
|
|
"""Cookbook app configuration."""
|
|
|
|
folder: Optional[str] = Field(None, description="Recipe folder path")
|
|
update_interval: Optional[int] = Field(
|
|
None, description="Auto-rescan interval in minutes"
|
|
)
|
|
print_image: Optional[bool] = Field(None, description="Print images with recipes")
|
|
visibleInfoBlocks: Optional[VisibleInfoBlocks] = Field(
|
|
None, description="Visible info blocks configuration"
|
|
)
|
|
|
|
|
|
class APIVersion(BaseModel):
|
|
"""API version information."""
|
|
|
|
epoch: int = Field(description="API epoch")
|
|
major: int = Field(description="Major version")
|
|
minor: int = Field(description="Minor version")
|
|
|
|
|
|
class Version(BaseModel):
|
|
"""Version information for Cookbook app and API."""
|
|
|
|
cookbook_version: List[int] = Field(description="Cookbook app version")
|
|
api_version: APIVersion = Field(description="API version")
|
|
|
|
|
|
# Response models for MCP tools
|
|
|
|
|
|
class ImportRecipeResponse(BaseResponse):
|
|
"""Response model for recipe import."""
|
|
|
|
recipe: Recipe = Field(description="The imported recipe")
|
|
recipe_id: str = Field(description="ID of the imported recipe")
|
|
|
|
|
|
class CreateRecipeResponse(IdResponse):
|
|
"""Response model for recipe creation."""
|
|
|
|
pass
|
|
|
|
|
|
class UpdateRecipeResponse(IdResponse):
|
|
"""Response model for recipe update."""
|
|
|
|
pass
|
|
|
|
|
|
class DeleteRecipeResponse(StatusResponse):
|
|
"""Response model for recipe deletion."""
|
|
|
|
deleted_id: int = Field(description="ID of deleted recipe")
|
|
|
|
|
|
class ListRecipesResponse(BaseResponse):
|
|
"""Response model for listing recipes."""
|
|
|
|
recipes: List[RecipeStub] = Field(description="List of recipe stubs")
|
|
total_count: int = Field(description="Total number of recipes")
|
|
|
|
|
|
class SearchRecipesResponse(BaseResponse):
|
|
"""Response model for recipe search."""
|
|
|
|
recipes: List[RecipeStub] = Field(description="Matching recipes")
|
|
query: str = Field(description="Search query used")
|
|
total_found: int = Field(description="Number of recipes found")
|
|
|
|
|
|
class ListCategoriesResponse(BaseResponse):
|
|
"""Response model for listing categories."""
|
|
|
|
categories: List[Category] = Field(description="List of categories")
|
|
|
|
|
|
class ListKeywordsResponse(BaseResponse):
|
|
"""Response model for listing keywords."""
|
|
|
|
keywords: List[Keyword] = Field(description="List of keywords")
|
|
|
|
|
|
class ReindexResponse(StatusResponse):
|
|
"""Response model for reindex operation."""
|
|
|
|
pass
|