diff --git a/frontend/composables/recipe-page/use-extract-recipe-yield.test.ts b/frontend/composables/recipe-page/use-extract-recipe-yield.test.ts
deleted file mode 100644
index 3bc8e7996..000000000
--- a/frontend/composables/recipe-page/use-extract-recipe-yield.test.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import { describe, expect, test } from "vitest";
-import { useExtractRecipeYield } from "./use-extract-recipe-yield";
-
-describe("test use extract recipe yield", () => {
- test("when text empty return empty", () => {
- const result = useExtractRecipeYield(null, 1);
- expect(result).toStrictEqual("");
- });
-
- test("when text matches nothing return text", () => {
- const val = "this won't match anything";
- const result = useExtractRecipeYield(val, 1);
- expect(result).toStrictEqual(val);
-
- const resultScaled = useExtractRecipeYield(val, 5);
- expect(resultScaled).toStrictEqual(val);
- });
-
- test("when text matches a mixed fraction, return a scaled fraction", () => {
- const val = "10 1/2 units";
- const result = useExtractRecipeYield(val, 1);
- expect(result).toStrictEqual(val);
-
- const resultScaled = useExtractRecipeYield(val, 3);
- expect(resultScaled).toStrictEqual("31 1/2 units");
-
- const resultScaledPartial = useExtractRecipeYield(val, 2.5);
- expect(resultScaledPartial).toStrictEqual("26 1/4 units");
-
- const resultScaledInt = useExtractRecipeYield(val, 4);
- expect(resultScaledInt).toStrictEqual("42 units");
- });
-
- test("when text matches a fraction, return a scaled fraction", () => {
- const val = "1/3 plates";
- const result = useExtractRecipeYield(val, 1);
- expect(result).toStrictEqual(val);
-
- const resultScaled = useExtractRecipeYield(val, 2);
- expect(resultScaled).toStrictEqual("2/3 plates");
-
- const resultScaledInt = useExtractRecipeYield(val, 3);
- expect(resultScaledInt).toStrictEqual("1 plates");
-
- const resultScaledPartial = useExtractRecipeYield(val, 2.5);
- expect(resultScaledPartial).toStrictEqual("5/6 plates");
-
- const resultScaledMixed = useExtractRecipeYield(val, 4);
- expect(resultScaledMixed).toStrictEqual("1 1/3 plates");
- });
-
- test("when text matches a decimal, return a scaled, rounded decimal", () => {
- const val = "1.25 parts";
- const result = useExtractRecipeYield(val, 1);
- expect(result).toStrictEqual(val);
-
- const resultScaled = useExtractRecipeYield(val, 2);
- expect(resultScaled).toStrictEqual("2.5 parts");
-
- const resultScaledInt = useExtractRecipeYield(val, 4);
- expect(resultScaledInt).toStrictEqual("5 parts");
-
- const resultScaledPartial = useExtractRecipeYield(val, 2.5);
- expect(resultScaledPartial).toStrictEqual("3.125 parts");
-
- const roundedVal = "1.33333333333333333333 parts";
- const resultScaledRounded = useExtractRecipeYield(roundedVal, 2);
- expect(resultScaledRounded).toStrictEqual("2.667 parts");
- });
-
- test("when text matches an int, return a scaled int", () => {
- const val = "5 bowls";
- const result = useExtractRecipeYield(val, 1);
- expect(result).toStrictEqual(val);
-
- const resultScaled = useExtractRecipeYield(val, 2);
- expect(resultScaled).toStrictEqual("10 bowls");
-
- const resultScaledPartial = useExtractRecipeYield(val, 2.5);
- expect(resultScaledPartial).toStrictEqual("12.5 bowls");
-
- const resultScaledLarge = useExtractRecipeYield(val, 10);
- expect(resultScaledLarge).toStrictEqual("50 bowls");
- });
-
- test("when text contains an invalid fraction, return the original string", () => {
- const valDivZero = "3/0 servings";
- const resultDivZero = useExtractRecipeYield(valDivZero, 3);
- expect(resultDivZero).toStrictEqual(valDivZero);
-
- const valDivZeroMixed = "2 4/0 servings";
- const resultDivZeroMixed = useExtractRecipeYield(valDivZeroMixed, 6);
- expect(resultDivZeroMixed).toStrictEqual(valDivZeroMixed);
- });
-
- test("when text contains a weird or small fraction, return the original string", () => {
- const valWeird = "2323231239087/134527431962272135 servings";
- const resultWeird = useExtractRecipeYield(valWeird, 5);
- expect(resultWeird).toStrictEqual(valWeird);
-
- const valSmall = "1/20230225 lovable servings";
- const resultSmall = useExtractRecipeYield(valSmall, 12);
- expect(resultSmall).toStrictEqual(valSmall);
- });
-
- test("when text contains multiple numbers, the first is parsed as the servings amount", () => {
- const val = "100 sets of 55 bowls";
- const result = useExtractRecipeYield(val, 3);
- expect(result).toStrictEqual("300 sets of 55 bowls");
- })
-});
diff --git a/frontend/composables/recipe-page/use-extract-recipe-yield.ts b/frontend/composables/recipe-page/use-extract-recipe-yield.ts
deleted file mode 100644
index bec7d6585..000000000
--- a/frontend/composables/recipe-page/use-extract-recipe-yield.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-import { useFraction } from "~/composables/recipes";
-
-const matchMixedFraction = /(?:\d*\s\d*\d*|0)\/\d*\d*/;
-const matchFraction = /(?:\d*\d*|0)\/\d*\d*/;
-const matchDecimal = /(\d+.\d+)|(.\d+)/;
-const matchInt = /\d+/;
-
-
-
-function extractServingsFromMixedFraction(fractionString: string): number | undefined {
- const mixedSplit = fractionString.split(/\s/);
- const wholeNumber = parseInt(mixedSplit[0]);
- const fraction = mixedSplit[1];
-
- const fractionSplit = fraction.split("/");
- const numerator = parseInt(fractionSplit[0]);
- const denominator = parseInt(fractionSplit[1]);
-
- if (denominator === 0) {
- return undefined; // if the denominator is zero, just give up
- }
- else {
- return wholeNumber + (numerator / denominator);
- }
-}
-
-function extractServingsFromFraction(fractionString: string): number | undefined {
- const fractionSplit = fractionString.split("/");
- const numerator = parseInt(fractionSplit[0]);
- const denominator = parseInt(fractionSplit[1]);
-
- if (denominator === 0) {
- return undefined; // if the denominator is zero, just give up
- }
- else {
- return numerator / denominator;
- }
-}
-
-
-
-export function findMatch(yieldString: string): [matchString: string, servings: number, isFraction: boolean] | null {
- if (!yieldString) {
- return null;
- }
-
- const mixedFractionMatch = yieldString.match(matchMixedFraction);
- if (mixedFractionMatch?.length) {
- const match = mixedFractionMatch[0];
- const servings = extractServingsFromMixedFraction(match);
-
- // if the denominator is zero, return no match
- if (servings === undefined) {
- return null;
- } else {
- return [match, servings, true];
- }
- }
-
- const fractionMatch = yieldString.match(matchFraction);
- if (fractionMatch?.length) {
- const match = fractionMatch[0]
- const servings = extractServingsFromFraction(match);
-
- // if the denominator is zero, return no match
- if (servings === undefined) {
- return null;
- } else {
- return [match, servings, true];
- }
- }
-
- const decimalMatch = yieldString.match(matchDecimal);
- if (decimalMatch?.length) {
- const match = decimalMatch[0];
- return [match, parseFloat(match), false];
- }
-
- const intMatch = yieldString.match(matchInt);
- if (intMatch?.length) {
- const match = intMatch[0];
- return [match, parseInt(match), false];
- }
-
- return null;
-}
-
-function formatServings(servings: number, scale: number, isFraction: boolean): string {
- const val = servings * scale;
- if (Number.isInteger(val)) {
- return val.toString();
- } else if (!isFraction) {
- return (Math.round(val * 1000) / 1000).toString();
- }
-
- // convert val into a fraction string
- const { frac } = useFraction();
-
- let valString = "";
- const fraction = frac(val, 10, true);
-
- if (fraction[0] !== undefined && fraction[0] > 0) {
- valString += fraction[0];
- }
-
- if (fraction[1] > 0) {
- valString += ` ${fraction[1]}/${fraction[2]}`;
- }
-
- return valString.trim();
-}
-
-
-export function useExtractRecipeYield(yieldString: string | null, scale: number): string {
- if (!yieldString) {
- return "";
- }
-
- const match = findMatch(yieldString);
- if (!match) {
- return yieldString;
- }
-
- const [matchString, servings, isFraction] = match;
-
- const formattedServings = formatServings(servings, scale, isFraction);
- if (!formattedServings) {
- return yieldString // this only happens with very weird or small fractions
- } else {
- return yieldString.replace(matchString, formatServings(servings, scale, isFraction));
- }
-}
diff --git a/frontend/composables/recipes/use-scaled-amount.test.ts b/frontend/composables/recipes/use-scaled-amount.test.ts
new file mode 100644
index 000000000..f3f0cdae5
--- /dev/null
+++ b/frontend/composables/recipes/use-scaled-amount.test.ts
@@ -0,0 +1,68 @@
+import { describe, expect, test } from "vitest";
+import { useScaledAmount } from "./use-scaled-amount";
+
+describe("test use recipe yield", () => {
+ function asFrac(numerator: number, denominator: number): string {
+ return `
${numerator}⁄${denominator}`;
+ }
+
+ test("base case", () => {
+ const { scaledAmount, scaledAmountDisplay } = useScaledAmount(3);
+ expect(scaledAmount).toStrictEqual(3);
+ expect(scaledAmountDisplay).toStrictEqual("3");
+ });
+
+ test("base case scaled", () => {
+ const { scaledAmount, scaledAmountDisplay } = useScaledAmount(3, 2);
+ expect(scaledAmount).toStrictEqual(6);
+ expect(scaledAmountDisplay).toStrictEqual("6");
+ });
+
+ test("zero scale", () => {
+ const { scaledAmount, scaledAmountDisplay } = useScaledAmount(3, 0);
+ expect(scaledAmount).toStrictEqual(0);
+ expect(scaledAmountDisplay).toStrictEqual("");
+ });
+
+ test("zero quantity", () => {
+ const { scaledAmount, scaledAmountDisplay } = useScaledAmount(0);
+ expect(scaledAmount).toStrictEqual(0);
+ expect(scaledAmountDisplay).toStrictEqual("");
+ });
+
+ test("basic fraction", () => {
+ const { scaledAmount, scaledAmountDisplay } = useScaledAmount(0.5);
+ expect(scaledAmount).toStrictEqual(0.5);
+ expect(scaledAmountDisplay).toStrictEqual(asFrac(1, 2));
+ });
+
+ test("mixed fraction", () => {
+ const { scaledAmount, scaledAmountDisplay } = useScaledAmount(1.5);
+ expect(scaledAmount).toStrictEqual(1.5);
+ expect(scaledAmountDisplay).toStrictEqual(`1${asFrac(1, 2)}`);
+ });
+
+ test("mixed fraction scaled", () => {
+ const { scaledAmount, scaledAmountDisplay } = useScaledAmount(1.5, 9);
+ expect(scaledAmount).toStrictEqual(13.5);
+ expect(scaledAmountDisplay).toStrictEqual(`13${asFrac(1, 2)}`);
+ });
+
+ test("small scale", () => {
+ const { scaledAmount, scaledAmountDisplay } = useScaledAmount(1, 0.125);
+ expect(scaledAmount).toStrictEqual(0.125);
+ expect(scaledAmountDisplay).toStrictEqual(asFrac(1, 8));
+ });
+
+ test("small qty", () => {
+ const { scaledAmount, scaledAmountDisplay } = useScaledAmount(0.125);
+ expect(scaledAmount).toStrictEqual(0.125);
+ expect(scaledAmountDisplay).toStrictEqual(asFrac(1, 8));
+ });
+
+ test("rounded decimal", () => {
+ const { scaledAmount, scaledAmountDisplay } = useScaledAmount(1.3344559997);
+ expect(scaledAmount).toStrictEqual(1.334);
+ expect(scaledAmountDisplay).toStrictEqual(`1${asFrac(1, 3)}`);
+ });
+});
diff --git a/frontend/composables/recipes/use-scaled-amount.ts b/frontend/composables/recipes/use-scaled-amount.ts
new file mode 100644
index 000000000..1f642e91d
--- /dev/null
+++ b/frontend/composables/recipes/use-scaled-amount.ts
@@ -0,0 +1,32 @@
+import { useFraction } from "~/composables/recipes";
+
+function formatQuantity(val: number): string {
+ if (Number.isInteger(val)) {
+ return val.toString();
+ }
+
+ const { frac } = useFraction();
+
+ let valString = "";
+ const fraction = frac(val, 10, true);
+
+ if (fraction[0] !== undefined && fraction[0] > 0) {
+ valString += fraction[0];
+ }
+
+ if (fraction[1] > 0) {
+ valString += `
${fraction[1]}⁄${fraction[2]}`;
+ }
+
+ return valString.trim();
+}
+
+export function useScaledAmount(amount: number, scale = 1) {
+ const scaledAmount = Number(((amount || 0) * scale).toFixed(3));
+ const scaledAmountDisplay = scaledAmount ? formatQuantity(scaledAmount) : "";
+
+ return {
+ scaledAmount,
+ scaledAmountDisplay,
+ };
+}
diff --git a/frontend/lang/messages/en-US.json b/frontend/lang/messages/en-US.json
index 9b5c04f41..b40fcc94e 100644
--- a/frontend/lang/messages/en-US.json
+++ b/frontend/lang/messages/en-US.json
@@ -517,6 +517,7 @@
"save-recipe-before-use": "Save recipe before use",
"section-title": "Section Title",
"servings": "Servings",
+ "serves-amount": "Serves {amount}",
"share-recipe-message": "I wanted to share my {0} recipe with you.",
"show-nutrition-values": "Show Nutrition Values",
"sodium-content": "Sodium",
@@ -545,6 +546,8 @@
"failed-to-add-recipe-to-mealplan": "Failed to add recipe to mealplan",
"failed-to-add-to-list": "Failed to add to list",
"yield": "Yield",
+ "yields-amount-with-text": "Yields {amount} {text}",
+ "yield-text": "Yield Text",
"quantity": "Quantity",
"choose-unit": "Choose Unit",
"press-enter-to-create": "Press Enter to Create",
@@ -640,7 +643,9 @@
"recipe-debugger-use-openai-description": "Use OpenAI to parse the results instead of relying on the scraper library. When creating a recipe via URL, this is done automatically if the scraper library fails, but you may test it manually here.",
"debug": "Debug",
"tree-view": "Tree View",
+ "recipe-servings": "Recipe Servings",
"recipe-yield": "Recipe Yield",
+ "recipe-yield-text": "Recipe Yield Text",
"unit": "Unit",
"upload-image": "Upload image",
"screen-awake": "Keep Screen Awake",
diff --git a/frontend/lib/api/types/admin.ts b/frontend/lib/api/types/admin.ts
index 7b40f6a84..79678aed5 100644
--- a/frontend/lib/api/types/admin.ts
+++ b/frontend/lib/api/types/admin.ts
@@ -126,6 +126,8 @@ export interface RecipeSummary {
name?: string | null;
slug?: string;
image?: unknown;
+ recipeServings?: number;
+ recipeYieldQuantity?: number;
recipeYield?: string | null;
totalTime?: string | null;
prepTime?: string | null;
diff --git a/frontend/lib/api/types/cookbook.ts b/frontend/lib/api/types/cookbook.ts
index e43e48bcb..721e6320f 100644
--- a/frontend/lib/api/types/cookbook.ts
+++ b/frontend/lib/api/types/cookbook.ts
@@ -62,6 +62,8 @@ export interface RecipeSummary {
name?: string | null;
slug?: string;
image?: unknown;
+ recipeServings?: number;
+ recipeYieldQuantity?: number;
recipeYield?: string | null;
totalTime?: string | null;
prepTime?: string | null;
diff --git a/frontend/lib/api/types/meal-plan.ts b/frontend/lib/api/types/meal-plan.ts
index ae033c1f4..f11ec1feb 100644
--- a/frontend/lib/api/types/meal-plan.ts
+++ b/frontend/lib/api/types/meal-plan.ts
@@ -87,6 +87,8 @@ export interface RecipeSummary {
name?: string | null;
slug?: string;
image?: unknown;
+ recipeServings?: number;
+ recipeYieldQuantity?: number;
recipeYield?: string | null;
totalTime?: string | null;
prepTime?: string | null;
diff --git a/frontend/lib/api/types/recipe.ts b/frontend/lib/api/types/recipe.ts
index 72672dc15..ed0c7ab00 100644
--- a/frontend/lib/api/types/recipe.ts
+++ b/frontend/lib/api/types/recipe.ts
@@ -230,6 +230,8 @@ export interface Recipe {
name?: string | null;
slug?: string;
image?: unknown;
+ recipeServings?: number;
+ recipeYieldQuantity?: number;
recipeYield?: string | null;
totalTime?: string | null;
prepTime?: string | null;
@@ -307,6 +309,8 @@ export interface RecipeSummary {
name?: string | null;
slug?: string;
image?: unknown;
+ recipeServings?: number;
+ recipeYieldQuantity?: number;
recipeYield?: string | null;
totalTime?: string | null;
prepTime?: string | null;
diff --git a/frontend/pages/group/data/recipes.vue b/frontend/pages/group/data/recipes.vue
index 94d2f245b..37bc47760 100644
--- a/frontend/pages/group/data/recipes.vue
+++ b/frontend/pages/group/data/recipes.vue
@@ -218,6 +218,8 @@ export default defineComponent({
tags: true,
tools: true,
categories: true,
+ recipeServings: false,
+ recipeYieldQuantity: false,
recipeYield: false,
dateAdded: false,
});
@@ -228,7 +230,9 @@ export default defineComponent({
tags: i18n.t("tag.tags"),
categories: i18n.t("recipe.categories"),
tools: i18n.t("tool.tools"),
- recipeYield: i18n.t("recipe.recipe-yield"),
+ recipeServings: i18n.t("recipe.recipe-servings"),
+ recipeYieldQuantity: i18n.t("recipe.recipe-yield"),
+ recipeYield: i18n.t("recipe.recipe-yield-text"),
dateAdded: i18n.t("general.date-added"),
};
diff --git a/mealie/db/models/recipe/recipe.py b/mealie/db/models/recipe/recipe.py
index fc9c142bb..ebbb0bfc9 100644
--- a/mealie/db/models/recipe/recipe.py
+++ b/mealie/db/models/recipe/recipe.py
@@ -89,7 +89,8 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
cook_time: Mapped[str | None] = mapped_column(sa.String)
recipe_yield: Mapped[str | None] = mapped_column(sa.String)
- recipeCuisine: Mapped[str | None] = mapped_column(sa.String)
+ recipe_yield_quantity: Mapped[float] = mapped_column(sa.Float, index=True, default=0)
+ recipe_servings: Mapped[float] = mapped_column(sa.Float, index=True, default=0)
assets: Mapped[list[RecipeAsset]] = orm.relationship("RecipeAsset", cascade="all, delete-orphan")
nutrition: Mapped[Nutrition] = orm.relationship("Nutrition", uselist=False, cascade="all, delete-orphan")
@@ -131,7 +132,6 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
notes: Mapped[list[Note]] = orm.relationship("Note", cascade="all, delete-orphan")
org_url: Mapped[str | None] = mapped_column(sa.String)
extras: Mapped[list[ApiExtras]] = orm.relationship("ApiExtras", cascade="all, delete-orphan")
- is_ocr_recipe: Mapped[bool | None] = mapped_column(sa.Boolean, default=False)
# Time Stamp Properties
date_added: Mapped[date | None] = mapped_column(sa.Date, default=get_utc_today)
@@ -167,6 +167,10 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
},
)
+ # Deprecated
+ recipeCuisine: Mapped[str | None] = mapped_column(sa.String)
+ is_ocr_recipe: Mapped[bool | None] = mapped_column(sa.Boolean, default=False)
+
@validates("name")
def validate_name(self, _, name):
assert name != ""
diff --git a/mealie/lang/messages/en-US.json b/mealie/lang/messages/en-US.json
index ad7c43af4..e5d8c10ce 100644
--- a/mealie/lang/messages/en-US.json
+++ b/mealie/lang/messages/en-US.json
@@ -8,6 +8,14 @@
"recipe-defaults": {
"ingredient-note": "1 Cup Flour",
"step-text": "Recipe steps as well as other fields in the recipe page support markdown syntax.\n\n**Add a link**\n\n[My Link](https://demo.mealie.io)\n"
+ },
+ "servings-text": {
+ "makes": "Makes",
+ "serves": "Serves",
+ "serving": "Serving",
+ "servings": "Servings",
+ "yield": "Yield",
+ "yields": "Yields"
}
},
"mealplan": {
diff --git a/mealie/lang/providers.py b/mealie/lang/providers.py
index b263e6564..106a89403 100644
--- a/mealie/lang/providers.py
+++ b/mealie/lang/providers.py
@@ -29,3 +29,9 @@ def local_provider(accept_language: str | None = Header(None)) -> Translator:
factory = _load_factory()
accept_language = accept_language or "en-US"
return factory.get(accept_language)
+
+
+@lru_cache
+def get_all_translations(key: str) -> dict[str, str]:
+ factory = _load_factory()
+ return {locale: factory.get(locale).t(key) for locale in factory.supported_locales}
diff --git a/mealie/pkgs/i18n/provider_factory.py b/mealie/pkgs/i18n/provider_factory.py
index 81cb7fa65..5324126a0 100644
--- a/mealie/pkgs/i18n/provider_factory.py
+++ b/mealie/pkgs/i18n/provider_factory.py
@@ -1,4 +1,5 @@
from dataclasses import dataclass, field
+from functools import cached_property
from pathlib import Path
from .json_provider import JsonProvider
@@ -10,7 +11,7 @@ class InUseProvider:
locks: int
-@dataclass(slots=True)
+@dataclass
class ProviderFactory:
directory: Path
fallback_locale: str = "en-US"
@@ -22,6 +23,10 @@ class ProviderFactory:
def fallback_file(self) -> Path:
return self.directory / self.filename_format.format(locale=self.fallback_locale, format="json")
+ @cached_property
+ def supported_locales(self) -> list[str]:
+ return [path.stem for path in self.directory.glob(self.filename_format.format(locale="*", format="json"))]
+
def _load(self, locale: str) -> JsonProvider:
filename = self.filename_format.format(locale=locale, format="json")
path = self.directory / filename
diff --git a/mealie/routes/spa/__init__.py b/mealie/routes/spa/__init__.py
index d4c21e4c0..824764b6e 100644
--- a/mealie/routes/spa/__init__.py
+++ b/mealie/routes/spa/__init__.py
@@ -116,7 +116,7 @@ def content_with_meta(group_slug: str, recipe: Recipe) -> str:
"prepTime": recipe.prep_time,
"cookTime": recipe.cook_time,
"totalTime": recipe.total_time,
- "recipeYield": recipe.recipe_yield,
+ "recipeYield": recipe.recipe_yield_display,
"recipeIngredient": ingredients,
"recipeInstructions": [i.text for i in recipe.recipe_instructions] if recipe.recipe_instructions else [],
"recipeCategory": [c.name for c in recipe.recipe_category] if recipe.recipe_category else [],
diff --git a/mealie/schema/recipe/recipe.py b/mealie/schema/recipe/recipe.py
index a328614d5..9de142427 100644
--- a/mealie/schema/recipe/recipe.py
+++ b/mealie/schema/recipe/recipe.py
@@ -91,6 +91,8 @@ class RecipeSummary(MealieModel):
name: str | None = None
slug: Annotated[str, Field(validate_default=True)] = ""
image: Any | None = None
+ recipe_servings: float = 0
+ recipe_yield_quantity: float = 0
recipe_yield: str | None = None
total_time: str | None = None
@@ -122,6 +124,10 @@ class RecipeSummary(MealieModel):
return val
+ @property
+ def recipe_yield_display(self) -> str:
+ return f"{self.recipe_yield_quantity} {self.recipe_yield}".strip()
+
@classmethod
def loader_options(cls) -> list[LoaderOption]:
return [
diff --git a/mealie/services/migrations/tandoor.py b/mealie/services/migrations/tandoor.py
index 7806e55ad..ba8979245 100644
--- a/mealie/services/migrations/tandoor.py
+++ b/mealie/services/migrations/tandoor.py
@@ -92,10 +92,8 @@ class TandoorMigrator(BaseMigrator):
recipe_data.pop("working_time", 0), recipe_data.pop("waiting_time", 0)
)
- serving_size = recipe_data.pop("servings", 0)
- serving_text = recipe_data.pop("servings_text", "")
- if serving_size and serving_text:
- recipe_data["recipeYield"] = f"{serving_size} {serving_text}"
+ recipe_data["recipeYieldQuantity"] = recipe_data.pop("servings", 0)
+ recipe_data["recipeYield"] = recipe_data.pop("servings_text", "")
try:
recipe_image_path = next(source_dir.glob("image.*"))
diff --git a/mealie/services/parser_services/_helpers/string_utils.py b/mealie/services/parser_services/_helpers/string_utils.py
deleted file mode 100644
index 426d540c8..000000000
--- a/mealie/services/parser_services/_helpers/string_utils.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import re
-
-compiled_match = re.compile(r"(.){1,6}\s\((.[^\(\)])+\)\s")
-compiled_search = re.compile(r"\((.[^\(])+\)")
-
-
-def move_parens_to_end(ing_str) -> str:
- """
- Moves all parentheses in the string to the end of the string using Regex.
- If no parentheses are found, the string is returned unchanged.
- """
- if re.match(compiled_match, ing_str):
- if match := re.search(compiled_search, ing_str):
- start = match.start()
- end = match.end()
- ing_str = ing_str[:start] + ing_str[end:] + " " + ing_str[start:end]
-
- return ing_str
-
-
-def check_char(char, *eql) -> bool:
- """Helper method to check if a characters matches any of the additional provided arguments"""
- return any(char == eql_char for eql_char in eql)
diff --git a/mealie/services/parser_services/brute/process.py b/mealie/services/parser_services/brute/process.py
index 446a6f54a..f9cd97b49 100644
--- a/mealie/services/parser_services/brute/process.py
+++ b/mealie/services/parser_services/brute/process.py
@@ -3,7 +3,7 @@ import unicodedata
from pydantic import BaseModel, ConfigDict
-from .._helpers import check_char, move_parens_to_end
+from ..parser_utils import check_char, move_parens_to_end
class BruteParsedIngredient(BaseModel):
diff --git a/mealie/services/parser_services/crfpp/pre_processor.py b/mealie/services/parser_services/crfpp/pre_processor.py
index d008acb49..aea0262a0 100644
--- a/mealie/services/parser_services/crfpp/pre_processor.py
+++ b/mealie/services/parser_services/crfpp/pre_processor.py
@@ -1,5 +1,6 @@
import re
-import unicodedata
+
+from mealie.services.parser_services.parser_utils import convert_vulgar_fractions_to_regular_fractions
replace_abbreviations = {
"cup": " cup ",
@@ -29,23 +30,6 @@ def remove_periods(string: str) -> str:
return re.sub(r"(? str:
"""
string = string.lower()
- string = replace_fraction_unicode(string)
+ string = convert_vulgar_fractions_to_regular_fractions(string)
string = remove_periods(string)
string = replace_common_abbreviations(string)
diff --git a/mealie/services/parser_services/_helpers/__init__.py b/mealie/services/parser_services/parser_utils/__init__.py
similarity index 100%
rename from mealie/services/parser_services/_helpers/__init__.py
rename to mealie/services/parser_services/parser_utils/__init__.py
diff --git a/mealie/services/parser_services/parser_utils/string_utils.py b/mealie/services/parser_services/parser_utils/string_utils.py
new file mode 100644
index 000000000..497bf76b7
--- /dev/null
+++ b/mealie/services/parser_services/parser_utils/string_utils.py
@@ -0,0 +1,111 @@
+import re
+from fractions import Fraction
+
+compiled_match = re.compile(r"(.){1,6}\s\((.[^\(\)])+\)\s")
+compiled_search = re.compile(r"\((.[^\(])+\)")
+
+
+def move_parens_to_end(ing_str) -> str:
+ """
+ Moves all parentheses in the string to the end of the string using Regex.
+ If no parentheses are found, the string is returned unchanged.
+ """
+ if re.match(compiled_match, ing_str):
+ if match := re.search(compiled_search, ing_str):
+ start = match.start()
+ end = match.end()
+ ing_str = ing_str[:start] + ing_str[end:] + " " + ing_str[start:end]
+
+ return ing_str
+
+
+def check_char(char, *eql) -> bool:
+ """Helper method to check if a characters matches any of the additional provided arguments"""
+ return any(char == eql_char for eql_char in eql)
+
+
+def convert_vulgar_fractions_to_regular_fractions(text: str) -> str:
+ vulgar_fractions = {
+ "ยผ": "1/4",
+ "ยฝ": "1/2",
+ "ยพ": "3/4",
+ "โ
": "1/7",
+ "โ
": "1/9",
+ "โ
": "1/10",
+ "โ
": "1/3",
+ "โ
": "2/3",
+ "โ
": "1/5",
+ "โ
": "2/5",
+ "โ
": "3/5",
+ "โ
": "4/5",
+ "โ
": "1/6",
+ "โ
": "5/6",
+ "โ
": "1/8",
+ "โ
": "3/8",
+ "โ
": "5/8",
+ "โ
": "7/8",
+ }
+
+ for vulgar_fraction, regular_fraction in vulgar_fractions.items():
+ # if we don't add a space in front of the fraction, mixed fractions will be broken
+ # e.g. "1ยฝ" -> "11/2"
+ text = text.replace(vulgar_fraction, f" {regular_fraction}").strip()
+
+ return text
+
+
+def extract_quantity_from_string(source_str: str) -> tuple[float, str]:
+ """
+ Extracts a quantity from a string. The quantity can be a fraction, decimal, or integer.
+
+ Returns the quantity and the remaining string. If no quantity is found, returns the quantity as 0.
+ """
+
+ source_str = source_str.strip()
+ if not source_str:
+ return 0, ""
+
+ source_str = convert_vulgar_fractions_to_regular_fractions(source_str)
+
+ mixed_fraction_pattern = re.compile(r"(\d+)\s+(\d+)/(\d+)")
+ fraction_pattern = re.compile(r"(\d+)/(\d+)")
+ number_pattern = re.compile(r"\d+(\.\d+)?")
+
+ try:
+ # Check for a mixed fraction (e.g. "1 1/2")
+ match = mixed_fraction_pattern.search(source_str)
+ if match:
+ whole_number = int(match.group(1))
+ numerator = int(match.group(2))
+ denominator = int(match.group(3))
+ quantity = whole_number + float(Fraction(numerator, denominator))
+ remaining_str = source_str[: match.start()] + source_str[match.end() :]
+
+ remaining_str = remaining_str.strip()
+ return quantity, remaining_str
+
+ # Check for a fraction (e.g. "1/2")
+ match = fraction_pattern.search(source_str)
+ if match:
+ numerator = int(match.group(1))
+ denominator = int(match.group(2))
+ quantity = float(Fraction(numerator, denominator))
+ remaining_str = source_str[: match.start()] + source_str[match.end() :]
+
+ remaining_str = remaining_str.strip()
+ return quantity, remaining_str
+
+ # Check for a number (integer or float)
+ match = number_pattern.search(source_str)
+ if match:
+ quantity = float(match.group())
+ remaining_str = source_str[: match.start()] + source_str[match.end() :]
+
+ remaining_str = remaining_str.strip()
+ return quantity, remaining_str
+
+ except ZeroDivisionError:
+ pass
+
+ # If no match, return 0 and the original string
+ return 0, source_str
diff --git a/mealie/services/scraper/cleaner.py b/mealie/services/scraper/cleaner.py
index bfbc971d4..661792022 100644
--- a/mealie/services/scraper/cleaner.py
+++ b/mealie/services/scraper/cleaner.py
@@ -10,8 +10,9 @@ from datetime import datetime, timedelta
from slugify import slugify
from mealie.core.root_logger import get_logger
-from mealie.lang.providers import Translator
+from mealie.lang.providers import Translator, get_all_translations
from mealie.schema.recipe.recipe import Recipe
+from mealie.services.parser_services.parser_utils import extract_quantity_from_string
logger = get_logger("recipe-scraper")
@@ -51,18 +52,21 @@ def clean(recipe_data: Recipe | dict, translator: Translator, url=None) -> Recip
recipe_data = recipe_data_dict
+ recipe_data["slug"] = slugify(recipe_data.get("name", ""))
recipe_data["description"] = clean_string(recipe_data.get("description", ""))
- # Times
recipe_data["prepTime"] = clean_time(recipe_data.get("prepTime"), translator)
recipe_data["performTime"] = clean_time(recipe_data.get("performTime"), translator)
recipe_data["totalTime"] = clean_time(recipe_data.get("totalTime"), translator)
+
+ recipe_data["recipeServings"], recipe_data["recipeYieldQuantity"], recipe_data["recipeYield"] = clean_yield(
+ recipe_data.get("recipeYield")
+ )
recipe_data["recipeCategory"] = clean_categories(recipe_data.get("recipeCategory", []))
- recipe_data["recipeYield"] = clean_yield(recipe_data.get("recipeYield"))
recipe_data["recipeIngredient"] = clean_ingredients(recipe_data.get("recipeIngredient", []))
recipe_data["recipeInstructions"] = clean_instructions(recipe_data.get("recipeInstructions", []))
+
recipe_data["image"] = clean_image(recipe_data.get("image"))[0]
- recipe_data["slug"] = slugify(recipe_data.get("name", ""))
recipe_data["orgURL"] = url or recipe_data.get("orgURL")
recipe_data["notes"] = clean_notes(recipe_data.get("notes"))
recipe_data["rating"] = clean_int(recipe_data.get("rating"))
@@ -324,7 +328,31 @@ def clean_notes(notes: typing.Any) -> list[dict] | None:
return parsed_notes
-def clean_yield(yld: str | list[str] | None) -> str:
+@functools.lru_cache
+def _get_servings_options() -> set[str]:
+ options: set[str] = set()
+ for key in [
+ "recipe.servings-text.makes",
+ "recipe.servings-text.serves",
+ "recipe.servings-text.serving",
+ "recipe.servings-text.servings",
+ "recipe.servings-text.yield",
+ "recipe.servings-text.yields",
+ ]:
+ options.update([t.strip().lower() for t in get_all_translations(key).values()])
+
+ return options
+
+
+def _is_serving_string(txt: str) -> bool:
+ txt = txt.strip().lower()
+ for option in _get_servings_options():
+ if option in txt.strip().lower():
+ return True
+ return False
+
+
+def clean_yield(yields: str | list[str] | None) -> tuple[float, float, str]:
"""
yield_amount attemps to parse out the yield amount from a recipe.
@@ -333,15 +361,34 @@ def clean_yield(yld: str | list[str] | None) -> str:
- `["4 servings", "4 Pies"]` - returns the last value
Returns:
+ float: The servings, if it can be parsed else 0
+ float: The yield quantity, if it can be parsed else 0
str: The yield amount, if it can be parsed else an empty string
"""
- if not yld:
- return ""
+ servings_qty: float = 0
+ yld_qty: float = 0
+ yld_str = ""
- if isinstance(yld, list):
- return yld[-1]
+ if not yields:
+ return servings_qty, yld_qty, yld_str
- return yld
+ if not isinstance(yields, list):
+ yields = [yields]
+
+ for yld in yields:
+ if not yld:
+ continue
+ if not isinstance(yld, str):
+ yld = str(yld)
+
+ qty, txt = extract_quantity_from_string(yld)
+ if qty and _is_serving_string(yld):
+ servings_qty = qty
+ else:
+ yld_qty = qty
+ yld_str = txt
+
+ return servings_qty, yld_qty, yld_str
def clean_time(time_entry: str | timedelta | None, translator: Translator) -> None | str:
diff --git a/tests/unit_tests/services_tests/scraper_tests/test_cleaner_parts.py b/tests/unit_tests/services_tests/scraper_tests/test_cleaner_parts.py
index 1c308ac91..98bf636dc 100644
--- a/tests/unit_tests/services_tests/scraper_tests/test_cleaner_parts.py
+++ b/tests/unit_tests/services_tests/scraper_tests/test_cleaner_parts.py
@@ -275,22 +275,102 @@ yield_test_cases = (
CleanerCase(
test_id="empty string",
input="",
- expected="",
+ expected=(0, 0, ""),
+ ),
+ CleanerCase(
+ test_id="regular string",
+ input="4 Batches",
+ expected=(0, 4, "Batches"),
+ ),
+ CleanerCase(
+ test_id="regular serving string",
+ input="4 Servings",
+ expected=(4, 0, ""),
+ ),
+ CleanerCase(
+ test_id="regular string with whitespace",
+ input="4 Batches ",
+ expected=(0, 4, "Batches"),
+ ),
+ CleanerCase(
+ test_id="regular serving string with whitespace",
+ input="4 Servings ",
+ expected=(4, 0, ""),
),
CleanerCase(
test_id="list of strings",
- input=["Makes 4 Batches", "4 Batches"],
- expected="4 Batches",
+ input=["Serves 2", "4 Batches", "5 Batches"],
+ expected=(2, 5, "Batches"),
),
CleanerCase(
test_id="basic string",
+ input="Makes a lot of Batches",
+ expected=(0, 0, "Makes a lot of Batches"),
+ ),
+ CleanerCase(
+ test_id="basic serving string",
input="Makes 4 Batches",
- expected="Makes 4 Batches",
+ expected=(4, 0, ""),
),
CleanerCase(
test_id="empty list",
input=[],
- expected="",
+ expected=(0, 0, ""),
+ ),
+ CleanerCase(
+ test_id="basic fraction",
+ input="1/2 Batches",
+ expected=(0, 0.5, "Batches"),
+ ),
+ CleanerCase(
+ test_id="mixed fraction",
+ input="1 1/2 Batches",
+ expected=(0, 1.5, "Batches"),
+ ),
+ CleanerCase(
+ test_id="improper fraction",
+ input="11/2 Batches",
+ expected=(0, 5.5, "Batches"),
+ ),
+ CleanerCase(
+ test_id="vulgar fraction",
+ input="ยพ Batches",
+ expected=(0, 0.75, "Batches"),
+ ),
+ CleanerCase(
+ test_id="mixed vulgar fraction",
+ input="2ยพ Batches",
+ expected=(0, 2.75, "Batches"),
+ ),
+ CleanerCase(
+ test_id="mixed vulgar fraction with space",
+ input="2 ยพ Batches",
+ expected=(0, 2.75, "Batches"),
+ ),
+ CleanerCase(
+ test_id="basic decimal",
+ input="0.5 Batches",
+ expected=(0, 0.5, "Batches"),
+ ),
+ CleanerCase(
+ test_id="text with numbers",
+ input="6 Batches or 10 Batches",
+ expected=(0, 6, "Batches or 10 Batches"),
+ ),
+ CleanerCase(
+ test_id="no qty",
+ input="A Lot of Servings",
+ expected=(0, 0, "A Lot of Servings"),
+ ),
+ CleanerCase(
+ test_id="invalid qty",
+ input="1/0 Batches",
+ expected=(0, 0, "1/0 Batches"),
+ ),
+ CleanerCase(
+ test_id="int as float",
+ input="3.0 Batches",
+ expected=(0, 3, "Batches"),
),
)
From e32bae4575b311c7f4c6b46ab082aa405d7b17c4 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 21 Nov 2024 09:53:19 +0100
Subject: [PATCH 035/238] fix(deps): update dependency openai to v1.55.0
(#4587)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 756a19ef2..26c85d480 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1596,13 +1596,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "openai"
-version = "1.54.5"
+version = "1.55.0"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.8"
files = [
- {file = "openai-1.54.5-py3-none-any.whl", hash = "sha256:f55a4450f38501814b53e76311ed7845a6f7f35bab46d0fb2a3728035d7a72d8"},
- {file = "openai-1.54.5.tar.gz", hash = "sha256:2aab4f9755a3e1e04d8a45ac1f4ce7b6948bab76646020c6386256d7e5cbb7e0"},
+ {file = "openai-1.55.0-py3-none-any.whl", hash = "sha256:446e08918f8dd70d8723274be860404c8c7cc46b91b93bbc0ef051f57eb503c1"},
+ {file = "openai-1.55.0.tar.gz", hash = "sha256:6c0975ac8540fe639d12b4ff5a8e0bf1424c844c4a4251148f59f06c4b2bd5db"},
]
[package.dependencies]
From 3fc120236d216cc213807e18e5529f53eef19a2a Mon Sep 17 00:00:00 2001
From: Michael Genson <71845777+michael-genson@users.noreply.github.com>
Date: Sat, 23 Nov 2024 21:56:39 -0600
Subject: [PATCH 036/238] chore(deps): Bump Ruff (#4602)
---
mealie/db/models/household/shopping_list.py | 2 +-
poetry.lock | 40 ++++++++++-----------
pyproject.toml | 2 +-
3 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/mealie/db/models/household/shopping_list.py b/mealie/db/models/household/shopping_list.py
index 99db67652..02f60a56a 100644
--- a/mealie/db/models/household/shopping_list.py
+++ b/mealie/db/models/household/shopping_list.py
@@ -194,7 +194,7 @@ class SessionBuffer:
self.shopping_list_ids.clear()
-session_buffer_context = ContextVar("session_buffer", default=SessionBuffer())
+session_buffer_context = ContextVar("session_buffer", default=SessionBuffer()) # noqa: B039
@event.listens_for(ShoppingListItem, "after_insert")
diff --git a/poetry.lock b/poetry.lock
index 26c85d480..c9f24788e 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2813,29 +2813,29 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "ruff"
-version = "0.7.4"
+version = "0.8.0"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
- {file = "ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478"},
- {file = "ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63"},
- {file = "ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20"},
- {file = "ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109"},
- {file = "ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452"},
- {file = "ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea"},
- {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7"},
- {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05"},
- {file = "ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06"},
- {file = "ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc"},
- {file = "ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172"},
- {file = "ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a"},
- {file = "ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd"},
- {file = "ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a"},
- {file = "ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac"},
- {file = "ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6"},
- {file = "ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f"},
- {file = "ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2"},
+ {file = "ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea"},
+ {file = "ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b"},
+ {file = "ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a"},
+ {file = "ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99"},
+ {file = "ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c"},
+ {file = "ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9"},
+ {file = "ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362"},
+ {file = "ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df"},
+ {file = "ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3"},
+ {file = "ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c"},
+ {file = "ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2"},
+ {file = "ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70"},
+ {file = "ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd"},
+ {file = "ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426"},
+ {file = "ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468"},
+ {file = "ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f"},
+ {file = "ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6"},
+ {file = "ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44"},
]
[[package]]
@@ -3414,4 +3414,4 @@ pgsql = ["psycopg2-binary"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
-content-hash = "2a3b97688c700f6c01241c0559afa48bdf039399261e7cdd68eebad96dadb44f"
+content-hash = "4d6f1e1665de07327fd42e27bddcff9b5e6b3f7b64bcf8782490e2acdaaf2b9a"
diff --git a/pyproject.toml b/pyproject.toml
index 7cb1a9b88..9974f3450 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -64,7 +64,7 @@ pylint = "^3.0.0"
pytest = "^8.0.0"
pytest-asyncio = "^0.24.0"
rich = "^13.5.2"
-ruff = "^0.7.0"
+ruff = "^0.8.0"
types-PyYAML = "^6.0.4"
types-python-dateutil = "^2.8.18"
types-python-slugify = "^6.0.0"
From 82cc9e11f7421d5a63ff329ac556c331bea5439d Mon Sep 17 00:00:00 2001
From: Michael Genson <71845777+michael-genson@users.noreply.github.com>
Date: Mon, 25 Nov 2024 03:25:35 -0600
Subject: [PATCH 037/238] dev: Fix json2ts codegen (#4590)
---
dev/code-generation/gen_ts_types.py | 113 +++++++++++++++++-
frontend/lib/api/types/admin.ts | 1 +
frontend/lib/api/types/cookbook.ts | 14 +--
frontend/lib/api/types/household.ts | 21 ++--
frontend/lib/api/types/meal-plan.ts | 41 +++----
frontend/lib/api/types/openai.ts | 178 ----------------------------
frontend/lib/api/types/recipe.ts | 13 +-
frontend/lib/api/types/reports.ts | 6 +-
frontend/lib/api/types/response.ts | 2 +-
frontend/lib/api/types/user.ts | 16 +--
10 files changed, 161 insertions(+), 244 deletions(-)
diff --git a/dev/code-generation/gen_ts_types.py b/dev/code-generation/gen_ts_types.py
index b15f7ee9d..408262f25 100644
--- a/dev/code-generation/gen_ts_types.py
+++ b/dev/code-generation/gen_ts_types.py
@@ -1,3 +1,4 @@
+import re
from pathlib import Path
from jinja2 import Template
@@ -64,7 +65,112 @@ def generate_global_components_types() -> None:
# Pydantic To Typescript Generator
-def generate_typescript_types() -> None:
+def generate_typescript_types() -> None: # noqa: C901
+ def contains_number(s: str) -> bool:
+ return bool(re.search(r"\d", s))
+
+ def remove_numbers(s: str) -> str:
+ return re.sub(r"\d", "", s)
+
+ def extract_type_name(line: str) -> str:
+ # Looking for "export type EnumName = enumVal1 | enumVal2 | ..."
+ if not (line.startswith("export type") and "=" in line):
+ return ""
+
+ return line.split(" ")[2]
+
+ def extract_property_type_name(line: str) -> str:
+ # Looking for " fieldName: FieldType;" or " fieldName: FieldType & string;"
+ if not (line.startswith(" ") and ":" in line):
+ return ""
+
+ return line.split(":")[1].strip().split(";")[0]
+
+ def extract_interface_name(line: str) -> str:
+ # Looking for "export interface InterfaceName {"
+ if not (line.startswith("export interface") and "{" in line):
+ return ""
+
+ return line.split(" ")[2]
+
+ def is_comment_line(line: str) -> bool:
+ s = line.strip()
+ return s.startswith("/*") or s.startswith("*")
+
+ def clean_output_file(file: Path) -> None:
+ """
+ json2ts generates duplicate types off of our enums and appends a number to the end of the type name.
+ Our Python code (hopefully) doesn't have any duplicate enum names, or types with numbers in them,
+ so we can safely remove the numbers.
+
+ To do this, we read the output line-by-line and replace any type names that contain numbers with
+ the same type name, but without the numbers.
+
+ Note: the issue arrises from the JSON package json2ts, not the Python package pydantic2ts,
+ otherwise we could just fix pydantic2ts.
+ """
+
+ # First pass: build a map of type names to their numberless counterparts and lines to skip
+ replacement_map = {}
+ lines_to_skip = set()
+ wait_for_semicolon = False
+ wait_for_close_bracket = False
+ skip_comments = False
+ with open(file) as f:
+ for i, line in enumerate(f.readlines()):
+ if wait_for_semicolon:
+ if ";" in line:
+ wait_for_semicolon = False
+ lines_to_skip.add(i)
+ continue
+ if wait_for_close_bracket:
+ if "}" in line:
+ wait_for_close_bracket = False
+ lines_to_skip.add(i)
+ continue
+
+ if type_name := extract_type_name(line):
+ if not contains_number(type_name):
+ continue
+
+ replacement_map[type_name] = remove_numbers(type_name)
+ if ";" not in line:
+ wait_for_semicolon = True
+ lines_to_skip.add(i)
+
+ elif type_name := extract_interface_name(line):
+ if not contains_number(type_name):
+ continue
+
+ replacement_map[type_name] = remove_numbers(type_name)
+ if "}" not in line:
+ wait_for_close_bracket = True
+ lines_to_skip.add(i)
+
+ elif skip_comments and is_comment_line(line):
+ lines_to_skip.add(i)
+
+ # we've passed the opening comments and empty line at the header
+ elif not skip_comments and not line.strip():
+ skip_comments = True
+
+ # Second pass: rewrite or remove lines as needed.
+ # We have to do two passes here because definitions don't always appear in the same order as their usage.
+ lines = []
+ with open(file) as f:
+ for i, line in enumerate(f.readlines()):
+ if i in lines_to_skip:
+ continue
+
+ if type_name := extract_property_type_name(line):
+ if type_name in replacement_map:
+ line = line.replace(type_name, replacement_map[type_name])
+
+ lines.append(line)
+
+ with open(file, "w") as f:
+ f.writelines(lines)
+
def path_to_module(path: Path):
str_path: str = str(path)
@@ -98,9 +204,10 @@ def generate_typescript_types() -> None:
try:
path_as_module = path_to_module(module)
generate_typescript_defs(path_as_module, str(out_path), exclude=("MealieModel")) # type: ignore
- except Exception as e:
+ clean_output_file(out_path)
+ except Exception:
failed_modules.append(module)
- log.error(f"Module Error: {e}")
+ log.exception(f"Module Error: {module}")
log.debug("\n๐ Skipped Directories:")
for skipped_dir in skipped_dirs:
diff --git a/frontend/lib/api/types/admin.ts b/frontend/lib/api/types/admin.ts
index 79678aed5..34553897d 100644
--- a/frontend/lib/api/types/admin.ts
+++ b/frontend/lib/api/types/admin.ts
@@ -162,6 +162,7 @@ export interface RecipeTool {
name: string;
slug: string;
onHand?: boolean;
+ [k: string]: unknown;
}
export interface CustomPageImport {
name: string;
diff --git a/frontend/lib/api/types/cookbook.ts b/frontend/lib/api/types/cookbook.ts
index 721e6320f..22b5079a2 100644
--- a/frontend/lib/api/types/cookbook.ts
+++ b/frontend/lib/api/types/cookbook.ts
@@ -15,7 +15,7 @@ export interface CreateCookBook {
slug?: string | null;
position?: number;
public?: boolean;
- queryFilterString: string;
+ queryFilterString?: string;
}
export interface ReadCookBook {
name: string;
@@ -23,11 +23,11 @@ export interface ReadCookBook {
slug?: string | null;
position?: number;
public?: boolean;
- queryFilterString: string;
+ queryFilterString?: string;
groupId: string;
householdId: string;
id: string;
- queryFilter: QueryFilterJSON;
+ queryFilter?: QueryFilterJSON;
}
export interface QueryFilterJSON {
parts?: QueryFilterJSONPart[];
@@ -47,11 +47,11 @@ export interface RecipeCookBook {
slug?: string | null;
position?: number;
public?: boolean;
- queryFilterString: string;
+ queryFilterString?: string;
groupId: string;
householdId: string;
id: string;
- queryFilter: QueryFilterJSON;
+ queryFilter?: QueryFilterJSON;
recipes: RecipeSummary[];
}
export interface RecipeSummary {
@@ -106,7 +106,7 @@ export interface SaveCookBook {
slug?: string | null;
position?: number;
public?: boolean;
- queryFilterString: string;
+ queryFilterString?: string;
groupId: string;
householdId: string;
}
@@ -116,7 +116,7 @@ export interface UpdateCookBook {
slug?: string | null;
position?: number;
public?: boolean;
- queryFilterString: string;
+ queryFilterString?: string;
groupId: string;
householdId: string;
id: string;
diff --git a/frontend/lib/api/types/household.ts b/frontend/lib/api/types/household.ts
index 79b1a7ffa..0e0072391 100644
--- a/frontend/lib/api/types/household.ts
+++ b/frontend/lib/api/types/household.ts
@@ -26,12 +26,14 @@ export interface CreateHouseholdPreferences {
}
export interface CreateInviteToken {
uses: number;
+ groupId?: string | null;
+ householdId?: string | null;
}
export interface CreateWebhook {
enabled?: boolean;
name?: string;
url?: string;
- webhookType?: WebhookType & string;
+ webhookType?: WebhookType;
scheduledTime: string;
}
export interface EmailInitationResponse {
@@ -46,10 +48,6 @@ export interface GroupEventNotifierCreate {
name: string;
appriseUrl?: string | null;
}
-/**
- * These events are in-sync with the EventTypes found in the EventBusService.
- * If you modify this, make sure to update the EventBusService as well.
- */
export interface GroupEventNotifierOptions {
testMessage?: boolean;
webhookTask?: boolean;
@@ -204,7 +202,7 @@ export interface ReadWebhook {
enabled?: boolean;
name?: string;
url?: string;
- webhookType?: WebhookType & string;
+ webhookType?: WebhookType;
scheduledTime: string;
groupId: string;
householdId: string;
@@ -263,7 +261,7 @@ export interface SaveWebhook {
enabled?: boolean;
name?: string;
url?: string;
- webhookType?: WebhookType & string;
+ webhookType?: WebhookType;
scheduledTime: string;
groupId: string;
householdId: string;
@@ -486,9 +484,6 @@ export interface ShoppingListItemUpdate {
} | null;
recipeReferences?: (ShoppingListItemRecipeRefCreate | ShoppingListItemRecipeRefUpdate)[];
}
-/**
- * Only used for bulk update operations where the shopping list item id isn't already supplied
- */
export interface ShoppingListItemUpdateBulk {
quantity?: number;
unit?: IngredientUnit | CreateIngredientUnit | null;
@@ -509,9 +504,6 @@ export interface ShoppingListItemUpdateBulk {
recipeReferences?: (ShoppingListItemRecipeRefCreate | ShoppingListItemRecipeRefUpdate)[];
id: string;
}
-/**
- * Container for bulk shopping list item changes
- */
export interface ShoppingListItemsCollectionOut {
createdItems?: ShoppingListItemOut[];
updatedItems?: ShoppingListItemOut[];
@@ -565,6 +557,8 @@ export interface RecipeSummary {
name?: string | null;
slug?: string;
image?: unknown;
+ recipeServings?: number;
+ recipeYieldQuantity?: number;
recipeYield?: string | null;
totalTime?: string | null;
prepTime?: string | null;
@@ -599,6 +593,7 @@ export interface RecipeTool {
name: string;
slug: string;
onHand?: boolean;
+ [k: string]: unknown;
}
export interface ShoppingListRemoveRecipeParams {
recipeDecrementQuantity?: number;
diff --git a/frontend/lib/api/types/meal-plan.ts b/frontend/lib/api/types/meal-plan.ts
index f11ec1feb..4869c7c59 100644
--- a/frontend/lib/api/types/meal-plan.ts
+++ b/frontend/lib/api/types/meal-plan.ts
@@ -12,21 +12,16 @@ export type LogicalOperator = "AND" | "OR";
export type RelationalKeyword = "IS" | "IS NOT" | "IN" | "NOT IN" | "CONTAINS ALL" | "LIKE" | "NOT LIKE";
export type RelationalOperator = "=" | "<>" | ">" | "<" | ">=" | "<=";
-export interface Category {
- id: string;
- name: string;
- slug: string;
-}
export interface CreatePlanEntry {
date: string;
- entryType?: PlanEntryType & string;
+ entryType?: PlanEntryType;
title?: string;
text?: string;
recipeId?: string | null;
}
export interface CreateRandomEntry {
date: string;
- entryType?: PlanEntryType & string;
+ entryType?: PlanEntryType;
}
export interface ListItem {
title?: string | null;
@@ -35,18 +30,18 @@ export interface ListItem {
checked?: boolean;
}
export interface PlanRulesCreate {
- day?: PlanRulesDay & string;
- entryType?: PlanRulesType & string;
- queryFilterString: string;
+ day?: PlanRulesDay;
+ entryType?: PlanRulesType;
+ queryFilterString?: string;
}
export interface PlanRulesOut {
- day?: PlanRulesDay & string;
- entryType?: PlanRulesType & string;
- queryFilterString: string;
+ day?: PlanRulesDay;
+ entryType?: PlanRulesType;
+ queryFilterString?: string;
groupId: string;
householdId: string;
id: string;
- queryFilter: QueryFilterJSON;
+ queryFilter?: QueryFilterJSON;
}
export interface QueryFilterJSON {
parts?: QueryFilterJSONPart[];
@@ -61,21 +56,21 @@ export interface QueryFilterJSONPart {
[k: string]: unknown;
}
export interface PlanRulesSave {
- day?: PlanRulesDay & string;
- entryType?: PlanRulesType & string;
- queryFilterString: string;
+ day?: PlanRulesDay;
+ entryType?: PlanRulesType;
+ queryFilterString?: string;
groupId: string;
householdId: string;
}
export interface ReadPlanEntry {
date: string;
- entryType?: PlanEntryType & string;
+ entryType?: PlanEntryType;
title?: string;
text?: string;
recipeId?: string | null;
id: number;
groupId: string;
- userId?: string | null;
+ userId: string;
householdId: string;
recipe?: RecipeSummary | null;
}
@@ -127,12 +122,12 @@ export interface RecipeTool {
}
export interface SavePlanEntry {
date: string;
- entryType?: PlanEntryType & string;
+ entryType?: PlanEntryType;
title?: string;
text?: string;
recipeId?: string | null;
groupId: string;
- userId?: string | null;
+ userId: string;
}
export interface ShoppingListIn {
name: string;
@@ -147,11 +142,11 @@ export interface ShoppingListOut {
}
export interface UpdatePlanEntry {
date: string;
- entryType?: PlanEntryType & string;
+ entryType?: PlanEntryType;
title?: string;
text?: string;
recipeId?: string | null;
id: number;
groupId: string;
- userId?: string | null;
+ userId: string;
}
diff --git a/frontend/lib/api/types/openai.ts b/frontend/lib/api/types/openai.ts
index f1a358ae0..be28b87be 100644
--- a/frontend/lib/api/types/openai.ts
+++ b/frontend/lib/api/types/openai.ts
@@ -6,215 +6,37 @@
*/
export interface OpenAIIngredient {
- /**
- *
- * The input is simply the ingredient string you are processing as-is. It is forbidden to
- * modify this at all, you must provide the input exactly as you received it.
- *
- */
input: string;
- /**
- *
- * This value is a float between 0 - 100, where 100 is full confidence that the result is correct,
- * and 0 is no confidence that the result is correct. If you're unable to parse anything,
- * and you put the entire string in the notes, you should return 0 confidence. If you can easily
- * parse the string into each component, then you should return a confidence of 100. If you have to
- * guess which part is the unit and which part is the food, your confidence should be lower, such as 60.
- * Even if there is no unit or note, if you're able to determine the food, you may use a higher confidence.
- * If the entire ingredient consists of only a food, you can use a confidence of 100.
- *
- */
confidence?: number | null;
- /**
- *
- * The numerical representation of how much of this ingredient. For instance, if you receive
- * "3 1/2 grams of minced garlic", the quantity is "3 1/2". Quantity may be represented as a whole number
- * (integer), a float or decimal, or a fraction. You should output quantity in only whole numbers or
- * floats, converting fractions into floats. Floats longer than 10 decimal places should be
- * rounded to 10 decimal places.
- *
- */
quantity?: number | null;
- /**
- *
- * The unit of measurement for this ingredient. For instance, if you receive
- * "2 lbs chicken breast", the unit is "lbs" (short for "pounds").
- *
- */
unit?: string | null;
- /**
- *
- * The actual physical ingredient used in the recipe. For instance, if you receive
- * "3 cups of onions, chopped", the food is "onions".
- *
- */
food?: string | null;
- /**
- *
- * The rest of the text that represents more detail on how to prepare the ingredient.
- * Anything that is not one of the above should be the note. For instance, if you receive
- * "one can of butter beans, drained" the note would be "drained". If you receive
- * "3 cloves of garlic peeled and finely chopped", the note would be "peeled and finely chopped".
- *
- */
note?: string | null;
}
export interface OpenAIIngredients {
ingredients?: OpenAIIngredient[];
}
export interface OpenAIRecipe {
- /**
- *
- * The name or title of the recipe. If you're unable to determine the name of the recipe, you should
- * make your best guess based upon the ingredients and instructions provided.
- *
- */
name: string;
- /**
- *
- * A long description of the recipe. This should be a string that describes the recipe in a few words
- * or sentences. If the recipe doesn't have a description, you should return None.
- *
- */
description: string | null;
- /**
- *
- * The yield of the recipe. For instance, if the recipe makes 12 cookies, the yield is "12 cookies".
- * If the recipe makes 2 servings, the yield is "2 servings". Typically yield consists of a number followed
- * by the word "serving" or "servings", but it can be any string that describes the yield. If the yield
- * isn't specified, you should return None.
- *
- */
recipe_yield?: string | null;
- /**
- *
- * The total time it takes to make the recipe. This should be a string that describes a duration of time,
- * such as "1 hour and 30 minutes", "90 minutes", or "1.5 hours". If the recipe has multiple times, choose
- * the longest time. If the recipe doesn't specify a total time or duration, or it specifies a prep time or
- * perform time but not a total time, you should return None. Do not duplicate times between total time, prep
- * time and perform time.
- *
- */
total_time?: string | null;
- /**
- *
- * The time it takes to prepare the recipe. This should be a string that describes a duration of time,
- * such as "30 minutes", "1 hour", or "1.5 hours". If the recipe has a total time, the prep time should be
- * less than the total time. If the recipe doesn't specify a prep time, you should return None. If the recipe
- * supplies only one time, it should be the total time. Do not duplicate times between total time, prep
- * time and coperformok time.
- *
- */
prep_time?: string | null;
- /**
- *
- * The time it takes to cook the recipe. This should be a string that describes a duration of time,
- * such as "30 minutes", "1 hour", or "1.5 hours". If the recipe has a total time, the perform time should be
- * less than the total time. If the recipe doesn't specify a perform time, you should return None. If the
- * recipe specifies a cook time, active time, or other time besides total or prep, you should use that
- * time as the perform time. If the recipe supplies only one time, it should be the total time, and not the
- * perform time. Do not duplicate times between total time, prep time and perform time.
- *
- */
perform_time?: string | null;
- /**
- *
- * A list of ingredients used in the recipe. Ingredients should be inserted in the order they appear in the
- * recipe. If the recipe has no ingredients, you should return an empty list.
- *
- * Often times, but not always, ingredients are separated by line breaks. Use these as a guide to
- * separate ingredients.
- *
- */
ingredients?: OpenAIRecipeIngredient[];
- /**
- *
- * A list of ingredients used in the recipe. Ingredients should be inserted in the order they appear in the
- * recipe. If the recipe has no ingredients, you should return an empty list.
- *
- * Often times, but not always, instructions are separated by line breaks and/or separated by paragraphs.
- * Use these as a guide to separate instructions. They also may be separated by numbers or words, such as
- * "1.", "2.", "Step 1", "Step 2", "First", "Second", etc.
- *
- */
instructions?: OpenAIRecipeInstruction[];
- /**
- *
- * A list of notes found in the recipe. Notes should be inserted in the order they appear in the recipe.
- * They may appear anywhere on the recipe, though they are typically found under the instructions.
- *
- */
notes?: OpenAIRecipeNotes[];
}
export interface OpenAIRecipeIngredient {
- /**
- *
- * The title of the section of the recipe that the ingredient is found in. Recipes may not specify
- * ingredient sections, in which case this should be left blank.
- * Only the first item in the section should have this set,
- * whereas subsuquent items should have their titles left blank (unless they start a new section).
- *
- */
title?: string | null;
- /**
- *
- * The text of the ingredient. This should represent the entire ingredient, such as "1 cup of flour" or
- * "2 cups of onions, chopped". If the ingredient is completely blank, skip it and do not add the ingredient,
- * since this field is required.
- *
- * If the ingredient has no text, but has a title, include the title on the
- * next ingredient instead.
- *
- */
text: string;
}
export interface OpenAIRecipeInstruction {
- /**
- *
- * The title of the section of the recipe that the instruction is found in. Recipes may not specify
- * instruction sections, in which case this should be left blank.
- * Only the first instruction in the section should have this set,
- * whereas subsuquent instructions should have their titles left blank (unless they start a new section).
- *
- */
title?: string | null;
- /**
- *
- * The text of the instruction. This represents one step in the recipe, such as "Preheat the oven to 350",
- * or "Sautรฉ the onions for 20 minutes". Sometimes steps can be longer, such as "Bring a large pot of lightly
- * salted water to a boil. Add ditalini pasta and cook for 8 minutes or until al dente; drain.".
- *
- * Sometimes, but not always, recipes will include their number in front of the text, such as
- * "1.", "2.", or "Step 1", "Step 2", or "First", "Second". In the case where they are directly numbered
- * ("1.", "2.", "Step one", "Step 1", "Step two", "Step 2", etc.), you should not include the number in
- * the text. However, if they use words ("First", "Second", etc.), then those should be included.
- *
- * If the instruction is completely blank, skip it and do not add the instruction, since this field is
- * required. If the ingredient has no text, but has a title, include the title on the next
- * instruction instead.
- *
- */
text: string;
}
export interface OpenAIRecipeNotes {
- /**
- *
- * The title of the note. Notes may not specify a title, and just have a body of text. In this case,
- * title should be left blank, and all content should go in the note text. If the note title is just
- * "note" or "info", you should ignore it and leave the title blank.
- *
- */
title?: string | null;
- /**
- *
- * The text of the note. This should represent the entire note, such as "This recipe is great for
- * a summer picnic" or "This recipe is a family favorite". They may also include additional prep
- * instructions such as "to make this recipe gluten free, use gluten free flour", or "you may prepare
- * the dough the night before and refrigerate it until ready to bake".
- *
- * If the note is completely blank, skip it and do not add the note, since this field is required.
- *
- */
text: string;
}
export interface OpenAIBase {}
diff --git a/frontend/lib/api/types/recipe.ts b/frontend/lib/api/types/recipe.ts
index ed0c7ab00..84440f19b 100644
--- a/frontend/lib/api/types/recipe.ts
+++ b/frontend/lib/api/types/recipe.ts
@@ -116,7 +116,7 @@ export interface ExportBase {
}
export interface ExportRecipes {
recipes: string[];
- exportType?: ExportTypes & string;
+ exportType?: ExportTypes;
}
export interface IngredientConfidence {
average?: number | null;
@@ -150,14 +150,11 @@ export interface MultiPurposeLabelSummary {
groupId: string;
id: string;
}
-/**
- * A list of ingredient references.
- */
export interface IngredientReferences {
referenceId?: string | null;
}
export interface IngredientRequest {
- parser?: RegisteredParser & string;
+ parser?: RegisteredParser;
ingredient: string;
}
export interface IngredientUnit {
@@ -181,7 +178,7 @@ export interface IngredientUnitAlias {
name: string;
}
export interface IngredientsRequest {
- parser?: RegisteredParser & string;
+ parser?: RegisteredParser;
ingredients: string[];
}
export interface MergeFood {
@@ -268,9 +265,9 @@ export interface RecipeTool {
export interface RecipeStep {
id?: string | null;
title?: string | null;
+ summary?: string | null;
text: string;
ingredientReferences?: IngredientReferences[];
- summary?: string | null;
}
export interface RecipeAsset {
name: string;
@@ -495,7 +492,7 @@ export interface ScrapeRecipeTest {
url: string;
useOpenAI?: boolean;
}
-export interface SlugResponse { }
+export interface SlugResponse {}
export interface TagIn {
name: string;
}
diff --git a/frontend/lib/api/types/reports.ts b/frontend/lib/api/types/reports.ts
index 39de61648..2b763275e 100644
--- a/frontend/lib/api/types/reports.ts
+++ b/frontend/lib/api/types/reports.ts
@@ -13,7 +13,7 @@ export interface ReportCreate {
category: ReportCategory;
groupId: string;
name: string;
- status?: ReportSummaryStatus & string;
+ status?: ReportSummaryStatus;
}
export interface ReportEntryCreate {
reportId: string;
@@ -35,7 +35,7 @@ export interface ReportOut {
category: ReportCategory;
groupId: string;
name: string;
- status?: ReportSummaryStatus & string;
+ status?: ReportSummaryStatus;
id: string;
entries?: ReportEntryOut[];
}
@@ -44,6 +44,6 @@ export interface ReportSummary {
category: ReportCategory;
groupId: string;
name: string;
- status?: ReportSummaryStatus & string;
+ status?: ReportSummaryStatus;
id: string;
}
diff --git a/frontend/lib/api/types/response.ts b/frontend/lib/api/types/response.ts
index 2eef08546..30480aba6 100644
--- a/frontend/lib/api/types/response.ts
+++ b/frontend/lib/api/types/response.ts
@@ -24,7 +24,7 @@ export interface PaginationQuery {
perPage?: number;
orderBy?: string | null;
orderByNullPosition?: OrderByNullPosition | null;
- orderDirection?: OrderDirection & string;
+ orderDirection?: OrderDirection;
queryFilter?: string | null;
paginationSeed?: string | null;
}
diff --git a/frontend/lib/api/types/user.ts b/frontend/lib/api/types/user.ts
index 27c5d794f..855f64e7b 100644
--- a/frontend/lib/api/types/user.ts
+++ b/frontend/lib/api/types/user.ts
@@ -69,7 +69,7 @@ export interface ReadWebhook {
enabled?: boolean;
name?: string;
url?: string;
- webhookType?: WebhookType & string;
+ webhookType?: WebhookType;
scheduledTime: string;
groupId: string;
householdId: string;
@@ -110,7 +110,7 @@ export interface PrivateUser {
username?: string | null;
fullName?: string | null;
email: string;
- authMethod?: AuthMethod & string;
+ authMethod?: AuthMethod;
admin?: boolean;
group: string;
household: string;
@@ -175,7 +175,7 @@ export interface CreateWebhook {
enabled?: boolean;
name?: string;
url?: string;
- webhookType?: WebhookType & string;
+ webhookType?: WebhookType;
scheduledTime: string;
}
export interface UserBase {
@@ -183,7 +183,7 @@ export interface UserBase {
username?: string | null;
fullName?: string | null;
email: string;
- authMethod?: AuthMethod & string;
+ authMethod?: AuthMethod;
admin?: boolean;
group?: string | null;
household?: string | null;
@@ -195,10 +195,10 @@ export interface UserBase {
}
export interface UserIn {
id?: string | null;
- username?: string | null;
- fullName?: string | null;
+ username: string;
+ fullName: string;
email: string;
- authMethod?: AuthMethod & string;
+ authMethod?: AuthMethod;
admin?: boolean;
group?: string | null;
household?: string | null;
@@ -214,7 +214,7 @@ export interface UserOut {
username?: string | null;
fullName?: string | null;
email: string;
- authMethod?: AuthMethod & string;
+ authMethod?: AuthMethod;
admin?: boolean;
group: string;
household: string;
From 5afa611ec39fd093c15babe3dfcc87b37f46dc00 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 25 Nov 2024 09:34:11 +0000
Subject: [PATCH 038/238] chore(auto): Update pre-commit hooks (#4606)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
---
.pre-commit-config.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5f52cfc56..41cc06824 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -12,7 +12,7 @@ repos:
exclude: ^tests/data/
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
- rev: v0.7.4
+ rev: v0.8.0
hooks:
- id: ruff
- id: ruff-format
From efb72b1859bac5dd3c9553ef05ab29e42e4b1659 Mon Sep 17 00:00:00 2001
From: Saireddy1369 <162394750+Saireddy1369@users.noreply.github.com>
Date: Mon, 25 Nov 2024 08:38:50 -0700
Subject: [PATCH 039/238] fix: Incorrect date format in Add to mealplan modal
(#4605)
---
frontend/components/Domain/Recipe/RecipeContextMenu.vue | 2 --
1 file changed, 2 deletions(-)
diff --git a/frontend/components/Domain/Recipe/RecipeContextMenu.vue b/frontend/components/Domain/Recipe/RecipeContextMenu.vue
index 0f847d60e..b54189251 100644
--- a/frontend/components/Domain/Recipe/RecipeContextMenu.vue
+++ b/frontend/components/Domain/Recipe/RecipeContextMenu.vue
@@ -51,8 +51,6 @@
Date: Mon, 25 Nov 2024 16:50:56 +0100
Subject: [PATCH 040/238] chore(deps): update dependency mkdocs-material to
v9.5.46 (#4607)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index c9f24788e..3268400ca 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1462,13 +1462,13 @@ pyyaml = ">=5.1"
[[package]]
name = "mkdocs-material"
-version = "9.5.45"
+version = "9.5.46"
description = "Documentation that simply works"
optional = false
python-versions = ">=3.8"
files = [
- {file = "mkdocs_material-9.5.45-py3-none-any.whl", hash = "sha256:a9be237cfd0be14be75f40f1726d83aa3a81ce44808dc3594d47a7a592f44547"},
- {file = "mkdocs_material-9.5.45.tar.gz", hash = "sha256:286489cf0beca4a129d91d59d6417419c63bceed1ce5cd0ec1fc7e1ebffb8189"},
+ {file = "mkdocs_material-9.5.46-py3-none-any.whl", hash = "sha256:98f0a2039c62e551a68aad0791a8d41324ff90c03a6e6cea381a384b84908b83"},
+ {file = "mkdocs_material-9.5.46.tar.gz", hash = "sha256:ae2043f4238e572f9a40e0b577f50400d6fc31e2fef8ea141800aebf3bd273d7"},
]
[package.dependencies]
From e97f1f805ba0d0999516ca2cdfdd7f60fefaf9e4 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 26 Nov 2024 10:21:29 +0100
Subject: [PATCH 041/238] fix(deps): update dependency openai to v1.55.1
(#4609)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 3268400ca..d3cf60f0d 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1596,13 +1596,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "openai"
-version = "1.55.0"
+version = "1.55.1"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.8"
files = [
- {file = "openai-1.55.0-py3-none-any.whl", hash = "sha256:446e08918f8dd70d8723274be860404c8c7cc46b91b93bbc0ef051f57eb503c1"},
- {file = "openai-1.55.0.tar.gz", hash = "sha256:6c0975ac8540fe639d12b4ff5a8e0bf1424c844c4a4251148f59f06c4b2bd5db"},
+ {file = "openai-1.55.1-py3-none-any.whl", hash = "sha256:d10d96a4f9dc5f05d38dea389119ec8dcd24bc9698293c8357253c601b4a77a5"},
+ {file = "openai-1.55.1.tar.gz", hash = "sha256:471324321e7739214f16a544e801947a046d3c5d516fae8719a317234e4968d3"},
]
[package.dependencies]
From 3539385429b5258fa2544384b616061abf6cb2d4 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 26 Nov 2024 12:25:04 +0100
Subject: [PATCH 042/238] fix(deps): update dependency uvicorn to v0.32.1
(#4586)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 93 ++++++++++++++++++++++++++++-------------------------
1 file changed, 50 insertions(+), 43 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index d3cf60f0d..e5c431d40 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -839,51 +839,58 @@ trio = ["trio (>=0.22.0,<0.23.0)"]
[[package]]
name = "httptools"
-version = "0.6.1"
+version = "0.6.4"
description = "A collection of framework independent HTTP protocol utils."
optional = false
python-versions = ">=3.8.0"
files = [
- {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"},
- {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"},
- {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"},
- {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"},
- {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"},
- {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"},
- {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"},
- {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"},
- {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"},
- {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"},
- {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"},
- {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"},
- {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"},
- {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"},
- {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"},
- {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"},
- {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"},
- {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"},
- {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"},
- {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"},
- {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"},
- {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"},
- {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"},
- {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"},
- {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"},
- {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"},
- {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"},
- {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"},
- {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"},
- {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"},
- {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"},
- {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"},
- {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"},
- {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"},
- {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"},
- {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"},
+ {file = "httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0"},
+ {file = "httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da"},
+ {file = "httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1"},
+ {file = "httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50"},
+ {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959"},
+ {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4"},
+ {file = "httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c"},
+ {file = "httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069"},
+ {file = "httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a"},
+ {file = "httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975"},
+ {file = "httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636"},
+ {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721"},
+ {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988"},
+ {file = "httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17"},
+ {file = "httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2"},
+ {file = "httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44"},
+ {file = "httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1"},
+ {file = "httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2"},
+ {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81"},
+ {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f"},
+ {file = "httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970"},
+ {file = "httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660"},
+ {file = "httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083"},
+ {file = "httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3"},
+ {file = "httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071"},
+ {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5"},
+ {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0"},
+ {file = "httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8"},
+ {file = "httptools-0.6.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d3f0d369e7ffbe59c4b6116a44d6a8eb4783aae027f2c0b366cf0aa964185dba"},
+ {file = "httptools-0.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:94978a49b8f4569ad607cd4946b759d90b285e39c0d4640c6b36ca7a3ddf2efc"},
+ {file = "httptools-0.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dc6a8e399e15ea525305a2ddba998b0af5caa2566bcd79dcbe8948181eeaff"},
+ {file = "httptools-0.6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab9ba8dcf59de5181f6be44a77458e45a578fc99c31510b8c65b7d5acc3cf490"},
+ {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc411e1c0a7dcd2f902c7c48cf079947a7e65b5485dea9decb82b9105ca71a43"},
+ {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d54efd20338ac52ba31e7da78e4a72570cf729fac82bc31ff9199bedf1dc7440"},
+ {file = "httptools-0.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:df959752a0c2748a65ab5387d08287abf6779ae9165916fe053e68ae1fbdc47f"},
+ {file = "httptools-0.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85797e37e8eeaa5439d33e556662cc370e474445d5fab24dcadc65a8ffb04003"},
+ {file = "httptools-0.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db353d22843cf1028f43c3651581e4bb49374d85692a85f95f7b9a130e1b2cab"},
+ {file = "httptools-0.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ffd262a73d7c28424252381a5b854c19d9de5f56f075445d33919a637e3547"},
+ {file = "httptools-0.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c346571fa50d2e9856a37d7cd9435a25e7fd15e236c397bf224afaa355fe9"},
+ {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aafe0f1918ed07b67c1e838f950b1c1fabc683030477e60b335649b8020e1076"},
+ {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0e563e54979e97b6d13f1bbc05a96109923e76b901f786a5eae36e99c01237bd"},
+ {file = "httptools-0.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:b799de31416ecc589ad79dd85a0b2657a8fe39327944998dea368c1d4c9e55e6"},
+ {file = "httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c"},
]
[package.extras]
-test = ["Cython (>=0.29.24,<0.30.0)"]
+test = ["Cython (>=0.29.24)"]
[[package]]
name = "httpx"
@@ -3150,20 +3157,20 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "uvicorn"
-version = "0.32.0"
+version = "0.32.1"
description = "The lightning-fast ASGI server."
optional = false
python-versions = ">=3.8"
files = [
- {file = "uvicorn-0.32.0-py3-none-any.whl", hash = "sha256:60b8f3a5ac027dcd31448f411ced12b5ef452c646f76f02f8cc3f25d8d26fd82"},
- {file = "uvicorn-0.32.0.tar.gz", hash = "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e"},
+ {file = "uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e"},
+ {file = "uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175"},
]
[package.dependencies]
click = ">=7.0"
colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""}
h11 = ">=0.8"
-httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""}
+httptools = {version = ">=0.6.3", optional = true, markers = "extra == \"standard\""}
python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
@@ -3172,7 +3179,7 @@ watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standar
websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""}
[package.extras]
-standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
+standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
[[package]]
name = "uvloop"
From dce6d86cbf32a4bc773ad43791c803224a42c706 Mon Sep 17 00:00:00 2001
From: Kenni Lund
Date: Tue, 26 Nov 2024 19:00:17 +0100
Subject: [PATCH 043/238] docs: Update API path in home-assistant.md (#4614)
---
docs/docs/documentation/community-guide/home-assistant.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/docs/documentation/community-guide/home-assistant.md b/docs/docs/documentation/community-guide/home-assistant.md
index b88b30dbd..d11db6a8d 100644
--- a/docs/docs/documentation/community-guide/home-assistant.md
+++ b/docs/docs/documentation/community-guide/home-assistant.md
@@ -24,7 +24,7 @@ Make sure the url and port (`http://mealie:9000` ) matches your installation's a
```yaml
rest:
- - resource: "http://mealie:9000/api/groups/mealplans/today"
+ - resource: "http://mealie:9000/api/households/mealplans/today"
method: GET
headers:
Authorization: Bearer <>
From d6de0d0b3d3ffd63a72ac75eec43c4802c3dcc31 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 26 Nov 2024 20:13:43 +0100
Subject: [PATCH 044/238] chore(deps): update dependency coverage to v7.6.8
(#4603)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 126 ++++++++++++++++++++++++++--------------------------
1 file changed, 63 insertions(+), 63 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index e5c431d40..a2a6647b2 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -417,73 +417,73 @@ files = [
[[package]]
name = "coverage"
-version = "7.6.7"
+version = "7.6.8"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.9"
files = [
- {file = "coverage-7.6.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:108bb458827765d538abcbf8288599fee07d2743357bdd9b9dad456c287e121e"},
- {file = "coverage-7.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c973b2fe4dc445cb865ab369df7521df9c27bf40715c837a113edaa2aa9faf45"},
- {file = "coverage-7.6.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c6b24007c4bcd0b19fac25763a7cac5035c735ae017e9a349b927cfc88f31c1"},
- {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acbb8af78f8f91b3b51f58f288c0994ba63c646bc1a8a22ad072e4e7e0a49f1c"},
- {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad32a981bcdedb8d2ace03b05e4fd8dace8901eec64a532b00b15217d3677dd2"},
- {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:34d23e28ccb26236718a3a78ba72744212aa383141961dd6825f6595005c8b06"},
- {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e25bacb53a8c7325e34d45dddd2f2fbae0dbc230d0e2642e264a64e17322a777"},
- {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af05bbba896c4472a29408455fe31b3797b4d8648ed0a2ccac03e074a77e2314"},
- {file = "coverage-7.6.7-cp310-cp310-win32.whl", hash = "sha256:796c9b107d11d2d69e1849b2dfe41730134b526a49d3acb98ca02f4985eeff7a"},
- {file = "coverage-7.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:987a8e3da7da4eed10a20491cf790589a8e5e07656b6dc22d3814c4d88faf163"},
- {file = "coverage-7.6.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e61b0e77ff4dddebb35a0e8bb5a68bf0f8b872407d8d9f0c726b65dfabe2469"},
- {file = "coverage-7.6.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a5407a75ca4abc20d6252efeb238377a71ce7bda849c26c7a9bece8680a5d99"},
- {file = "coverage-7.6.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df002e59f2d29e889c37abd0b9ee0d0e6e38c24f5f55d71ff0e09e3412a340ec"},
- {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673184b3156cba06154825f25af33baa2671ddae6343f23175764e65a8c4c30b"},
- {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69ad502f1a2243f739f5bd60565d14a278be58be4c137d90799f2c263e7049a"},
- {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60dcf7605c50ea72a14490d0756daffef77a5be15ed1b9fea468b1c7bda1bc3b"},
- {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9c2eb378bebb2c8f65befcb5147877fc1c9fbc640fc0aad3add759b5df79d55d"},
- {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c0317288f032221d35fa4cbc35d9f4923ff0dfd176c79c9b356e8ef8ef2dff4"},
- {file = "coverage-7.6.7-cp311-cp311-win32.whl", hash = "sha256:951aade8297358f3618a6e0660dc74f6b52233c42089d28525749fc8267dccd2"},
- {file = "coverage-7.6.7-cp311-cp311-win_amd64.whl", hash = "sha256:5e444b8e88339a2a67ce07d41faabb1d60d1004820cee5a2c2b54e2d8e429a0f"},
- {file = "coverage-7.6.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f07ff574986bc3edb80e2c36391678a271d555f91fd1d332a1e0f4b5ea4b6ea9"},
- {file = "coverage-7.6.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:49ed5ee4109258973630c1f9d099c7e72c5c36605029f3a91fe9982c6076c82b"},
- {file = "coverage-7.6.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3e8796434a8106b3ac025fd15417315d7a58ee3e600ad4dbcfddc3f4b14342c"},
- {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3b925300484a3294d1c70f6b2b810d6526f2929de954e5b6be2bf8caa1f12c1"},
- {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c42ec2c522e3ddd683dec5cdce8e62817afb648caedad9da725001fa530d354"},
- {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0266b62cbea568bd5e93a4da364d05de422110cbed5056d69339bd5af5685433"},
- {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e5f2a0f161d126ccc7038f1f3029184dbdf8f018230af17ef6fd6a707a5b881f"},
- {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c132b5a22821f9b143f87446805e13580b67c670a548b96da945a8f6b4f2efbb"},
- {file = "coverage-7.6.7-cp312-cp312-win32.whl", hash = "sha256:7c07de0d2a110f02af30883cd7dddbe704887617d5c27cf373362667445a4c76"},
- {file = "coverage-7.6.7-cp312-cp312-win_amd64.whl", hash = "sha256:fd49c01e5057a451c30c9b892948976f5d38f2cbd04dc556a82743ba8e27ed8c"},
- {file = "coverage-7.6.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46f21663e358beae6b368429ffadf14ed0a329996248a847a4322fb2e35d64d3"},
- {file = "coverage-7.6.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40cca284c7c310d622a1677f105e8507441d1bb7c226f41978ba7c86979609ab"},
- {file = "coverage-7.6.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77256ad2345c29fe59ae861aa11cfc74579c88d4e8dbf121cbe46b8e32aec808"},
- {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87ea64b9fa52bf395272e54020537990a28078478167ade6c61da7ac04dc14bc"},
- {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d608a7808793e3615e54e9267519351c3ae204a6d85764d8337bd95993581a8"},
- {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdd94501d65adc5c24f8a1a0eda110452ba62b3f4aeaba01e021c1ed9cb8f34a"},
- {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82c809a62e953867cf57e0548c2b8464207f5f3a6ff0e1e961683e79b89f2c55"},
- {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb684694e99d0b791a43e9fc0fa58efc15ec357ac48d25b619f207c41f2fd384"},
- {file = "coverage-7.6.7-cp313-cp313-win32.whl", hash = "sha256:963e4a08cbb0af6623e61492c0ec4c0ec5c5cf74db5f6564f98248d27ee57d30"},
- {file = "coverage-7.6.7-cp313-cp313-win_amd64.whl", hash = "sha256:14045b8bfd5909196a90da145a37f9d335a5d988a83db34e80f41e965fb7cb42"},
- {file = "coverage-7.6.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f2c7a045eef561e9544359a0bf5784b44e55cefc7261a20e730baa9220c83413"},
- {file = "coverage-7.6.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dd4e4a49d9c72a38d18d641135d2fb0bdf7b726ca60a103836b3d00a1182acd"},
- {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c95e0fa3d1547cb6f021ab72f5c23402da2358beec0a8e6d19a368bd7b0fb37"},
- {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f63e21ed474edd23f7501f89b53280014436e383a14b9bd77a648366c81dce7b"},
- {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead9b9605c54d15be228687552916c89c9683c215370c4a44f1f217d2adcc34d"},
- {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0573f5cbf39114270842d01872952d301027d2d6e2d84013f30966313cadb529"},
- {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e2c8e3384c12dfa19fa9a52f23eb091a8fad93b5b81a41b14c17c78e23dd1d8b"},
- {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:70a56a2ec1869e6e9fa69ef6b76b1a8a7ef709972b9cc473f9ce9d26b5997ce3"},
- {file = "coverage-7.6.7-cp313-cp313t-win32.whl", hash = "sha256:dbba8210f5067398b2c4d96b4e64d8fb943644d5eb70be0d989067c8ca40c0f8"},
- {file = "coverage-7.6.7-cp313-cp313t-win_amd64.whl", hash = "sha256:dfd14bcae0c94004baba5184d1c935ae0d1231b8409eb6c103a5fd75e8ecdc56"},
- {file = "coverage-7.6.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37a15573f988b67f7348916077c6d8ad43adb75e478d0910957394df397d2874"},
- {file = "coverage-7.6.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6cce5c76985f81da3769c52203ee94722cd5d5889731cd70d31fee939b74bf0"},
- {file = "coverage-7.6.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ab9763d291a17b527ac6fd11d1a9a9c358280adb320e9c2672a97af346ac2c"},
- {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cf96ceaa275f071f1bea3067f8fd43bec184a25a962c754024c973af871e1b7"},
- {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aee9cf6b0134d6f932d219ce253ef0e624f4fa588ee64830fcba193269e4daa3"},
- {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2bc3e45c16564cc72de09e37413262b9f99167803e5e48c6156bccdfb22c8327"},
- {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:623e6965dcf4e28a3debaa6fcf4b99ee06d27218f46d43befe4db1c70841551c"},
- {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:850cfd2d6fc26f8346f422920ac204e1d28814e32e3a58c19c91980fa74d8289"},
- {file = "coverage-7.6.7-cp39-cp39-win32.whl", hash = "sha256:c296263093f099da4f51b3dff1eff5d4959b527d4f2f419e16508c5da9e15e8c"},
- {file = "coverage-7.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:90746521206c88bdb305a4bf3342b1b7316ab80f804d40c536fc7d329301ee13"},
- {file = "coverage-7.6.7-pp39.pp310-none-any.whl", hash = "sha256:0ddcb70b3a3a57581b450571b31cb774f23eb9519c2aaa6176d3a84c9fc57671"},
- {file = "coverage-7.6.7.tar.gz", hash = "sha256:d79d4826e41441c9a118ff045e4bccb9fdbdcb1d02413e7ea6eb5c87b5439d24"},
+ {file = "coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50"},
+ {file = "coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf"},
+ {file = "coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee"},
+ {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6"},
+ {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d"},
+ {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331"},
+ {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638"},
+ {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed"},
+ {file = "coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e"},
+ {file = "coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a"},
+ {file = "coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4"},
+ {file = "coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94"},
+ {file = "coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4"},
+ {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1"},
+ {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb"},
+ {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8"},
+ {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a"},
+ {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0"},
+ {file = "coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801"},
+ {file = "coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"},
+ {file = "coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee"},
+ {file = "coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a"},
+ {file = "coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d"},
+ {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb"},
+ {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649"},
+ {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787"},
+ {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c"},
+ {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443"},
+ {file = "coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad"},
+ {file = "coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4"},
+ {file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"},
+ {file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"},
+ {file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"},
+ {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002"},
+ {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3"},
+ {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"},
+ {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"},
+ {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"},
+ {file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"},
+ {file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"},
+ {file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"},
+ {file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"},
+ {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"},
+ {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf"},
+ {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5"},
+ {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"},
+ {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"},
+ {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"},
+ {file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"},
+ {file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"},
+ {file = "coverage-7.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e"},
+ {file = "coverage-7.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c"},
+ {file = "coverage-7.6.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0"},
+ {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779"},
+ {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92"},
+ {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4"},
+ {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc"},
+ {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea"},
+ {file = "coverage-7.6.8-cp39-cp39-win32.whl", hash = "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e"},
+ {file = "coverage-7.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076"},
+ {file = "coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce"},
+ {file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"},
]
[package.extras]
From 79e712503ca2b6fd54ffef273a4f833fe4feacb3 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 27 Nov 2024 10:16:29 +0000
Subject: [PATCH 045/238] docs(auto): Update image tag, for release v2.3.0
(#4617)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
---
.../getting-started/installation/installation-checklist.md | 2 +-
.../docs/documentation/getting-started/installation/postgres.md | 2 +-
docs/docs/documentation/getting-started/installation/sqlite.md | 2 +-
frontend/package.json | 2 +-
pyproject.toml | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/docs/documentation/getting-started/installation/installation-checklist.md b/docs/docs/documentation/getting-started/installation/installation-checklist.md
index 417105b0e..f941a7e0d 100644
--- a/docs/docs/documentation/getting-started/installation/installation-checklist.md
+++ b/docs/docs/documentation/getting-started/installation/installation-checklist.md
@@ -31,7 +31,7 @@ To deploy mealie on your local network, it is highly recommended to use Docker t
We've gone through a few versions of Mealie v1 deployment targets. We have settled on a single container deployment, and we've begun publishing the nightly container on github containers. If you're looking to move from the old nightly (split containers _or_ the omni image) to the new nightly, there are a few things you need to do:
1. Take a backup just in case!
-2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v2.2.0`
+2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v2.3.0`
3. Take the external port from the frontend container and set that as the port mapped to port `9000` on the new container. The frontend is now served on port 9000 from the new container, so it will need to be mapped for you to have access.
4. Restart the container
diff --git a/docs/docs/documentation/getting-started/installation/postgres.md b/docs/docs/documentation/getting-started/installation/postgres.md
index 4fdfb4cf5..b4a9d35d5 100644
--- a/docs/docs/documentation/getting-started/installation/postgres.md
+++ b/docs/docs/documentation/getting-started/installation/postgres.md
@@ -7,7 +7,7 @@ PostgreSQL might be considered if you need to support many concurrent users. In
```yaml
services:
mealie:
- image: ghcr.io/mealie-recipes/mealie:v2.2.0 # (3)
+ image: ghcr.io/mealie-recipes/mealie:v2.3.0 # (3)
container_name: mealie
restart: always
ports:
diff --git a/docs/docs/documentation/getting-started/installation/sqlite.md b/docs/docs/documentation/getting-started/installation/sqlite.md
index 1f7ccef4b..f1b22bcb9 100644
--- a/docs/docs/documentation/getting-started/installation/sqlite.md
+++ b/docs/docs/documentation/getting-started/installation/sqlite.md
@@ -11,7 +11,7 @@ SQLite is a popular, open source, self-contained, zero-configuration database th
```yaml
services:
mealie:
- image: ghcr.io/mealie-recipes/mealie:v2.2.0 # (3)
+ image: ghcr.io/mealie-recipes/mealie:v2.3.0 # (3)
container_name: mealie
restart: always
ports:
diff --git a/frontend/package.json b/frontend/package.json
index 673b06da1..3765cf12d 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "mealie",
-"version": "2.2.0",
+"version": "2.3.0",
"private": true,
"scripts": {
"dev": "nuxt",
diff --git a/pyproject.toml b/pyproject.toml
index 9974f3450..9684f9820 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,7 +3,7 @@ authors = ["Hayden "]
description = "A Recipe Manager"
license = "AGPL"
name = "mealie"
-version = "2.2.0"
+version = "2.3.0"
[tool.poetry.scripts]
start = "mealie.app:main"
From 12a90b63348add23225341c6eed212a299d8e4fe Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 27 Nov 2024 23:07:04 +0100
Subject: [PATCH 046/238] fix(deps): update dependency openai to v1.55.2
(#4618)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index a2a6647b2..4e1f8ad39 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1603,13 +1603,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "openai"
-version = "1.55.1"
+version = "1.55.2"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.8"
files = [
- {file = "openai-1.55.1-py3-none-any.whl", hash = "sha256:d10d96a4f9dc5f05d38dea389119ec8dcd24bc9698293c8357253c601b4a77a5"},
- {file = "openai-1.55.1.tar.gz", hash = "sha256:471324321e7739214f16a544e801947a046d3c5d516fae8719a317234e4968d3"},
+ {file = "openai-1.55.2-py3-none-any.whl", hash = "sha256:3027c7fa4a33ed759f4a3d076093fcfa1c55658660c889bec33f651e2dc77922"},
+ {file = "openai-1.55.2.tar.gz", hash = "sha256:5cc0b1162b65dcdf670b4b41448f18dd470d2724ca04821ab1e86b6b4e88650b"},
]
[package.dependencies]
From c0a4ae27b0c2071b7dc4b67d40b67973f86dd6a0 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 28 Nov 2024 01:37:21 -0600
Subject: [PATCH 047/238] fix(deps): update dependency pyjwt to v2.10.1 (#4620)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 4e1f8ad39..d27608aeb 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2242,13 +2242,13 @@ files = [
[[package]]
name = "pyjwt"
-version = "2.10.0"
+version = "2.10.1"
description = "JSON Web Token implementation in Python"
optional = false
python-versions = ">=3.9"
files = [
- {file = "PyJWT-2.10.0-py3-none-any.whl", hash = "sha256:543b77207db656de204372350926bed5a86201c4cbff159f623f79c7bb487a15"},
- {file = "pyjwt-2.10.0.tar.gz", hash = "sha256:7628a7eb7938959ac1b26e819a1df0fd3259505627b575e4bad6d08f76db695c"},
+ {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"},
+ {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"},
]
[package.extras]
From 8821653dcb0d07c2d6985fbe84c098e2bf56d75f Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 29 Nov 2024 23:34:53 -0600
Subject: [PATCH 048/238] fix(deps): update dependency pydantic to v2.10.2
(#4588)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 204 +++++++++++++++++++++++++++-------------------------
1 file changed, 106 insertions(+), 98 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index d27608aeb..c9e1a8e44 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2055,22 +2055,19 @@ files = [
[[package]]
name = "pydantic"
-version = "2.9.2"
+version = "2.10.2"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"},
- {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"},
+ {file = "pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e"},
+ {file = "pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa"},
]
[package.dependencies]
annotated-types = ">=0.6.0"
-pydantic-core = "2.23.4"
-typing-extensions = [
- {version = ">=4.6.1", markers = "python_version < \"3.13\""},
- {version = ">=4.12.2", markers = "python_version >= \"3.13\""},
-]
+pydantic-core = "2.27.1"
+typing-extensions = ">=4.12.2"
[package.extras]
email = ["email-validator (>=2.0.0)"]
@@ -2078,100 +2075,111 @@ timezone = ["tzdata"]
[[package]]
name = "pydantic-core"
-version = "2.23.4"
+version = "2.27.1"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"},
- {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"},
- {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"},
- {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"},
- {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"},
- {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"},
- {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"},
- {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"},
- {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"},
- {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"},
- {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"},
- {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"},
- {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"},
- {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"},
- {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"},
- {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"},
- {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"},
- {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"},
- {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"},
- {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"},
- {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"},
- {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"},
- {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"},
- {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"},
- {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"},
- {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"},
- {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"},
- {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"},
- {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"},
- {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"},
- {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"},
- {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"},
- {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"},
- {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"},
- {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"},
- {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"},
- {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"},
- {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"},
- {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"},
- {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"},
- {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"},
- {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"},
- {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"},
- {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"},
- {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"},
- {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"},
- {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"},
- {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"},
- {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"},
- {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"},
- {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"},
- {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"},
- {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"},
- {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"},
- {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"},
- {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"},
- {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"},
- {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"},
- {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"},
- {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"},
- {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"},
- {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"},
- {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"},
- {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"},
- {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"},
- {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"},
- {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"},
- {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"},
- {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"},
- {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"},
- {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"},
- {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"},
- {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"},
- {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"},
- {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"},
- {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"},
- {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"},
- {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"},
- {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"},
- {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"},
- {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"},
- {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"},
- {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"},
- {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"},
- {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"},
- {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"},
- {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"},
- {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"},
- {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"},
+ {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"},
+ {file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"},
+ {file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"},
+ {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"},
+ {file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"},
+ {file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"},
+ {file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"},
+ {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"},
+ {file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"},
+ {file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"},
+ {file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"},
+ {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"},
+ {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"},
+ {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"},
+ {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"},
+ {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"},
+ {file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"},
+ {file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"},
+ {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"},
+ {file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"},
+ {file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"},
+ {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"},
+ {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"},
+ {file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"},
]
[package.dependencies]
From 29f47d1722072b5929480ce34345388190a77741 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 30 Nov 2024 05:46:53 +0000
Subject: [PATCH 049/238] fix(deps): update dependency orjson to v3.10.12
(#4601)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 135 +++++++++++++++++++++++++++++-----------------------
1 file changed, 76 insertions(+), 59 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index c9e1a8e44..d282cfc92 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1627,69 +1627,86 @@ datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"]
[[package]]
name = "orjson"
-version = "3.10.11"
+version = "3.10.12"
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
optional = false
python-versions = ">=3.8"
files = [
- {file = "orjson-3.10.11-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6dade64687f2bd7c090281652fe18f1151292d567a9302b34c2dbb92a3872f1f"},
- {file = "orjson-3.10.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82f07c550a6ccd2b9290849b22316a609023ed851a87ea888c0456485a7d196a"},
- {file = "orjson-3.10.11-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd9a187742d3ead9df2e49240234d728c67c356516cf4db018833a86f20ec18c"},
- {file = "orjson-3.10.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77b0fed6f209d76c1c39f032a70df2d7acf24b1812ca3e6078fd04e8972685a3"},
- {file = "orjson-3.10.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63fc9d5fe1d4e8868f6aae547a7b8ba0a2e592929245fff61d633f4caccdcdd6"},
- {file = "orjson-3.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65cd3e3bb4fbb4eddc3c1e8dce10dc0b73e808fcb875f9fab40c81903dd9323e"},
- {file = "orjson-3.10.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f67c570602300c4befbda12d153113b8974a3340fdcf3d6de095ede86c06d92"},
- {file = "orjson-3.10.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1f39728c7f7d766f1f5a769ce4d54b5aaa4c3f92d5b84817053cc9995b977acc"},
- {file = "orjson-3.10.11-cp310-none-win32.whl", hash = "sha256:1789d9db7968d805f3d94aae2c25d04014aae3a2fa65b1443117cd462c6da647"},
- {file = "orjson-3.10.11-cp310-none-win_amd64.whl", hash = "sha256:5576b1e5a53a5ba8f8df81872bb0878a112b3ebb1d392155f00f54dd86c83ff6"},
- {file = "orjson-3.10.11-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1444f9cb7c14055d595de1036f74ecd6ce15f04a715e73f33bb6326c9cef01b6"},
- {file = "orjson-3.10.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdec57fe3b4bdebcc08a946db3365630332dbe575125ff3d80a3272ebd0ddafe"},
- {file = "orjson-3.10.11-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4eed32f33a0ea6ef36ccc1d37f8d17f28a1d6e8eefae5928f76aff8f1df85e67"},
- {file = "orjson-3.10.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80df27dd8697242b904f4ea54820e2d98d3f51f91e97e358fc13359721233e4b"},
- {file = "orjson-3.10.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:705f03cee0cb797256d54de6695ef219e5bc8c8120b6654dd460848d57a9af3d"},
- {file = "orjson-3.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03246774131701de8e7059b2e382597da43144a9a7400f178b2a32feafc54bd5"},
- {file = "orjson-3.10.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8b5759063a6c940a69c728ea70d7c33583991c6982915a839c8da5f957e0103a"},
- {file = "orjson-3.10.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:677f23e32491520eebb19c99bb34675daf5410c449c13416f7f0d93e2cf5f981"},
- {file = "orjson-3.10.11-cp311-none-win32.whl", hash = "sha256:a11225d7b30468dcb099498296ffac36b4673a8398ca30fdaec1e6c20df6aa55"},
- {file = "orjson-3.10.11-cp311-none-win_amd64.whl", hash = "sha256:df8c677df2f9f385fcc85ab859704045fa88d4668bc9991a527c86e710392bec"},
- {file = "orjson-3.10.11-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:360a4e2c0943da7c21505e47cf6bd725588962ff1d739b99b14e2f7f3545ba51"},
- {file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:496e2cb45de21c369079ef2d662670a4892c81573bcc143c4205cae98282ba97"},
- {file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7dfa8db55c9792d53c5952900c6a919cfa377b4f4534c7a786484a6a4a350c19"},
- {file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51f3382415747e0dbda9dade6f1e1a01a9d37f630d8c9049a8ed0e385b7a90c0"},
- {file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f35a1b9f50a219f470e0e497ca30b285c9f34948d3c8160d5ad3a755d9299433"},
- {file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f3b7c5803138e67028dde33450e054c87e0703afbe730c105f1fcd873496d5"},
- {file = "orjson-3.10.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f91d9eb554310472bd09f5347950b24442600594c2edc1421403d7610a0998fd"},
- {file = "orjson-3.10.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dfbb2d460a855c9744bbc8e36f9c3a997c4b27d842f3d5559ed54326e6911f9b"},
- {file = "orjson-3.10.11-cp312-none-win32.whl", hash = "sha256:d4a62c49c506d4d73f59514986cadebb7e8d186ad510c518f439176cf8d5359d"},
- {file = "orjson-3.10.11-cp312-none-win_amd64.whl", hash = "sha256:f1eec3421a558ff7a9b010a6c7effcfa0ade65327a71bb9b02a1c3b77a247284"},
- {file = "orjson-3.10.11-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c46294faa4e4d0eb73ab68f1a794d2cbf7bab33b1dda2ac2959ffb7c61591899"},
- {file = "orjson-3.10.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52e5834d7d6e58a36846e059d00559cb9ed20410664f3ad156cd2cc239a11230"},
- {file = "orjson-3.10.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2fc947e5350fdce548bfc94f434e8760d5cafa97fb9c495d2fef6757aa02ec0"},
- {file = "orjson-3.10.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0efabbf839388a1dab5b72b5d3baedbd6039ac83f3b55736eb9934ea5494d258"},
- {file = "orjson-3.10.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3f29634260708c200c4fe148e42b4aae97d7b9fee417fbdd74f8cfc265f15b0"},
- {file = "orjson-3.10.11-cp313-none-win32.whl", hash = "sha256:1a1222ffcee8a09476bbdd5d4f6f33d06d0d6642df2a3d78b7a195ca880d669b"},
- {file = "orjson-3.10.11-cp313-none-win_amd64.whl", hash = "sha256:bc274ac261cc69260913b2d1610760e55d3c0801bb3457ba7b9004420b6b4270"},
- {file = "orjson-3.10.11-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:19b3763e8bbf8ad797df6b6b5e0fc7c843ec2e2fc0621398534e0c6400098f87"},
- {file = "orjson-3.10.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1be83a13312e5e58d633580c5eb8d0495ae61f180da2722f20562974188af205"},
- {file = "orjson-3.10.11-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:afacfd1ab81f46dedd7f6001b6d4e8de23396e4884cd3c3436bd05defb1a6446"},
- {file = "orjson-3.10.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb4d0bea56bba596723d73f074c420aec3b2e5d7d30698bc56e6048066bd560c"},
- {file = "orjson-3.10.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96ed1de70fcb15d5fed529a656df29f768187628727ee2788344e8a51e1c1350"},
- {file = "orjson-3.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bfb30c891b530f3f80e801e3ad82ef150b964e5c38e1fb8482441c69c35c61c"},
- {file = "orjson-3.10.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d496c74fc2b61341e3cefda7eec21b7854c5f672ee350bc55d9a4997a8a95204"},
- {file = "orjson-3.10.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:655a493bac606655db9a47fe94d3d84fc7f3ad766d894197c94ccf0c5408e7d3"},
- {file = "orjson-3.10.11-cp38-none-win32.whl", hash = "sha256:b9546b278c9fb5d45380f4809e11b4dd9844ca7aaf1134024503e134ed226161"},
- {file = "orjson-3.10.11-cp38-none-win_amd64.whl", hash = "sha256:b592597fe551d518f42c5a2eb07422eb475aa8cfdc8c51e6da7054b836b26782"},
- {file = "orjson-3.10.11-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c95f2ecafe709b4e5c733b5e2768ac569bed308623c85806c395d9cca00e08af"},
- {file = "orjson-3.10.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80c00d4acded0c51c98754fe8218cb49cb854f0f7eb39ea4641b7f71732d2cb7"},
- {file = "orjson-3.10.11-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:461311b693d3d0a060439aa669c74f3603264d4e7a08faa68c47ae5a863f352d"},
- {file = "orjson-3.10.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52ca832f17d86a78cbab86cdc25f8c13756ebe182b6fc1a97d534051c18a08de"},
- {file = "orjson-3.10.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c57ea78a753812f528178aa2f1c57da633754c91d2124cb28991dab4c79a54"},
- {file = "orjson-3.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7fcfc6f7ca046383fb954ba528587e0f9336828b568282b27579c49f8e16aad"},
- {file = "orjson-3.10.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:86b9dd983857970c29e4c71bb3e95ff085c07d3e83e7c46ebe959bac07ebd80b"},
- {file = "orjson-3.10.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4d83f87582d223e54efb2242a79547611ba4ebae3af8bae1e80fa9a0af83bb7f"},
- {file = "orjson-3.10.11-cp39-none-win32.whl", hash = "sha256:9fd0ad1c129bc9beb1154c2655f177620b5beaf9a11e0d10bac63ef3fce96950"},
- {file = "orjson-3.10.11-cp39-none-win_amd64.whl", hash = "sha256:10f416b2a017c8bd17f325fb9dee1fb5cdd7a54e814284896b7c3f2763faa017"},
- {file = "orjson-3.10.11.tar.gz", hash = "sha256:e35b6d730de6384d5b2dab5fd23f0d76fae8bbc8c353c2f78210aa5fa4beb3ef"},
+ {file = "orjson-3.10.12-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ece01a7ec71d9940cc654c482907a6b65df27251255097629d0dea781f255c6d"},
+ {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34ec9aebc04f11f4b978dd6caf697a2df2dd9b47d35aa4cc606cabcb9df69d7"},
+ {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd6ec8658da3480939c79b9e9e27e0db31dffcd4ba69c334e98c9976ac29140e"},
+ {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17e6baf4cf01534c9de8a16c0c611f3d94925d1701bf5f4aff17003677d8ced"},
+ {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6402ebb74a14ef96f94a868569f5dccf70d791de49feb73180eb3c6fda2ade56"},
+ {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0000758ae7c7853e0a4a6063f534c61656ebff644391e1f81698c1b2d2fc8cd2"},
+ {file = "orjson-3.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:888442dcee99fd1e5bd37a4abb94930915ca6af4db50e23e746cdf4d1e63db13"},
+ {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c1f7a3ce79246aa0e92f5458d86c54f257fb5dfdc14a192651ba7ec2c00f8a05"},
+ {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:802a3935f45605c66fb4a586488a38af63cb37aaad1c1d94c982c40dcc452e85"},
+ {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1da1ef0113a2be19bb6c557fb0ec2d79c92ebd2fed4cfb1b26bab93f021fb885"},
+ {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a3273e99f367f137d5b3fecb5e9f45bcdbfac2a8b2f32fbc72129bbd48789c2"},
+ {file = "orjson-3.10.12-cp310-none-win32.whl", hash = "sha256:475661bf249fd7907d9b0a2a2421b4e684355a77ceef85b8352439a9163418c3"},
+ {file = "orjson-3.10.12-cp310-none-win_amd64.whl", hash = "sha256:87251dc1fb2b9e5ab91ce65d8f4caf21910d99ba8fb24b49fd0c118b2362d509"},
+ {file = "orjson-3.10.12-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a734c62efa42e7df94926d70fe7d37621c783dea9f707a98cdea796964d4cf74"},
+ {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:750f8b27259d3409eda8350c2919a58b0cfcd2054ddc1bd317a643afc646ef23"},
+ {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb52c22bfffe2857e7aa13b4622afd0dd9d16ea7cc65fd2bf318d3223b1b6252"},
+ {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:440d9a337ac8c199ff8251e100c62e9488924c92852362cd27af0e67308c16ef"},
+ {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e15c06491c69997dfa067369baab3bf094ecb74be9912bdc4339972323f252"},
+ {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:362d204ad4b0b8724cf370d0cd917bb2dc913c394030da748a3bb632445ce7c4"},
+ {file = "orjson-3.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b57cbb4031153db37b41622eac67329c7810e5f480fda4cfd30542186f006ae"},
+ {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:165c89b53ef03ce0d7c59ca5c82fa65fe13ddf52eeb22e859e58c237d4e33b9b"},
+ {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5dee91b8dfd54557c1a1596eb90bcd47dbcd26b0baaed919e6861f076583e9da"},
+ {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a4e1cfb72de6f905bdff061172adfb3caf7a4578ebf481d8f0530879476c07"},
+ {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:038d42c7bc0606443459b8fe2d1f121db474c49067d8d14c6a075bbea8bf14dd"},
+ {file = "orjson-3.10.12-cp311-none-win32.whl", hash = "sha256:03b553c02ab39bed249bedd4abe37b2118324d1674e639b33fab3d1dafdf4d79"},
+ {file = "orjson-3.10.12-cp311-none-win_amd64.whl", hash = "sha256:8b8713b9e46a45b2af6b96f559bfb13b1e02006f4242c156cbadef27800a55a8"},
+ {file = "orjson-3.10.12-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53206d72eb656ca5ac7d3a7141e83c5bbd3ac30d5eccfe019409177a57634b0d"},
+ {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac8010afc2150d417ebda810e8df08dd3f544e0dd2acab5370cfa6bcc0662f8f"},
+ {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed459b46012ae950dd2e17150e838ab08215421487371fa79d0eced8d1461d70"},
+ {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dcb9673f108a93c1b52bfc51b0af422c2d08d4fc710ce9c839faad25020bb69"},
+ {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22a51ae77680c5c4652ebc63a83d5255ac7d65582891d9424b566fb3b5375ee9"},
+ {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910fdf2ac0637b9a77d1aad65f803bac414f0b06f720073438a7bd8906298192"},
+ {file = "orjson-3.10.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:24ce85f7100160936bc2116c09d1a8492639418633119a2224114f67f63a4559"},
+ {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a76ba5fc8dd9c913640292df27bff80a685bed3a3c990d59aa6ce24c352f8fc"},
+ {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ff70ef093895fd53f4055ca75f93f047e088d1430888ca1229393a7c0521100f"},
+ {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4244b7018b5753ecd10a6d324ec1f347da130c953a9c88432c7fbc8875d13be"},
+ {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16135ccca03445f37921fa4b585cff9a58aa8d81ebcb27622e69bfadd220b32c"},
+ {file = "orjson-3.10.12-cp312-none-win32.whl", hash = "sha256:2d879c81172d583e34153d524fcba5d4adafbab8349a7b9f16ae511c2cee8708"},
+ {file = "orjson-3.10.12-cp312-none-win_amd64.whl", hash = "sha256:fc23f691fa0f5c140576b8c365bc942d577d861a9ee1142e4db468e4e17094fb"},
+ {file = "orjson-3.10.12-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:47962841b2a8aa9a258b377f5188db31ba49af47d4003a32f55d6f8b19006543"},
+ {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6334730e2532e77b6054e87ca84f3072bee308a45a452ea0bffbbbc40a67e296"},
+ {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:accfe93f42713c899fdac2747e8d0d5c659592df2792888c6c5f829472e4f85e"},
+ {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7974c490c014c48810d1dede6c754c3cc46598da758c25ca3b4001ac45b703f"},
+ {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3f250ce7727b0b2682f834a3facff88e310f52f07a5dcfd852d99637d386e79e"},
+ {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f31422ff9486ae484f10ffc51b5ab2a60359e92d0716fcce1b3593d7bb8a9af6"},
+ {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5f29c5d282bb2d577c2a6bbde88d8fdcc4919c593f806aac50133f01b733846e"},
+ {file = "orjson-3.10.12-cp313-none-win32.whl", hash = "sha256:f45653775f38f63dc0e6cd4f14323984c3149c05d6007b58cb154dd080ddc0dc"},
+ {file = "orjson-3.10.12-cp313-none-win_amd64.whl", hash = "sha256:229994d0c376d5bdc91d92b3c9e6be2f1fbabd4cc1b59daae1443a46ee5e9825"},
+ {file = "orjson-3.10.12-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7d69af5b54617a5fac5c8e5ed0859eb798e2ce8913262eb522590239db6c6763"},
+ {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ed119ea7d2953365724a7059231a44830eb6bbb0cfead33fcbc562f5fd8f935"},
+ {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5fc1238ef197e7cad5c91415f524aaa51e004be5a9b35a1b8a84ade196f73f"},
+ {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43509843990439b05f848539d6f6198d4ac86ff01dd024b2f9a795c0daeeab60"},
+ {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f72e27a62041cfb37a3de512247ece9f240a561e6c8662276beaf4d53d406db4"},
+ {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a904f9572092bb6742ab7c16c623f0cdccbad9eeb2d14d4aa06284867bddd31"},
+ {file = "orjson-3.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:855c0833999ed5dc62f64552db26f9be767434917d8348d77bacaab84f787d7b"},
+ {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:897830244e2320f6184699f598df7fb9db9f5087d6f3f03666ae89d607e4f8ed"},
+ {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:0b32652eaa4a7539f6f04abc6243619c56f8530c53bf9b023e1269df5f7816dd"},
+ {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:36b4aa31e0f6a1aeeb6f8377769ca5d125db000f05c20e54163aef1d3fe8e833"},
+ {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5535163054d6cbf2796f93e4f0dbc800f61914c0e3c4ed8499cf6ece22b4a3da"},
+ {file = "orjson-3.10.12-cp38-none-win32.whl", hash = "sha256:90a5551f6f5a5fa07010bf3d0b4ca2de21adafbbc0af6cb700b63cd767266cb9"},
+ {file = "orjson-3.10.12-cp38-none-win_amd64.whl", hash = "sha256:703a2fb35a06cdd45adf5d733cf613cbc0cb3ae57643472b16bc22d325b5fb6c"},
+ {file = "orjson-3.10.12-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f29de3ef71a42a5822765def1febfb36e0859d33abf5c2ad240acad5c6a1b78d"},
+ {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de365a42acc65d74953f05e4772c974dad6c51cfc13c3240899f534d611be967"},
+ {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a5a0158648a67ff0004cb0df5df7dcc55bfc9ca154d9c01597a23ad54c8d0c"},
+ {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c47ce6b8d90fe9646a25b6fb52284a14ff215c9595914af63a5933a49972ce36"},
+ {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0eee4c2c5bfb5c1b47a5db80d2ac7aaa7e938956ae88089f098aff2c0f35d5d8"},
+ {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d3081bbe8b86587eb5c98a73b97f13d8f9fea685cf91a579beddacc0d10566"},
+ {file = "orjson-3.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c23a6e90383884068bc2dba83d5222c9fcc3b99a0ed2411d38150734236755"},
+ {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5472be7dc3269b4b52acba1433dac239215366f89dc1d8d0e64029abac4e714e"},
+ {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7319cda750fca96ae5973efb31b17d97a5c5225ae0bc79bf5bf84df9e1ec2ab6"},
+ {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:74d5ca5a255bf20b8def6a2b96b1e18ad37b4a122d59b154c458ee9494377f80"},
+ {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ff31d22ecc5fb85ef62c7d4afe8301d10c558d00dd24274d4bbe464380d3cd69"},
+ {file = "orjson-3.10.12-cp39-none-win32.whl", hash = "sha256:c22c3ea6fba91d84fcb4cda30e64aff548fcf0c44c876e681f47d61d24b12e6b"},
+ {file = "orjson-3.10.12-cp39-none-win_amd64.whl", hash = "sha256:be604f60d45ace6b0b33dd990a66b4526f1a7a186ac411c942674625456ca548"},
+ {file = "orjson-3.10.12.tar.gz", hash = "sha256:0a78bbda3aea0f9f079057ee1ee8a1ecf790d4f1af88dd67493c6b8ee52506ff"},
]
[[package]]
From 3b3d8fc89d1c79ebfc14f450de352e2570c95f72 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 30 Nov 2024 06:01:00 +0000
Subject: [PATCH 050/238] fix(deps): update dependency httpx to ^0.28.0 (#4621)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 9 ++++-----
pyproject.toml | 2 +-
2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index d282cfc92..7fd18d56c 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -894,13 +894,13 @@ test = ["Cython (>=0.29.24)"]
[[package]]
name = "httpx"
-version = "0.27.2"
+version = "0.28.0"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
files = [
- {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"},
- {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"},
+ {file = "httpx-0.28.0-py3-none-any.whl", hash = "sha256:dc0b419a0cfeb6e8b34e85167c0da2671206f5095f1baa9663d23bcfd6b535fc"},
+ {file = "httpx-0.28.0.tar.gz", hash = "sha256:0858d3bab51ba7e386637f22a61d8ccddaeec5f3fe4209da3a6168dbb91573e0"},
]
[package.dependencies]
@@ -908,7 +908,6 @@ anyio = "*"
certifi = "*"
httpcore = "==1.*"
idna = "*"
-sniffio = "*"
[package.extras]
brotli = ["brotli", "brotlicffi"]
@@ -3446,4 +3445,4 @@ pgsql = ["psycopg2-binary"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
-content-hash = "4d6f1e1665de07327fd42e27bddcff9b5e6b3f7b64bcf8782490e2acdaaf2b9a"
+content-hash = "915ee7c0ddd6a78b2403ff7754714da1b39bb7d85c570150bd98737a165ad090"
diff --git a/pyproject.toml b/pyproject.toml
index 9684f9820..32a568d45 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,7 +21,7 @@ apprise = "^1.4.5"
bcrypt = "^4.0.1"
extruct = "^0.18.0"
fastapi = "^0.115.0"
-httpx = "^0.27.0"
+httpx = "^0.28.0"
lxml = "^5.0.0"
orjson = "^3.8.0"
psycopg2-binary = { version = "^2.9.1", optional = true }
From 48549893bc360949c7fe468744805328fa3c7a11 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 30 Nov 2024 06:12:02 +0000
Subject: [PATCH 051/238] fix(deps): update dependency openai to v1.55.3
(#4622)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 7fd18d56c..71068e81d 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1602,13 +1602,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "openai"
-version = "1.55.2"
+version = "1.55.3"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.8"
files = [
- {file = "openai-1.55.2-py3-none-any.whl", hash = "sha256:3027c7fa4a33ed759f4a3d076093fcfa1c55658660c889bec33f651e2dc77922"},
- {file = "openai-1.55.2.tar.gz", hash = "sha256:5cc0b1162b65dcdf670b4b41448f18dd470d2724ca04821ab1e86b6b4e88650b"},
+ {file = "openai-1.55.3-py3-none-any.whl", hash = "sha256:2a235d0e1e312cd982f561b18c27692e253852f4e5fb6ccf08cb13540a9bdaa1"},
+ {file = "openai-1.55.3.tar.gz", hash = "sha256:547e85b94535469f137a779d8770c8c5adebd507c2cc6340ca401a7c4d5d16f0"},
]
[package.dependencies]
From f63196d5708e1ad8af76ada9af76e58a72ce4c10 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 30 Nov 2024 06:24:47 +0000
Subject: [PATCH 052/238] fix(deps): update dependency python-multipart to
^0.0.18 (#4623)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 8 ++++----
pyproject.toml | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 71068e81d..c43f835c8 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2442,13 +2442,13 @@ pyasn1_modules = ">=0.1.5"
[[package]]
name = "python-multipart"
-version = "0.0.17"
+version = "0.0.18"
description = "A streaming multipart parser for Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "python_multipart-0.0.17-py3-none-any.whl", hash = "sha256:15dc4f487e0a9476cc1201261188ee0940165cffc94429b6fc565c4d3045cb5d"},
- {file = "python_multipart-0.0.17.tar.gz", hash = "sha256:41330d831cae6e2f22902704ead2826ea038d0419530eadff3ea80175aec5538"},
+ {file = "python_multipart-0.0.18-py3-none-any.whl", hash = "sha256:efe91480f485f6a361427a541db4796f9e1591afc0fb8e7a4ba06bfbc6708996"},
+ {file = "python_multipart-0.0.18.tar.gz", hash = "sha256:7a68db60c8bfb82e460637fa4750727b45af1d5e2ed215593f917f64694d34fe"},
]
[[package]]
@@ -3445,4 +3445,4 @@ pgsql = ["psycopg2-binary"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
-content-hash = "915ee7c0ddd6a78b2403ff7754714da1b39bb7d85c570150bd98737a165ad090"
+content-hash = "a3be05804f6a0f7c1ae7afcbf6c92edc14171e4588f5f0c69f3101c1aac2fee8"
diff --git a/pyproject.toml b/pyproject.toml
index 32a568d45..840a5077c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -31,7 +31,7 @@ python = "^3.10"
python-dateutil = "^2.8.2"
python-dotenv = "^1.0.0"
python-ldap = "^3.3.1"
-python-multipart = "^0.0.17"
+python-multipart = "^0.0.18"
python-slugify = "^8.0.0"
recipe-scrapers = "^15.0.0"
requests = "^2.31.0"
From f4d23b9e1cd7d4fab74eeba4cee607f7ab61d693 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 30 Nov 2024 06:35:34 +0000
Subject: [PATCH 053/238] chore(deps): update dependency ruff to v0.8.1 (#4624)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 38 +++++++++++++++++++-------------------
1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index c43f835c8..1aa12d15e 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2844,29 +2844,29 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "ruff"
-version = "0.8.0"
+version = "0.8.1"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
- {file = "ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea"},
- {file = "ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b"},
- {file = "ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a"},
- {file = "ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99"},
- {file = "ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c"},
- {file = "ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9"},
- {file = "ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362"},
- {file = "ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df"},
- {file = "ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3"},
- {file = "ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c"},
- {file = "ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2"},
- {file = "ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70"},
- {file = "ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd"},
- {file = "ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426"},
- {file = "ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468"},
- {file = "ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f"},
- {file = "ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6"},
- {file = "ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44"},
+ {file = "ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5"},
+ {file = "ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087"},
+ {file = "ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209"},
+ {file = "ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871"},
+ {file = "ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1"},
+ {file = "ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5"},
+ {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d"},
+ {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26"},
+ {file = "ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1"},
+ {file = "ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c"},
+ {file = "ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa"},
+ {file = "ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540"},
+ {file = "ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9"},
+ {file = "ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5"},
+ {file = "ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790"},
+ {file = "ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6"},
+ {file = "ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737"},
+ {file = "ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f"},
]
[[package]]
From 2adbe5a196ea63b25907c37b34b9370c195f3201 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 30 Nov 2024 06:46:33 +0000
Subject: [PATCH 054/238] fix(deps): update dependency pillow-heif to ^0.21.0
(#4625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 96 +++++++++++++++++++++++++-------------------------
pyproject.toml | 2 +-
2 files changed, 49 insertions(+), 49 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 1aa12d15e..433697fba 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1847,57 +1847,57 @@ xmp = ["defusedxml"]
[[package]]
name = "pillow-heif"
-version = "0.20.0"
+version = "0.21.0"
description = "Python interface for libheif library"
optional = false
python-versions = ">=3.9"
files = [
- {file = "pillow_heif-0.20.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:ef2ad418f42adc9ef5d5e709547e799fb32141543856cb14f04fa4b22f83bfd7"},
- {file = "pillow_heif-0.20.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:af229e214ec23053bea1f162972645495bfb12f2c3b5ece463bd8a01aefda17a"},
- {file = "pillow_heif-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f446a78a9d84ef75761638a7e72a477aadeffb282ac70ffe67360a98d54775b1"},
- {file = "pillow_heif-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4a77c6e78756948a2a5fc8ec7341184fca1bc7316c11f6df0cf3fe9732e1688"},
- {file = "pillow_heif-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d30d19b8ba9c384a06523c3d419c46d62c823abdb6d75581ffd5328503f6d3aa"},
- {file = "pillow_heif-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0ec02ebbe88e2af0f093e80c95b716f54479a32b037da6b1c12b9f4024eab359"},
- {file = "pillow_heif-0.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:86a8920ea3a3b3923c827629afc850c1ee9f753b71346180c226882545028e06"},
- {file = "pillow_heif-0.20.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:8e3bc0bda64cce72e41f6c20a5cf3e24af308a09e146df78d31acb337a8ff58b"},
- {file = "pillow_heif-0.20.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a35e9e17d112573e9568d07c0e2c5cb81218a8f4c0da84a428618c7a746c4d98"},
- {file = "pillow_heif-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:618d63338afb9f49f1fb7b9a421aff6ad71ceb8092855e5988c05ab10dc21152"},
- {file = "pillow_heif-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6174d31580081d53f4eadc2428c699a5e47d111e64f136945951d12a9a277936"},
- {file = "pillow_heif-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7d106a1c87587838e9099bbfef9ddc7eef0dd3e77e9b1b8a1292a5f9dc4ad5a2"},
- {file = "pillow_heif-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba9e11f56a5e1fd1d559a1fd60d498f343922affc0e118fb3d4e808902fee1a9"},
- {file = "pillow_heif-0.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:daf209dd79ad21b21f7b2bbd575f331702d2f1dd0b529c12cdbee00d62c24254"},
- {file = "pillow_heif-0.20.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:0919f7738b886ed88367b9d0247132b1cbe5d40411bac5d7536d1876980af23e"},
- {file = "pillow_heif-0.20.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:146e55436b4feafcd086bd40211d5c3159b4d488b7f4918921560c9718c62dc9"},
- {file = "pillow_heif-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70564e6e301498b484e467d96d25065c8102b8bba6227959dcff2df68d888d82"},
- {file = "pillow_heif-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d14de6325eff7840d223c27fc974af28de0bb098b7678e05efe7e5cbf345e6b"},
- {file = "pillow_heif-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ff0d429d01ac1d4b54358bc3e10ac8aea7b04913e118800641394261d4430a3"},
- {file = "pillow_heif-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ccb16e488fc700029da111547039ac21e7cab9cae47f127ad2866824569a7a4c"},
- {file = "pillow_heif-0.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:ce6fb39f5d62d8a72ec2718ee110c49db529d9a1171c6ef243d7d66cfa17edc2"},
- {file = "pillow_heif-0.20.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:aadf4151095753b823b2ab061a51bfd4f5e56e69d6a1e125d12083eab639fd16"},
- {file = "pillow_heif-0.20.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4f3fac5a22946ec8df9c45a9f2d50a99407d798b2e7dce24bd2ef53b039f7f02"},
- {file = "pillow_heif-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6244a4934b21978c33651a77bdf446a9e9ae2450c332426bd2901a2523737938"},
- {file = "pillow_heif-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47fbfbd5b87c3ee2e165de8f43260c5cea45bb282f291ef09ae8a21fdd284467"},
- {file = "pillow_heif-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a3e9f2a87ba24468d1717c1403ceed7b6bc6c7f82023a8b888169ae494ee33d3"},
- {file = "pillow_heif-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cc5d116713657b12becd8a2241a5c70ec28a34053fcbd58164ca08b26b23970a"},
- {file = "pillow_heif-0.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:b4e9162f1265ed808af872abe894398ba2b5f2297221b03031f48870638cf491"},
- {file = "pillow_heif-0.20.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:5647cda2566da6786f0c090fd61c268b6d530d3a2c88361ed630f5ed2bd52766"},
- {file = "pillow_heif-0.20.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:9ae1a75deb8ffca01ae389593af6112a721415ff8a6ccc2676bb1da71186f13b"},
- {file = "pillow_heif-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:309d37303378ceb93d8408e26b67917a2091bc1e136fe0afb7c72610954de635"},
- {file = "pillow_heif-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0ccaade96a8a7d8614374b6d5c1b259e62040e33180fadfef336089b4919ed5"},
- {file = "pillow_heif-0.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1dffa961f316a9cb5a495087c17e41f2fdc83a8cbdf6d845716cbf2c9eb244bf"},
- {file = "pillow_heif-0.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7da99aa51bc80c24bc70fffcaa8e17c4944c4d4babdca0c38c82d5a69f7b8fa2"},
- {file = "pillow_heif-0.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:3f88de96b1ade76d408b4d490cd2f0de31c4790e4cf573e90503d9715082811c"},
- {file = "pillow_heif-0.20.0-pp310-pypy310_pp73-macosx_12_0_x86_64.whl", hash = "sha256:0a1a4ecaf150b569ad7d5fdeafde713e18d70e1a0d15395cdf96069818eae913"},
- {file = "pillow_heif-0.20.0-pp310-pypy310_pp73-macosx_14_0_arm64.whl", hash = "sha256:a8938faf7a48289601a5413078b2f21551228e1d1b203c41aaf7638ce156e073"},
- {file = "pillow_heif-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9430a33f69965d067be7e5c15dc70f1e43d5e3c8b5e9dc16c8c8d52179ce1cc"},
- {file = "pillow_heif-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da749d087ae3a7538af73d7a676cf332f81d1e6da9a6dea083aa382290d2d172"},
- {file = "pillow_heif-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:039f0c82ab3c0b364947979583d53ec9aad42d22159b9497e3c20ddde92c99bd"},
- {file = "pillow_heif-0.20.0-pp39-pypy39_pp73-macosx_12_0_x86_64.whl", hash = "sha256:9d42d164f378cf3ba1ddd00b2379360604a8461cee54eeebd67aac341f27ccac"},
- {file = "pillow_heif-0.20.0-pp39-pypy39_pp73-macosx_14_0_arm64.whl", hash = "sha256:740ef7652c7b278f24ead94e4098f0d1baf679a1e7373135e2820ce1c34a1bc5"},
- {file = "pillow_heif-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adc56caf280e39f0540d40df925cde2cd960d2ee2492f856224e2e399f4a7590"},
- {file = "pillow_heif-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:623c0b777b15773605eeed811b23658923b4e4d822172fb62d4cbe983e5a8722"},
- {file = "pillow_heif-0.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2f9515e21aa2f112252238175bbe3a1daad7a0c1020fc4ed52eae7805651431c"},
- {file = "pillow_heif-0.20.0.tar.gz", hash = "sha256:cac19c4434ab776f833160d61f3cbeddb347bd8ed2f82205b243eba5c572fa33"},
+ {file = "pillow_heif-0.21.0-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:f54609401164b0cb58000bd2516a88516b5e3e9b2f9c52ad9500575f1851da5e"},
+ {file = "pillow_heif-0.21.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:d0a68246340d4fad4f10721a1a50b87a7011f1bd18d0a7b7d231e196776d0260"},
+ {file = "pillow_heif-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:208b066bc7349b1ea1447199668edb6e2f74f36df54c86457ecb0131db8294df"},
+ {file = "pillow_heif-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cea6f1519a9c486baf3bdf63487fa3f699402724895d64841bb4636258a87c90"},
+ {file = "pillow_heif-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7f9e939cd8e343237800fe998e26558a82cb25496b74d7674f29e75dc87eb636"},
+ {file = "pillow_heif-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8b30fbbb672a3413413bcfc726f9994e495c647c6b96ab9f832dccb61b67fb2f"},
+ {file = "pillow_heif-0.21.0-cp310-cp310-win_amd64.whl", hash = "sha256:9807c955ea7ed2caa5d105aea7d870d8c0958079ed2aba39a6ace7ef82aad402"},
+ {file = "pillow_heif-0.21.0-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:0c3ffa486f56f52fe790d3b1bd522d93d2f59e22ce86045641cd596adc3c5273"},
+ {file = "pillow_heif-0.21.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c46be20058d72a5a158ffc65e6158279a4bcb337707a29b312c5293846bd5b8a"},
+ {file = "pillow_heif-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06663c825a3d71779e51df02080467761b74d515e59fce9d780220cd75de7dd0"},
+ {file = "pillow_heif-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23efab69a03a9a3a9ff07043d8c8bf0d15ffd661ecc5c7bff59b386eb25f0466"},
+ {file = "pillow_heif-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e5eebb73268b806d3c801271126382da4f556b756990f87590c843c5a8ec14e2"},
+ {file = "pillow_heif-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3456b4cdb4da485f27c53a91c81f0488b44dc99c0be6870f6a1dc5ac85709894"},
+ {file = "pillow_heif-0.21.0-cp311-cp311-win_amd64.whl", hash = "sha256:d36441100756122b9d401502e39b60d0df9d876a929f5db858a4b7d05cc02e88"},
+ {file = "pillow_heif-0.21.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:0aaea6ea45257cf74e76666b80b6109f8f56217009534726fa7f6a5694ebd563"},
+ {file = "pillow_heif-0.21.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f28c2c934f547823de3e204e48866c571d81ebb6b3e8646c32fe2104c570c7b2"},
+ {file = "pillow_heif-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e10ab63559346fc294b9612502221ddd6bfac8cd74091ace7328fefc1163a167"},
+ {file = "pillow_heif-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2a015cfe4afec75551190d93c99dda13410aec89dc468794885b90f870f657"},
+ {file = "pillow_heif-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:41693f5d87ed2b5fd01df4a6215045aff14d148a750aa0708c77e71139698154"},
+ {file = "pillow_heif-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b27031c561ee3485a119c769fc2ef41d81fae1de530857beef935683e09615e"},
+ {file = "pillow_heif-0.21.0-cp312-cp312-win_amd64.whl", hash = "sha256:60196c08e9c256e81054c5da468eb5a0266c931b8564c96283a43e5fd2d7ce0e"},
+ {file = "pillow_heif-0.21.0-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:9e67aae3c22a90bc7dfd42c9f0033c53a7d358e0f0d5d29aa42f2f193162fb01"},
+ {file = "pillow_heif-0.21.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ee2d68cbc0df8ba6fd9103ac6b550ebafcaa3a179416737a96becf6e5f079586"},
+ {file = "pillow_heif-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e5c0df7b8c84e4a8c249ba45ceca2453f205028d8a6525612ec6dd0553d925d"},
+ {file = "pillow_heif-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaedb7f16f3f18fbb315648ba576d0d7bb26b18b50c16281665123c38f73101e"},
+ {file = "pillow_heif-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6724d6a2561f36b06e14e1cd396c004d32717e81528cb03565491ac8679ed760"},
+ {file = "pillow_heif-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf2e2b0abad455a0896118856e82a8d5358dfe5480bedd09ddd6a04b23773899"},
+ {file = "pillow_heif-0.21.0-cp313-cp313-win_amd64.whl", hash = "sha256:1b6ba6c3c4de739a1abf4f7fe0cdd04acd9e0c7fc661985b9a5288d94893a4b1"},
+ {file = "pillow_heif-0.21.0-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:2448e180150b1ecb6576cc5030a6d14a179a7fa430b2b54d976f3beb3c5628ae"},
+ {file = "pillow_heif-0.21.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:fa9a91d6e390e78fe5670ff6083f26d13c6f1cabfaf0f61d0b272f50b5651c81"},
+ {file = "pillow_heif-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc919aa10fe97cb2134043d6e2d0d7fdbe17d7a2a833b202437e53be39fa7eae"},
+ {file = "pillow_heif-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d2fec1715ec77c2622e1eb52a6b30b58cea437b66dc45cfd28515dcb70bcc99"},
+ {file = "pillow_heif-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:55cba67787dfabb20e3fe0f54e4e768ca42c0ac5aa74c6b293b3407c7782fc87"},
+ {file = "pillow_heif-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04e824c087934bfd09605a992788db3c461f045a903dbc9f14b20eba0df0c6ac"},
+ {file = "pillow_heif-0.21.0-cp39-cp39-win_amd64.whl", hash = "sha256:c2d2ec026094c919ce010921586192968abe9dfd2528b38bce905c74cac9b9c6"},
+ {file = "pillow_heif-0.21.0-pp310-pypy310_pp73-macosx_13_0_x86_64.whl", hash = "sha256:9305aa837ce77d98a8b5e7bc8f86eeaefb52237686d84d60de11d55bad541d7f"},
+ {file = "pillow_heif-0.21.0-pp310-pypy310_pp73-macosx_14_0_arm64.whl", hash = "sha256:fc9bfc50f55267d13b0abf63bd7d141b92a39e09812dadee1a88b5863d9b8808"},
+ {file = "pillow_heif-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03273b94a7548ba615f6bfc1031137f1a025b657226de6c3f09f84945295f565"},
+ {file = "pillow_heif-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6576c9c7713e33150395cdc6e9cf59efd8f42c5783cf0764092ba50a048ee2c6"},
+ {file = "pillow_heif-0.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2813c34cdd3f07e406b6a2cb216019409eb62270e6799088ddf3d4cb08a0d503"},
+ {file = "pillow_heif-0.21.0-pp39-pypy39_pp73-macosx_13_0_x86_64.whl", hash = "sha256:b06125d594ca71c9af3bf69118c661b8f82a3a7ce2d2ea5302328d91ebef36cb"},
+ {file = "pillow_heif-0.21.0-pp39-pypy39_pp73-macosx_14_0_arm64.whl", hash = "sha256:22a73ed7ca5c2c8ef1b4872827dc7d8a6875938e9e791fff2db92fb4ca60f560"},
+ {file = "pillow_heif-0.21.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121451d016c450bfb4d926fe08274e165553679917eb8c85d41fcadfda5f3b2e"},
+ {file = "pillow_heif-0.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5680a00519e5f3c7c1c51dfd41e7f1c632793dfde57a9620339ba4cc70cf9196"},
+ {file = "pillow_heif-0.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a39d1043ec74afdeef00086c8d24b3cc30095927817182ae5bc960ddb3422d9c"},
+ {file = "pillow_heif-0.21.0.tar.gz", hash = "sha256:07aee1bff05e5d61feb989eaa745ae21b367011fd66ee48f7732931f8a12b49b"},
]
[package.dependencies]
@@ -3445,4 +3445,4 @@ pgsql = ["psycopg2-binary"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
-content-hash = "a3be05804f6a0f7c1ae7afcbf6c92edc14171e4588f5f0c69f3101c1aac2fee8"
+content-hash = "6fa485052a28b9488bc132e258ace11bb12d3bfdbc0511bda59a831af5080dbb"
diff --git a/pyproject.toml b/pyproject.toml
index 840a5077c..7534d229e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -45,7 +45,7 @@ authlib = "^1.3.0"
html2text = "^2024.0.0"
paho-mqtt = "^1.6.1"
pydantic-settings = "^2.1.0"
-pillow-heif = "^0.20.0"
+pillow-heif = "^0.21.0"
pyjwt = "^2.8.0"
openai = "^1.27.0"
typing-extensions = "^4.12.2"
From f67f00ca53d533d16e07bf668697bacbffa09198 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 1 Dec 2024 11:18:18 -0600
Subject: [PATCH 055/238] fix(deps): update dependency python-multipart to
^0.0.19 (#4638)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 8 ++++----
pyproject.toml | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 433697fba..af0973b29 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2442,13 +2442,13 @@ pyasn1_modules = ">=0.1.5"
[[package]]
name = "python-multipart"
-version = "0.0.18"
+version = "0.0.19"
description = "A streaming multipart parser for Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "python_multipart-0.0.18-py3-none-any.whl", hash = "sha256:efe91480f485f6a361427a541db4796f9e1591afc0fb8e7a4ba06bfbc6708996"},
- {file = "python_multipart-0.0.18.tar.gz", hash = "sha256:7a68db60c8bfb82e460637fa4750727b45af1d5e2ed215593f917f64694d34fe"},
+ {file = "python_multipart-0.0.19-py3-none-any.whl", hash = "sha256:f8d5b0b9c618575bf9df01c684ded1d94a338839bdd8223838afacfb4bb2082d"},
+ {file = "python_multipart-0.0.19.tar.gz", hash = "sha256:905502ef39050557b7a6af411f454bc19526529ca46ae6831508438890ce12cc"},
]
[[package]]
@@ -3445,4 +3445,4 @@ pgsql = ["psycopg2-binary"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
-content-hash = "6fa485052a28b9488bc132e258ace11bb12d3bfdbc0511bda59a831af5080dbb"
+content-hash = "55dbb0d6a3e28964743f87ae1d3a4ead8428cf1051ea97839edb325f39c526ba"
diff --git a/pyproject.toml b/pyproject.toml
index 7534d229e..73c86eca9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -31,7 +31,7 @@ python = "^3.10"
python-dateutil = "^2.8.2"
python-dotenv = "^1.0.0"
python-ldap = "^3.3.1"
-python-multipart = "^0.0.18"
+python-multipart = "^0.0.19"
python-slugify = "^8.0.0"
recipe-scrapers = "^15.0.0"
requests = "^2.31.0"
From 2945ea8f078236091051ec528e382a98a4b63d0a Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 1 Dec 2024 17:28:21 +0000
Subject: [PATCH 056/238] chore(deps): update dependency mkdocs-material to
v9.5.47 (#4639)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index af0973b29..1dd1cf010 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1468,13 +1468,13 @@ pyyaml = ">=5.1"
[[package]]
name = "mkdocs-material"
-version = "9.5.46"
+version = "9.5.47"
description = "Documentation that simply works"
optional = false
python-versions = ">=3.8"
files = [
- {file = "mkdocs_material-9.5.46-py3-none-any.whl", hash = "sha256:98f0a2039c62e551a68aad0791a8d41324ff90c03a6e6cea381a384b84908b83"},
- {file = "mkdocs_material-9.5.46.tar.gz", hash = "sha256:ae2043f4238e572f9a40e0b577f50400d6fc31e2fef8ea141800aebf3bd273d7"},
+ {file = "mkdocs_material-9.5.47-py3-none-any.whl", hash = "sha256:53fb9c9624e7865da6ec807d116cd7be24b3cb36ab31b1d1d1a9af58c56009a2"},
+ {file = "mkdocs_material-9.5.47.tar.gz", hash = "sha256:fc3b7a8e00ad896660bd3a5cc12ca0cb28bdc2bcbe2a946b5714c23ac91b0ede"},
]
[package.dependencies]
From 590bf9dbc53b288a2fada3a5fb3cec387d69bda2 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 1 Dec 2024 12:05:54 -0600
Subject: [PATCH 057/238] chore(deps): update dependency pytest to v8.3.4
(#4652)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 1dd1cf010..d278235a3 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2360,13 +2360,13 @@ requests = ">=2.25.1"
[[package]]
name = "pytest"
-version = "8.3.3"
+version = "8.3.4"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
- {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
+ {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"},
+ {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"},
]
[package.dependencies]
From fa28fe51c8e1e94fc1f9556c447e220f89f53650 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lukas=20K=C3=BChne?=
<79516198+eyko139@users.noreply.github.com>
Date: Sun, 1 Dec 2024 22:33:23 +0100
Subject: [PATCH 058/238] docs: Update authelia link in oidc.md (#4654)
---
docs/docs/documentation/getting-started/authentication/oidc.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/docs/documentation/getting-started/authentication/oidc.md b/docs/docs/documentation/getting-started/authentication/oidc.md
index d385a1cb6..9eb889f02 100644
--- a/docs/docs/documentation/getting-started/authentication/oidc.md
+++ b/docs/docs/documentation/getting-started/authentication/oidc.md
@@ -5,7 +5,7 @@
Mealie supports 3rd party authentication via [OpenID Connect (OIDC)](https://openid.net/connect/), an identity layer built on top of OAuth2. OIDC is supported by many Identity Providers (IdP), including:
- [Authentik](https://goauthentik.io/integrations/sources/oauth/#openid-connect)
-- [Authelia](https://www.authelia.com/configuration/identity-providers/open-id-connect/)
+- [Authelia](https://www.authelia.com/integration/openid-connect/mealie/)
- [Keycloak](https://www.keycloak.org/docs/latest/securing_apps/#_oidc)
- [Okta](https://www.okta.com/openid-connect/)
From a94bad19ff6c5c0c9aed964f83a11ec4765c9adf Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 1 Dec 2024 21:44:51 +0000
Subject: [PATCH 059/238] chore(deps): update dependency pylint to v3.3.2
(#4655)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index d278235a3..42311e153 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -108,13 +108,13 @@ requests-oauthlib = "*"
[[package]]
name = "astroid"
-version = "3.3.4"
+version = "3.3.5"
description = "An abstract syntax tree for Python with inference support."
optional = false
python-versions = ">=3.9.0"
files = [
- {file = "astroid-3.3.4-py3-none-any.whl", hash = "sha256:5eba185467253501b62a9f113c263524b4f5d55e1b30456370eed4cdbd6438fd"},
- {file = "astroid-3.3.4.tar.gz", hash = "sha256:e73d0b62dd680a7c07cb2cd0ce3c22570b044dd01bd994bc3a2dd16c6cbba162"},
+ {file = "astroid-3.3.5-py3-none-any.whl", hash = "sha256:a9d1c946ada25098d790e079ba2a1b112157278f3fb7e718ae6a9252f5835dc8"},
+ {file = "astroid-3.3.5.tar.gz", hash = "sha256:5cfc40ae9f68311075d27ef68a4841bdc5cc7f6cf86671b49f00607d30188e2d"},
]
[package.dependencies]
@@ -2283,17 +2283,17 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
[[package]]
name = "pylint"
-version = "3.3.1"
+version = "3.3.2"
description = "python code static checker"
optional = false
python-versions = ">=3.9.0"
files = [
- {file = "pylint-3.3.1-py3-none-any.whl", hash = "sha256:2f846a466dd023513240bc140ad2dd73bfc080a5d85a710afdb728c420a5a2b9"},
- {file = "pylint-3.3.1.tar.gz", hash = "sha256:9f3dcc87b1203e612b78d91a896407787e708b3f189b5fa0b307712d49ff0c6e"},
+ {file = "pylint-3.3.2-py3-none-any.whl", hash = "sha256:77f068c287d49b8683cd7c6e624243c74f92890f767f106ffa1ddf3c0a54cb7a"},
+ {file = "pylint-3.3.2.tar.gz", hash = "sha256:9ec054ec992cd05ad30a6df1676229739a73f8feeabf3912c995d17601052b01"},
]
[package.dependencies]
-astroid = ">=3.3.4,<=3.4.0-dev0"
+astroid = ">=3.3.5,<=3.4.0-dev0"
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
dill = [
{version = ">=0.2", markers = "python_version < \"3.11\""},
From 60edaf4b160f42c0935e775a6ddfd8854378b24b Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 2 Dec 2024 05:19:28 +0000
Subject: [PATCH 060/238] fix(deps): update dependency recipe-scrapers to
v15.3.2 (#4656)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 42311e153..5e7dd8642 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2670,13 +2670,13 @@ tests = ["html5lib", "pytest", "pytest-cov"]
[[package]]
name = "recipe-scrapers"
-version = "15.2.1"
+version = "15.3.2"
description = "Python package, scraping recipes from all over the internet"
optional = false
python-versions = ">=3.9"
files = [
- {file = "recipe_scrapers-15.2.1-py3-none-any.whl", hash = "sha256:ac38572f8a564c68287cc1f611eaf9f5dcda00fef3284d0d037f4010c8e4070b"},
- {file = "recipe_scrapers-15.2.1.tar.gz", hash = "sha256:5c81ad6b20dab653895ca7e1dcdc511fb3368196442e413e6516bfbf2ed7041a"},
+ {file = "recipe_scrapers-15.3.2-py3-none-any.whl", hash = "sha256:4fc361512b899fefbcf1426e769880e909edf4d4ab875fcb670a98f3ce8a7c21"},
+ {file = "recipe_scrapers-15.3.2.tar.gz", hash = "sha256:434dfee1ff5769f168f1bef979f89e17b6bb60b0501577971f54c13fc1809387"},
]
[package.dependencies]
@@ -2685,6 +2685,7 @@ extruct = ">=0.17.0"
isodate = ">=0.6.1"
[package.extras]
+dev = ["coverage (>=7.4.4)", "importlib-metadata (>=4.6)", "types-beautifulsoup4 (>=4.12.0)"]
online = ["requests (>=2.31.0)"]
[[package]]
From a439428ab8febbf817afb050d655e6aec1c1810d Mon Sep 17 00:00:00 2001
From: Michael Genson <71845777+michael-genson@users.noreply.github.com>
Date: Mon, 2 Dec 2024 10:14:28 -0600
Subject: [PATCH 061/238] fix: Improve UX for new households/userrs (#4653)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
---
frontend/lang/messages/en-US.json | 3 +-
.../pages/admin/manage/households/index.vue | 51 ++++++++++++-------
frontend/pages/admin/manage/users/create.vue | 5 +-
3 files changed, 39 insertions(+), 20 deletions(-)
diff --git a/frontend/lang/messages/en-US.json b/frontend/lang/messages/en-US.json
index b40fcc94e..cd229d3fa 100644
--- a/frontend/lang/messages/en-US.json
+++ b/frontend/lang/messages/en-US.json
@@ -276,7 +276,8 @@
"admin-group-management": "Admin Group Management",
"admin-group-management-text": "Changes to this group will be reflected immediately.",
"group-id-value": "Group Id: {0}",
- "total-households": "Total Households"
+ "total-households": "Total Households",
+ "you-must-select-a-group-before-selecting-a-household": "You must select a group before selecting a household"
},
"household": {
"household": "Household",
diff --git a/frontend/pages/admin/manage/households/index.vue b/frontend/pages/admin/manage/households/index.vue
index 98426310f..e14f84e3a 100644
--- a/frontend/pages/admin/manage/households/index.vue
+++ b/frontend/pages/admin/manage/households/index.vue
@@ -4,25 +4,29 @@
v-model="createDialog"
:title="$t('household.create-household')"
:icon="$globals.icons.household"
- @submit="createHousehold(createHouseholdForm.data)"
>
-
-
+
+
+
+
+
+ {{ $t("general.create") }}
+
diff --git a/frontend/components/Layout/DefaultLayout.vue b/frontend/components/Layout/DefaultLayout.vue
index 5e1ea6eca..ffba96ca4 100644
--- a/frontend/components/Layout/DefaultLayout.vue
+++ b/frontend/components/Layout/DefaultLayout.vue
@@ -221,7 +221,13 @@ export default defineComponent({
icon: $globals.icons.silverwareForkKnife,
to: `/g/${groupSlug.value}`,
title: i18n.tc("general.recipes"),
- restricted: true,
+ restricted: false,
+ },
+ {
+ icon: $globals.icons.search,
+ to: `/g/${groupSlug.value}/recipes/finder`,
+ title: i18n.tc("recipe-finder.recipe-finder"),
+ restricted: false,
},
{
icon: $globals.icons.calendarMultiselect,
diff --git a/frontend/components/global/BaseDialog.vue b/frontend/components/global/BaseDialog.vue
index 9fe321009..1a8e77ff2 100644
--- a/frontend/components/global/BaseDialog.vue
+++ b/frontend/components/global/BaseDialog.vue
@@ -45,11 +45,13 @@
+
{{ $t("general.confirm") }}
-
-
+
{{ submitText }}
{{ submitIcon }}
diff --git a/frontend/composables/use-users/preferences.ts b/frontend/composables/use-users/preferences.ts
index b5e47f44a..78f6f8041 100644
--- a/frontend/composables/use-users/preferences.ts
+++ b/frontend/composables/use-users/preferences.ts
@@ -1,6 +1,7 @@
import { Ref, useContext } from "@nuxtjs/composition-api";
import { useLocalStorage, useSessionStorage } from "@vueuse/core";
import { RegisteredParser, TimelineEventType } from "~/lib/api/types/recipe";
+import { QueryFilterJSON } from "~/lib/api/types/response";
export interface UserPrintPreferences {
imagePosition: string;
@@ -49,6 +50,17 @@ export interface UserCookbooksPreferences {
hideOtherHouseholds: boolean;
}
+export interface UserRecipeFinderPreferences {
+ foodIds: string[];
+ toolIds: string[];
+ queryFilter: string;
+ queryFilterJSON: QueryFilterJSON;
+ maxMissingFoods: number;
+ maxMissingTools: number;
+ includeFoodsOnHand: boolean;
+ includeToolsOnHand: boolean;
+}
+
export function useUserMealPlanPreferences(): Ref {
const fromStorage = useLocalStorage(
"meal-planner-preferences",
@@ -171,3 +183,24 @@ export function useCookbookPreferences(): Ref {
return fromStorage;
}
+
+export function useRecipeFinderPreferences(): Ref {
+ const fromStorage = useLocalStorage(
+ "recipe-finder-preferences",
+ {
+ foodIds: [],
+ toolIds: [],
+ queryFilter: "",
+ queryFilterJSON: { parts: [] } as QueryFilterJSON,
+ maxMissingFoods: 20,
+ maxMissingTools: 20,
+ includeFoodsOnHand: true,
+ includeToolsOnHand: true,
+ },
+ { mergeDefaults: true }
+ // we cast to a Ref because by default it will return an optional type ref
+ // but since we pass defaults we know all properties are set.
+ ) as unknown as Ref;
+
+ return fromStorage;
+}
diff --git a/frontend/lang/messages/en-US.json b/frontend/lang/messages/en-US.json
index cd229d3fa..208219ffa 100644
--- a/frontend/lang/messages/en-US.json
+++ b/frontend/lang/messages/en-US.json
@@ -671,6 +671,23 @@
"reset-servings-count": "Reset Servings Count",
"not-linked-ingredients": "Additional Ingredients"
},
+ "recipe-finder": {
+ "recipe-finder": "Recipe Finder",
+ "recipe-finder-description": "Search for recipes based on ingredients you have on hand. You can also filter by tools you have available, and set a maximum number of missing ingredients or tools.",
+ "selected-ingredients": "Selected Ingredients",
+ "no-ingredients-selected": "No ingredients selected",
+ "missing": "Missing",
+ "no-recipes-found": "No recipes found",
+ "no-recipes-found-description": "Try adding more ingredients to your search or adjusting your filters",
+ "include-ingredients-on-hand": "Include Ingredients On Hand",
+ "include-tools-on-hand": "Include Tools On Hand",
+ "max-missing-ingredients": "Max Missing Ingredients",
+ "max-missing-tools": "Max Missing Tools",
+ "selected-tools": "Selected Tools",
+ "other-filters": "Other Filters",
+ "ready-to-make": "Ready to Make",
+ "almost-ready-to-make": "Almost Ready to Make"
+ },
"search": {
"advanced-search": "Advanced Search",
"and": "and",
diff --git a/frontend/lib/api/public/explore/recipes.ts b/frontend/lib/api/public/explore/recipes.ts
index 7ff010fbb..978e4a6d6 100644
--- a/frontend/lib/api/public/explore/recipes.ts
+++ b/frontend/lib/api/public/explore/recipes.ts
@@ -1,6 +1,6 @@
import { BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
import { route } from "../../base";
-import { Recipe } from "~/lib/api/types/recipe";
+import { Recipe, RecipeSuggestionQuery, RecipeSuggestionResponse } from "~/lib/api/types/recipe";
import { ApiRequestInstance, PaginationData } from "~/lib/api/types/non-generated";
import { RecipeSearchQuery } from "../../user/recipes/recipe";
@@ -23,4 +23,10 @@ export class PublicRecipeApi extends BaseCRUDAPIReadOnly {
async search(rsq: RecipeSearchQuery) {
return await this.requests.get>(route(routes.recipesGroupSlug(this.groupSlug), rsq));
}
+
+ async getSuggestions(q: RecipeSuggestionQuery, foods: string[] | null = null, tools: string[]| null = null) {
+ return await this.requests.get(
+ route(`${this.baseRoute}/suggestions`, { ...q, foods, tools })
+ );
+ }
}
diff --git a/frontend/lib/api/types/recipe.ts b/frontend/lib/api/types/recipe.ts
index 84440f19b..ebd84eaff 100644
--- a/frontend/lib/api/types/recipe.ts
+++ b/frontend/lib/api/types/recipe.ts
@@ -7,6 +7,8 @@
export type ExportTypes = "json";
export type RegisteredParser = "nlp" | "brute" | "openai";
+export type OrderByNullPosition = "first" | "last";
+export type OrderDirection = "asc" | "desc";
export type TimelineEventType = "system" | "info" | "comment";
export type TimelineEventImage = "has image" | "does not have image";
@@ -380,6 +382,26 @@ export interface RecipeShareTokenSummary {
export interface RecipeSlug {
slug: string;
}
+export interface RecipeSuggestionQuery {
+ orderBy?: string | null;
+ orderByNullPosition?: OrderByNullPosition | null;
+ orderDirection?: OrderDirection;
+ queryFilter?: string | null;
+ paginationSeed?: string | null;
+ limit?: number;
+ maxMissingFoods?: number;
+ maxMissingTools?: number;
+ includeFoodsOnHand?: boolean;
+ includeToolsOnHand?: boolean;
+}
+export interface RecipeSuggestionResponse {
+ items: RecipeSuggestionResponseItem[];
+}
+export interface RecipeSuggestionResponseItem {
+ recipe: RecipeSummary;
+ missingFoods: IngredientFood[];
+ missingTools: RecipeTool[];
+}
export interface RecipeTagResponse {
name: string;
id: string;
@@ -519,3 +541,10 @@ export interface UnitFoodBase {
export interface UpdateImageResponse {
image: string;
}
+export interface RequestQuery {
+ orderBy?: string | null;
+ orderByNullPosition?: OrderByNullPosition | null;
+ orderDirection?: OrderDirection;
+ queryFilter?: string | null;
+ paginationSeed?: string | null;
+}
diff --git a/frontend/lib/api/types/response.ts b/frontend/lib/api/types/response.ts
index 30480aba6..dfa8a54f4 100644
--- a/frontend/lib/api/types/response.ts
+++ b/frontend/lib/api/types/response.ts
@@ -20,13 +20,13 @@ export interface FileTokenResponse {
fileToken: string;
}
export interface PaginationQuery {
- page?: number;
- perPage?: number;
orderBy?: string | null;
orderByNullPosition?: OrderByNullPosition | null;
orderDirection?: OrderDirection;
queryFilter?: string | null;
paginationSeed?: string | null;
+ page?: number;
+ perPage?: number;
}
export interface QueryFilterJSON {
parts?: QueryFilterJSONPart[];
@@ -47,6 +47,13 @@ export interface RecipeSearchQuery {
requireAllFoods?: boolean;
search?: string | null;
}
+export interface RequestQuery {
+ orderBy?: string | null;
+ orderByNullPosition?: OrderByNullPosition | null;
+ orderDirection?: OrderDirection;
+ queryFilter?: string | null;
+ paginationSeed?: string | null;
+}
export interface SuccessResponse {
message: string;
error?: boolean;
diff --git a/frontend/lib/api/user/recipes/recipe.ts b/frontend/lib/api/user/recipes/recipe.ts
index 87288814c..bfc930167 100644
--- a/frontend/lib/api/user/recipes/recipe.ts
+++ b/frontend/lib/api/user/recipes/recipe.ts
@@ -11,6 +11,8 @@ import {
UpdateImageResponse,
RecipeZipTokenResponse,
RecipeLastMade,
+ RecipeSuggestionQuery,
+ RecipeSuggestionResponse,
RecipeTimelineEventIn,
RecipeTimelineEventOut,
RecipeTimelineEventUpdate,
@@ -31,6 +33,7 @@ const prefix = "/api";
const routes = {
recipesCreate: `${prefix}/recipes/create`,
recipesBase: `${prefix}/recipes`,
+ recipesSuggestions: `${prefix}/recipes/suggestions`,
recipesTestScrapeUrl: `${prefix}/recipes/test-scrape-url`,
recipesCreateUrl: `${prefix}/recipes/create/url`,
recipesCreateUrlBulk: `${prefix}/recipes/create/url/bulk`,
@@ -109,6 +112,12 @@ export class RecipeAPI extends BaseCRUDAPI {
});
}
+ async getSuggestions(q: RecipeSuggestionQuery, foods: string[] | null = null, tools: string[]| null = null) {
+ return await this.requests.get(
+ route(routes.recipesSuggestions, { ...q, foods, tools })
+ );
+ }
+
async createAsset(recipeSlug: string, payload: CreateAsset) {
const formData = new FormData();
formData.append("file", payload.file);
diff --git a/frontend/pages/g/_groupSlug/recipes/finder/index.vue b/frontend/pages/g/_groupSlug/recipes/finder/index.vue
new file mode 100644
index 000000000..cd0248b4c
--- /dev/null
+++ b/frontend/pages/g/_groupSlug/recipes/finder/index.vue
@@ -0,0 +1,596 @@
+
+
+
+
+
+
+ {{ $tc('recipe-finder.recipe-finder') }}
+ {{ $t('recipe-finder.recipe-finder-description') }}
+
+
+
+
+
+
+
+
+
+ {{ $globals.icons.foods }}
+
+ {{ $t("general.foods") }}
+
+
+
+ {{ $globals.icons.potSteam }}
+
+ {{ $t("tool.tools") }}
+
+
+
+
+
+ {{ $globals.icons.filter }}
+
+ {{ $tc("recipe-finder.other-filters") }}
+
+ queryFilterEditorValue = value"
+ @inputJSON="(value) => queryFilterEditorValueJSON = value"
+ />
+
+
+
+ {{ $globals.icons.close }}
+
+ {{ $t("search.clear-selection") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $globals.icons.cog }}
+
+ {{ $t("general.settings") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $tc("recipe-finder.selected-ingredients") }}
+
+
+
+ {{ $tc("recipe-finder.no-ingredients-selected") }}
+
+
+
+
+
+ {{ food.pluralName || food.name }}
+
+
+
+
+
+
+
+
+ {{ food.pluralName || food.name }}
+
+
+
+
+
+
+
+
+ {{ $tc("recipe-finder.selected-tools") }}
+
+
+
+
+
+
+ {{ tool.name }}
+
+
+
+
+
+
+
+
+ {{ tool.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $tc("recipe-finder.ready-to-make") }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $tc("recipe-finder.almost-ready-to-make") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $tc("recipe-finder.no-recipes-found") }}
+
+ {{ $tc("recipe-finder.no-recipes-found-description") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mealie/repos/repository_generic.py b/mealie/repos/repository_generic.py
index b3a6857e1..87d406d3b 100644
--- a/mealie/repos/repository_generic.py
+++ b/mealie/repos/repository_generic.py
@@ -16,7 +16,13 @@ from sqlalchemy.sql import sqltypes
from mealie.core.root_logger import get_logger
from mealie.db.models._model_base import SqlAlchemyBase
from mealie.schema._mealie import MealieModel
-from mealie.schema.response.pagination import OrderByNullPosition, OrderDirection, PaginationBase, PaginationQuery
+from mealie.schema.response.pagination import (
+ OrderByNullPosition,
+ OrderDirection,
+ PaginationBase,
+ PaginationQuery,
+ RequestQuery,
+)
from mealie.schema.response.query_filter import QueryFilterBuilder
from mealie.schema.response.query_search import SearchFilter
@@ -404,11 +410,11 @@ class RepositoryGeneric(Generic[Schema, Model]):
return query.order_by(order_attr)
- def add_order_by_to_query(self, query: Select, pagination: PaginationQuery) -> Select:
- if not pagination.order_by:
+ def add_order_by_to_query(self, query: Select, request_query: RequestQuery) -> Select:
+ if not request_query.order_by:
return query
- elif pagination.order_by == "random":
+ elif request_query.order_by == "random":
# randomize outside of database, since not all db's can set random seeds
# this solution is db-independent & stable to paging
temp_query = query.with_only_columns(self.model.id)
@@ -417,14 +423,14 @@ class RepositoryGeneric(Generic[Schema, Model]):
return query
order = list(range(len(allids)))
- random.seed(pagination.pagination_seed)
+ random.seed(request_query.pagination_seed)
random.shuffle(order)
random_dict = dict(zip(allids, order, strict=True))
case_stmt = case(random_dict, value=self.model.id)
return query.order_by(case_stmt)
else:
- for order_by_val in pagination.order_by.split(","):
+ for order_by_val in request_query.order_by.split(","):
try:
order_by_val = order_by_val.strip()
if ":" in order_by_val:
@@ -432,20 +438,20 @@ class RepositoryGeneric(Generic[Schema, Model]):
order_dir = OrderDirection(order_dir_val)
else:
order_by = order_by_val
- order_dir = pagination.order_direction
+ order_dir = request_query.order_direction
_, order_attr, query = QueryFilterBuilder.get_model_and_model_attr_from_attr_string(
order_by, self.model, query=query
)
query = self.add_order_attr_to_query(
- query, order_attr, order_dir, pagination.order_by_null_position
+ query, order_attr, order_dir, request_query.order_by_null_position
)
except ValueError as e:
raise HTTPException(
status_code=400,
- detail=f'Invalid order_by statement "{pagination.order_by}": "{order_by_val}" is invalid',
+ detail=f'Invalid order_by statement "{request_query.order_by}": "{order_by_val}" is invalid',
) from e
return query
diff --git a/mealie/repos/repository_recipes.py b/mealie/repos/repository_recipes.py
index c1c78ee62..4b907ee3f 100644
--- a/mealie/repos/repository_recipes.py
+++ b/mealie/repos/repository_recipes.py
@@ -1,30 +1,37 @@
import re as re
from collections.abc import Sequence
from random import randint
+from typing import cast
from uuid import UUID
import sqlalchemy as sa
+from fastapi import HTTPException
from pydantic import UUID4
from slugify import slugify
+from sqlalchemy import orm
from sqlalchemy.exc import IntegrityError
-from sqlalchemy.orm import InstrumentedAttribute
from typing_extensions import Self
from mealie.db.models.household.household import Household
from mealie.db.models.recipe.category import Category
-from mealie.db.models.recipe.ingredient import RecipeIngredientModel
+from mealie.db.models.recipe.ingredient import IngredientFoodModel, RecipeIngredientModel
from mealie.db.models.recipe.recipe import RecipeModel
+from mealie.db.models.recipe.settings import RecipeSettings
from mealie.db.models.recipe.tag import Tag
-from mealie.db.models.recipe.tool import Tool
+from mealie.db.models.recipe.tool import Tool, recipes_to_tools
from mealie.db.models.users.user_to_recipe import UserToRecipe
from mealie.schema.cookbook.cookbook import ReadCookBook
from mealie.schema.recipe import Recipe
from mealie.schema.recipe.recipe import RecipeCategory, RecipePagination, RecipeSummary
+from mealie.schema.recipe.recipe_ingredient import IngredientFood
+from mealie.schema.recipe.recipe_suggestion import RecipeSuggestionQuery, RecipeSuggestionResponseItem
+from mealie.schema.recipe.recipe_tool import RecipeToolOut
from mealie.schema.response.pagination import (
OrderByNullPosition,
OrderDirection,
PaginationQuery,
)
+from mealie.schema.response.query_filter import QueryFilterBuilder
from ..db.models._model_base import SqlAlchemyBase
from .repository_generic import HouseholdRepositoryGeneric
@@ -100,7 +107,7 @@ class RepositoryRecipes(HouseholdRepositoryGeneric[Recipe, RecipeModel]):
def add_order_attr_to_query(
self,
query: sa.Select,
- order_attr: InstrumentedAttribute,
+ order_attr: orm.InstrumentedAttribute,
order_dir: OrderDirection,
order_by_null: OrderByNullPosition | None,
) -> sa.Select:
@@ -297,3 +304,176 @@ class RepositoryRecipes(HouseholdRepositoryGeneric[Recipe, RecipeModel]):
def all_ids(self, group_id: UUID4) -> Sequence[UUID4]:
stmt = sa.select(RecipeModel.id).filter(RecipeModel.group_id == group_id)
return self.session.execute(stmt).scalars().all()
+
+ def find_suggested_recipes(
+ self,
+ params: RecipeSuggestionQuery,
+ food_ids: list[UUID4] | None = None,
+ tool_ids: list[UUID4] | None = None,
+ ) -> list[RecipeSuggestionResponseItem]:
+ """
+ Queries all recipes and returns the ones that are missing the least amount of foods and tools.
+
+ Results are ordered first by number of missing tools, then foods, and finally by the user-specified order.
+ If foods are provided, the query will prefer recipes with more matches to user-provided foods.
+ """
+
+ if not params.order_by:
+ params.order_by = "created_at"
+
+ food_ids_with_on_hand = list(set(food_ids or []))
+ tool_ids_with_on_hand = list(set(tool_ids or []))
+
+ # preserve the original lists of ids before we add on_hand items
+ user_food_ids = food_ids_with_on_hand.copy()
+ user_tool_ids = tool_ids_with_on_hand.copy()
+
+ if params.include_foods_on_hand:
+ foods_on_hand_query = sa.select(IngredientFoodModel.id).filter(
+ IngredientFoodModel.on_hand == True, # noqa: E712 - required for SQLAlchemy comparison
+ sa.not_(IngredientFoodModel.id.in_(food_ids_with_on_hand)),
+ )
+ if self.group_id:
+ foods_on_hand_query = foods_on_hand_query.filter(IngredientFoodModel.group_id == self.group_id)
+
+ foods_on_hand = self.session.execute(foods_on_hand_query).scalars().all()
+ food_ids_with_on_hand.extend(foods_on_hand)
+ if params.include_tools_on_hand:
+ tools_on_hand_query = sa.select(Tool.id).filter(
+ Tool.on_hand == True, # noqa: E712 - required for SQLAlchemy comparison
+ sa.not_(
+ Tool.id.in_(tool_ids_with_on_hand),
+ ),
+ )
+ if self.group_id:
+ tools_on_hand_query = tools_on_hand_query.filter(Tool.group_id == self.group_id)
+
+ tools_on_hand = self.session.execute(tools_on_hand_query).scalars().all()
+ tool_ids_with_on_hand.extend(tools_on_hand)
+
+ ## Build suggestion query
+ settings_alias = orm.aliased(RecipeSettings)
+ ingredients_alias = orm.aliased(RecipeIngredientModel)
+ tools_alias = orm.aliased(Tool)
+
+ q = sa.select(self.model)
+ fltr = self._filter_builder()
+ q = q.filter_by(**fltr)
+
+ # Tools goes first so we can order by missing tools count before foods
+ if user_tool_ids:
+ unmatched_tools_query = (
+ sa.select(recipes_to_tools.c.recipe_id, sa.func.count().label("unmatched_tools_count"))
+ .join(tools_alias, recipes_to_tools.c.tool_id == tools_alias.id)
+ .filter(sa.not_(tools_alias.id.in_(tool_ids_with_on_hand)))
+ .group_by(recipes_to_tools.c.recipe_id)
+ .subquery()
+ )
+ q = (
+ q.outerjoin(unmatched_tools_query, self.model.id == unmatched_tools_query.c.recipe_id)
+ .filter(
+ sa.or_(
+ unmatched_tools_query.c.unmatched_tools_count.is_(None),
+ unmatched_tools_query.c.unmatched_tools_count <= params.max_missing_tools,
+ )
+ )
+ .order_by(unmatched_tools_query.c.unmatched_tools_count.asc().nulls_first())
+ )
+
+ if user_food_ids:
+ unmatched_foods_query = (
+ sa.select(ingredients_alias.recipe_id, sa.func.count().label("unmatched_foods_count"))
+ .filter(sa.not_(ingredients_alias.food_id.in_(food_ids_with_on_hand)))
+ .filter(ingredients_alias.food_id.isnot(None))
+ .group_by(ingredients_alias.recipe_id)
+ .subquery()
+ )
+ total_user_foods_query = (
+ sa.select(ingredients_alias.recipe_id, sa.func.count().label("total_user_foods_count"))
+ .filter(ingredients_alias.food_id.in_(user_food_ids))
+ .group_by(ingredients_alias.recipe_id)
+ .subquery()
+ )
+ q = (
+ q.join(settings_alias, self.model.settings)
+ .filter(settings_alias.disable_amount == False) # noqa: E712 - required for SQLAlchemy comparison
+ .outerjoin(unmatched_foods_query, self.model.id == unmatched_foods_query.c.recipe_id)
+ .outerjoin(total_user_foods_query, self.model.id == total_user_foods_query.c.recipe_id)
+ .filter(
+ sa.or_(
+ unmatched_foods_query.c.unmatched_foods_count.is_(None),
+ unmatched_foods_query.c.unmatched_foods_count <= params.max_missing_foods,
+ ),
+ )
+ .order_by(
+ unmatched_foods_query.c.unmatched_foods_count.asc().nulls_first(),
+ # favor recipes with more matched foods, in case the user is looking for something specific
+ total_user_foods_query.c.total_user_foods_count.desc().nulls_last(),
+ )
+ )
+
+ # only include recipes that have at least one food in the user's list
+ if user_food_ids:
+ q = q.filter(total_user_foods_query.c.total_user_foods_count > 0)
+
+ ## Add filters and loader options
+ if self.group_id:
+ q = q.filter(self.model.group_id == self.group_id)
+ if self.household_id:
+ q = q.filter(self.model.household_id == self.household_id)
+ if params.query_filter:
+ try:
+ query_filter_builder = QueryFilterBuilder(params.query_filter)
+ q = query_filter_builder.filter_query(q, model=self.model)
+
+ except ValueError as e:
+ self.logger.error(e)
+ raise HTTPException(status_code=400, detail=str(e)) from e
+
+ q = self.add_order_by_to_query(q, params)
+ q = q.limit(params.limit).options(*RecipeSummary.loader_options())
+
+ ## Execute query
+ try:
+ data = self.session.execute(q).scalars().unique().all()
+ except Exception as e:
+ self._log_exception(e)
+ self.session.rollback()
+ raise e
+
+ suggestions: list[RecipeSuggestionResponseItem] = []
+ for result in data:
+ recipe = cast(RecipeModel, result)
+
+ missing_foods: list[IngredientFood] = []
+ if user_food_ids: # only check for missing foods if the user has provided a list of foods
+ seen_food_ids: set[UUID4] = set()
+ seen_food_ids.update(food_ids_with_on_hand)
+ for ingredient in recipe.recipe_ingredient:
+ if not ingredient.food:
+ continue
+ if ingredient.food.id in seen_food_ids:
+ continue
+
+ seen_food_ids.add(ingredient.food.id)
+ missing_foods.append(IngredientFood.model_validate(ingredient.food))
+
+ missing_tools: list[RecipeToolOut] = []
+ if user_tool_ids: # only check for missing tools if the user has provided a list of tools
+ seen_tool_ids: set[UUID4] = set()
+ seen_tool_ids.update(tool_ids_with_on_hand)
+ for tool in recipe.tools:
+ if tool.id in seen_tool_ids:
+ continue
+
+ seen_tool_ids.add(tool.id)
+ missing_tools.append(RecipeToolOut.model_validate(tool))
+
+ suggestion = RecipeSuggestionResponseItem(
+ recipe=RecipeSummary.model_validate(recipe),
+ missing_foods=missing_foods,
+ missing_tools=missing_tools,
+ )
+ suggestions.append(suggestion)
+
+ return suggestions
diff --git a/mealie/routes/explore/controller_public_recipes.py b/mealie/routes/explore/controller_public_recipes.py
index fd173e315..200617c3c 100644
--- a/mealie/routes/explore/controller_public_recipes.py
+++ b/mealie/routes/explore/controller_public_recipes.py
@@ -11,6 +11,7 @@ from mealie.schema.cookbook.cookbook import ReadCookBook
from mealie.schema.make_dependable import make_dependable
from mealie.schema.recipe import Recipe
from mealie.schema.recipe.recipe import RecipeSummary
+from mealie.schema.recipe.recipe_suggestion import RecipeSuggestionQuery, RecipeSuggestionResponse
from mealie.schema.response.pagination import PaginationBase, PaginationQuery, RecipeSearchQuery
router = APIRouter(prefix="/recipes")
@@ -90,6 +91,26 @@ class PublicRecipesController(BasePublicHouseholdExploreController):
# Response is returned directly, to avoid validation and improve performance
return JSONBytes(content=json_compatible_response)
+ @router.get("/suggestions", response_model=RecipeSuggestionResponse)
+ def suggest_recipes(
+ self,
+ q: RecipeSuggestionQuery = Depends(make_dependable(RecipeSuggestionQuery)),
+ foods: list[UUID4] | None = Query(None),
+ tools: list[UUID4] | None = Query(None),
+ ) -> RecipeSuggestionResponse:
+ public_filter = "(household.preferences.privateHousehold = FALSE AND settings.public = TRUE)"
+ if q.query_filter:
+ q.query_filter = f"({q.query_filter}) AND {public_filter}"
+ else:
+ q.query_filter = public_filter
+
+ recipes = self.cross_household_recipes.find_suggested_recipes(q, foods, tools)
+ response = RecipeSuggestionResponse(items=recipes)
+ json_compatible_response = orjson.dumps(response.model_dump(by_alias=True))
+
+ # Response is returned directly, to avoid validation and improve performance
+ return JSONBytes(content=json_compatible_response)
+
@router.get("/{recipe_slug}", response_model=Recipe)
def get_recipe(self, recipe_slug: str) -> Recipe:
RECIPE_NOT_FOUND_EXCEPTION = HTTPException(404, "recipe not found")
diff --git a/mealie/routes/recipe/__init__.py b/mealie/routes/recipe/__init__.py
index 646a10ca9..355d5271d 100644
--- a/mealie/routes/recipe/__init__.py
+++ b/mealie/routes/recipe/__init__.py
@@ -1,14 +1,14 @@
from fastapi import APIRouter
-from . import bulk_actions, comments, recipe_crud_routes, shared_routes, timeline_events
+from . import bulk_actions, comments, exports, recipe_crud_routes, shared_routes, timeline_events
prefix = "/recipes"
router = APIRouter()
-router.include_router(recipe_crud_routes.router_exports, tags=["Recipe: Exports"])
+router.include_router(exports.router, tags=["Recipe: Exports"])
router.include_router(recipe_crud_routes.router, tags=["Recipe: CRUD"])
router.include_router(comments.router, prefix=prefix, tags=["Recipe: Comments"])
router.include_router(bulk_actions.router, prefix=prefix, tags=["Recipe: Bulk Actions"])
router.include_router(shared_routes.router, prefix=prefix, tags=["Recipe: Shared"])
-router.include_router(timeline_events.events_router, prefix=prefix, tags=["Recipe: Timeline"])
+router.include_router(timeline_events.router, prefix=prefix, tags=["Recipe: Timeline"])
diff --git a/mealie/routes/recipe/_base.py b/mealie/routes/recipe/_base.py
new file mode 100644
index 000000000..146cbe2bb
--- /dev/null
+++ b/mealie/routes/recipe/_base.py
@@ -0,0 +1,57 @@
+from functools import cached_property
+
+from fastapi.responses import JSONResponse
+from pydantic import BaseModel, Field
+
+from mealie.db.models.household.cookbook import CookBook
+from mealie.repos.all_repositories import get_repositories
+from mealie.repos.repository_generic import RepositoryGeneric
+from mealie.repos.repository_recipes import RepositoryRecipes
+from mealie.routes._base import BaseCrudController
+from mealie.routes._base.mixins import HttpRepo
+from mealie.schema.cookbook.cookbook import ReadCookBook
+from mealie.schema.recipe import Recipe
+from mealie.schema.recipe.recipe import (
+ CreateRecipe,
+)
+from mealie.services.recipe.recipe_service import RecipeService
+
+
+class JSONBytes(JSONResponse):
+ """
+ JSONBytes overrides the render method to return the bytes instead of a string.
+ You can use this when you want to use orjson and bypass the jsonable_encoder
+ """
+
+ media_type = "application/json"
+
+ def render(self, content: bytes) -> bytes:
+ return content
+
+
+class FormatResponse(BaseModel):
+ jjson: list[str] = Field(..., alias="json")
+ zip: list[str]
+ jinja2: list[str]
+
+
+class BaseRecipeController(BaseCrudController):
+ @cached_property
+ def recipes(self) -> RepositoryRecipes:
+ return self.repos.recipes
+
+ @cached_property
+ def group_recipes(self) -> RepositoryRecipes:
+ return get_repositories(self.session, group_id=self.group_id, household_id=None).recipes
+
+ @cached_property
+ def group_cookbooks(self) -> RepositoryGeneric[ReadCookBook, CookBook]:
+ return get_repositories(self.session, group_id=self.group_id, household_id=None).cookbooks
+
+ @cached_property
+ def service(self) -> RecipeService:
+ return RecipeService(self.repos, self.user, self.household, translator=self.translator)
+
+ @cached_property
+ def mixins(self):
+ return HttpRepo[CreateRecipe, Recipe, Recipe](self.recipes, self.logger)
diff --git a/mealie/routes/recipe/exports.py b/mealie/routes/recipe/exports.py
new file mode 100644
index 000000000..7c5f426a3
--- /dev/null
+++ b/mealie/routes/recipe/exports.py
@@ -0,0 +1,76 @@
+from shutil import rmtree
+from zipfile import ZipFile
+
+from fastapi import (
+ HTTPException,
+)
+from starlette.background import BackgroundTask
+from starlette.responses import FileResponse
+
+from mealie.core.dependencies import (
+ get_temporary_path,
+ get_temporary_zip_path,
+ validate_recipe_token,
+)
+from mealie.core.security import create_recipe_slug_token
+from mealie.routes._base import controller
+from mealie.routes._base.routers import UserAPIRouter
+from mealie.schema.recipe import Recipe, RecipeImageTypes
+from mealie.schema.recipe.request_helpers import (
+ RecipeZipTokenResponse,
+)
+from mealie.services.recipe.template_service import TemplateService
+
+from ._base import BaseRecipeController, FormatResponse
+
+router = UserAPIRouter(prefix="/recipes")
+
+
+@controller(router)
+class RecipeExportController(BaseRecipeController):
+ # ==================================================================================================================
+ # Export Operations
+
+ @router.get("/exports", response_model=FormatResponse)
+ def get_recipe_formats_and_templates(self):
+ return TemplateService().templates
+
+ @router.post("/{slug}/exports", response_model=RecipeZipTokenResponse)
+ def get_recipe_zip_token(self, slug: str):
+ """Generates a recipe zip token to be used to download a recipe as a zip file"""
+ return RecipeZipTokenResponse(token=create_recipe_slug_token(slug))
+
+ @router.get("/{slug}/exports", response_class=FileResponse)
+ def get_recipe_as_format(self, slug: str, template_name: str):
+ """
+ ## Parameters
+ `template_name`: The name of the template to use to use in the exports listed. Template type will automatically
+ be set on the backend. Because of this, it's important that your templates have unique names. See available
+ names and formats in the /api/recipes/exports endpoint.
+
+ """
+ with get_temporary_path(auto_unlink=False) as temp_path:
+ recipe = self.mixins.get_one(slug)
+ file = self.service.render_template(recipe, temp_path, template_name)
+ return FileResponse(file, background=BackgroundTask(rmtree, temp_path))
+
+ @router.get("/{slug}/exports/zip")
+ def get_recipe_as_zip(self, slug: str, token: str):
+ """Get a Recipe and Its Original Image as a Zip File"""
+ with get_temporary_zip_path(auto_unlink=False) as temp_path:
+ validated_slug = validate_recipe_token(token)
+
+ if validated_slug != slug:
+ raise HTTPException(status_code=400, detail="Invalid Slug")
+
+ recipe: Recipe = self.mixins.get_one(validated_slug)
+ image_asset = recipe.image_dir.joinpath(RecipeImageTypes.original.value)
+ with ZipFile(temp_path, "w") as myzip:
+ myzip.writestr(f"{slug}.json", recipe.model_dump_json())
+
+ if image_asset.is_file():
+ myzip.write(image_asset, arcname=image_asset.name)
+
+ return FileResponse(
+ temp_path, filename=f"{recipe.slug}.zip", background=BackgroundTask(temp_path.unlink, missing_ok=True)
+ )
diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py
index 6e248c97a..eec1c6c21 100644
--- a/mealie/routes/recipe/recipe_crud_routes.py
+++ b/mealie/routes/recipe/recipe_crud_routes.py
@@ -1,8 +1,6 @@
from collections import defaultdict
-from functools import cached_property
-from shutil import copyfileobj, rmtree
+from shutil import copyfileobj
from uuid import UUID
-from zipfile import ZipFile
import orjson
import sqlalchemy
@@ -18,30 +16,19 @@ from fastapi import (
status,
)
from fastapi.datastructures import UploadFile
-from fastapi.responses import JSONResponse
-from pydantic import UUID4, BaseModel, Field
+from pydantic import UUID4
from slugify import slugify
-from starlette.background import BackgroundTask
-from starlette.responses import FileResponse
from mealie.core import exceptions
from mealie.core.dependencies import (
- get_temporary_path,
get_temporary_zip_path,
- validate_recipe_token,
)
-from mealie.core.security import create_recipe_slug_token
-from mealie.db.models.household.cookbook import CookBook
from mealie.pkgs import cache
-from mealie.repos.all_repositories import get_repositories
-from mealie.repos.repository_generic import RepositoryGeneric
-from mealie.repos.repository_recipes import RepositoryRecipes
-from mealie.routes._base import BaseCrudController, controller
-from mealie.routes._base.mixins import HttpRepo
+from mealie.routes._base import controller
from mealie.routes._base.routers import MealieCrudRoute, UserAPIRouter
from mealie.schema.cookbook.cookbook import ReadCookBook
from mealie.schema.make_dependable import make_dependable
-from mealie.schema.recipe import Recipe, RecipeImageTypes, ScrapeRecipe, ScrapeRecipeData
+from mealie.schema.recipe import Recipe, ScrapeRecipe, ScrapeRecipeData
from mealie.schema.recipe.recipe import (
CreateRecipe,
CreateRecipeByUrlBulk,
@@ -50,9 +37,9 @@ from mealie.schema.recipe.recipe import (
)
from mealie.schema.recipe.recipe_asset import RecipeAsset
from mealie.schema.recipe.recipe_scraper import ScrapeRecipeTest
+from mealie.schema.recipe.recipe_suggestion import RecipeSuggestionQuery, RecipeSuggestionResponse
from mealie.schema.recipe.request_helpers import (
RecipeDuplicate,
- RecipeZipTokenResponse,
UpdateImageResponse,
)
from mealie.schema.response import PaginationBase, PaginationQuery
@@ -71,8 +58,6 @@ from mealie.services.recipe.recipe_data_service import (
NotAnImageError,
RecipeDataService,
)
-from mealie.services.recipe.recipe_service import RecipeService
-from mealie.services.recipe.template_service import TemplateService
from mealie.services.scraper.recipe_bulk_scraper import RecipeBulkScraperService
from mealie.services.scraper.scraped_extras import ScraperContext
from mealie.services.scraper.scraper import create_from_html
@@ -82,99 +67,7 @@ from mealie.services.scraper.scraper_strategies import (
RecipeScraperPackage,
)
-
-class JSONBytes(JSONResponse):
- """
- JSONBytes overrides the render method to return the bytes instead of a string.
- You can use this when you want to use orjson and bypass the jsonable_encoder
- """
-
- media_type = "application/json"
-
- def render(self, content: bytes) -> bytes:
- return content
-
-
-class BaseRecipeController(BaseCrudController):
- @cached_property
- def recipes(self) -> RepositoryRecipes:
- return self.repos.recipes
-
- @cached_property
- def group_recipes(self) -> RepositoryRecipes:
- return get_repositories(self.session, group_id=self.group_id, household_id=None).recipes
-
- @cached_property
- def group_cookbooks(self) -> RepositoryGeneric[ReadCookBook, CookBook]:
- return get_repositories(self.session, group_id=self.group_id, household_id=None).cookbooks
-
- @cached_property
- def service(self) -> RecipeService:
- return RecipeService(self.repos, self.user, self.household, translator=self.translator)
-
- @cached_property
- def mixins(self):
- return HttpRepo[CreateRecipe, Recipe, Recipe](self.recipes, self.logger)
-
-
-class FormatResponse(BaseModel):
- jjson: list[str] = Field(..., alias="json")
- zip: list[str]
- jinja2: list[str]
-
-
-router_exports = UserAPIRouter(prefix="/recipes")
-
-
-@controller(router_exports)
-class RecipeExportController(BaseRecipeController):
- # ==================================================================================================================
- # Export Operations
-
- @router_exports.get("/exports", response_model=FormatResponse)
- def get_recipe_formats_and_templates(self):
- return TemplateService().templates
-
- @router_exports.post("/{slug}/exports", response_model=RecipeZipTokenResponse)
- def get_recipe_zip_token(self, slug: str):
- """Generates a recipe zip token to be used to download a recipe as a zip file"""
- return RecipeZipTokenResponse(token=create_recipe_slug_token(slug))
-
- @router_exports.get("/{slug}/exports", response_class=FileResponse)
- def get_recipe_as_format(self, slug: str, template_name: str):
- """
- ## Parameters
- `template_name`: The name of the template to use to use in the exports listed. Template type will automatically
- be set on the backend. Because of this, it's important that your templates have unique names. See available
- names and formats in the /api/recipes/exports endpoint.
-
- """
- with get_temporary_path(auto_unlink=False) as temp_path:
- recipe = self.mixins.get_one(slug)
- file = self.service.render_template(recipe, temp_path, template_name)
- return FileResponse(file, background=BackgroundTask(rmtree, temp_path))
-
- @router_exports.get("/{slug}/exports/zip")
- def get_recipe_as_zip(self, slug: str, token: str):
- """Get a Recipe and Its Original Image as a Zip File"""
- with get_temporary_zip_path(auto_unlink=False) as temp_path:
- validated_slug = validate_recipe_token(token)
-
- if validated_slug != slug:
- raise HTTPException(status_code=400, detail="Invalid Slug")
-
- recipe: Recipe = self.mixins.get_one(validated_slug)
- image_asset = recipe.image_dir.joinpath(RecipeImageTypes.original.value)
- with ZipFile(temp_path, "w") as myzip:
- myzip.writestr(f"{slug}.json", recipe.model_dump_json())
-
- if image_asset.is_file():
- myzip.write(image_asset, arcname=image_asset.name)
-
- return FileResponse(
- temp_path, filename=f"{recipe.slug}.zip", background=BackgroundTask(temp_path.unlink, missing_ok=True)
- )
-
+from ._base import BaseRecipeController, JSONBytes
router = UserAPIRouter(prefix="/recipes", route_class=MealieCrudRoute)
@@ -388,6 +281,20 @@ class RecipeController(BaseRecipeController):
# Response is returned directly, to avoid validation and improve performance
return JSONBytes(content=json_compatible_response)
+ @router.get("/suggestions", response_model=RecipeSuggestionResponse)
+ def suggest_recipes(
+ self,
+ q: RecipeSuggestionQuery = Depends(make_dependable(RecipeSuggestionQuery)),
+ foods: list[UUID4] | None = Query(None),
+ tools: list[UUID4] | None = Query(None),
+ ) -> RecipeSuggestionResponse:
+ recipes = self.group_recipes.find_suggested_recipes(q, foods, tools)
+ response = RecipeSuggestionResponse(items=recipes)
+ json_compatible_response = orjson.dumps(response.model_dump(by_alias=True))
+
+ # Response is returned directly, to avoid validation and improve performance
+ return JSONBytes(content=json_compatible_response)
+
@router.get("/{slug}", response_model=Recipe)
def get_one(self, slug: str = Path(..., description="A recipe's slug or id")):
"""Takes in a recipe's slug or id and returns all data for a recipe"""
diff --git a/mealie/routes/recipe/timeline_events.py b/mealie/routes/recipe/timeline_events.py
index 9d5f23e56..7894e4626 100644
--- a/mealie/routes/recipe/timeline_events.py
+++ b/mealie/routes/recipe/timeline_events.py
@@ -22,10 +22,10 @@ from mealie.services import urls
from mealie.services.event_bus_service.event_types import EventOperation, EventRecipeTimelineEventData, EventTypes
from mealie.services.recipe.recipe_data_service import RecipeDataService
-events_router = UserAPIRouter(route_class=MealieCrudRoute, prefix="/timeline/events")
+router = UserAPIRouter(route_class=MealieCrudRoute, prefix="/timeline/events")
-@controller(events_router)
+@controller(router)
class RecipeTimelineEventsController(BaseCrudController):
@cached_property
def repo(self):
@@ -43,17 +43,17 @@ class RecipeTimelineEventsController(BaseCrudController):
self.registered_exceptions,
)
- @events_router.get("", response_model=RecipeTimelineEventPagination)
+ @router.get("", response_model=RecipeTimelineEventPagination)
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
response = self.repo.page_all(
pagination=q,
override=RecipeTimelineEventOut,
)
- response.set_pagination_guides(events_router.url_path_for("get_all"), q.model_dump())
+ response.set_pagination_guides(router.url_path_for("get_all"), q.model_dump())
return response
- @events_router.post("", response_model=RecipeTimelineEventOut, status_code=201)
+ @router.post("", response_model=RecipeTimelineEventOut, status_code=201)
def create_one(self, data: RecipeTimelineEventIn):
# if the user id is not specified, use the currently-authenticated user
data.user_id = data.user_id or self.user.id
@@ -81,11 +81,11 @@ class RecipeTimelineEventsController(BaseCrudController):
return event
- @events_router.get("/{item_id}", response_model=RecipeTimelineEventOut)
+ @router.get("/{item_id}", response_model=RecipeTimelineEventOut)
def get_one(self, item_id: UUID4):
return self.mixins.get_one(item_id)
- @events_router.put("/{item_id}", response_model=RecipeTimelineEventOut)
+ @router.put("/{item_id}", response_model=RecipeTimelineEventOut)
def update_one(self, item_id: UUID4, data: RecipeTimelineEventUpdate):
event = self.mixins.patch_one(data, item_id)
recipe = self.group_recipes.get_one(event.recipe_id, "id")
@@ -106,7 +106,7 @@ class RecipeTimelineEventsController(BaseCrudController):
return event
- @events_router.delete("/{item_id}", response_model=RecipeTimelineEventOut)
+ @router.delete("/{item_id}", response_model=RecipeTimelineEventOut)
def delete_one(self, item_id: UUID4):
event = self.mixins.delete_one(item_id)
if event.image_dir.exists():
@@ -136,7 +136,7 @@ class RecipeTimelineEventsController(BaseCrudController):
# ==================================================================================================================
# Image and Assets
- @events_router.put("/{item_id}/image", response_model=UpdateImageResponse)
+ @router.put("/{item_id}/image", response_model=UpdateImageResponse)
def update_event_image(self, item_id: UUID4, image: bytes = File(...), extension: str = Form(...)):
event = self.mixins.get_one(item_id)
data_service = RecipeDataService(event.recipe_id)
diff --git a/mealie/schema/recipe/__init__.py b/mealie/schema/recipe/__init__.py
index 4fe6861ef..2304c8a5e 100644
--- a/mealie/schema/recipe/__init__.py
+++ b/mealie/schema/recipe/__init__.py
@@ -75,6 +75,7 @@ from .recipe_scraper import ScrapeRecipe, ScrapeRecipeBase, ScrapeRecipeData, Sc
from .recipe_settings import RecipeSettings
from .recipe_share_token import RecipeShareToken, RecipeShareTokenCreate, RecipeShareTokenSave, RecipeShareTokenSummary
from .recipe_step import IngredientReferences, RecipeStep
+from .recipe_suggestion import RecipeSuggestionQuery, RecipeSuggestionResponse, RecipeSuggestionResponseItem
from .recipe_timeline_events import (
RecipeTimelineEventCreate,
RecipeTimelineEventIn,
@@ -109,6 +110,9 @@ __all__ = [
"RecipeTimelineEventUpdate",
"TimelineEventImage",
"TimelineEventType",
+ "RecipeSuggestionQuery",
+ "RecipeSuggestionResponse",
+ "RecipeSuggestionResponseItem",
"Nutrition",
"RecipeShareToken",
"RecipeShareTokenCreate",
diff --git a/mealie/schema/recipe/recipe_suggestion.py b/mealie/schema/recipe/recipe_suggestion.py
new file mode 100644
index 000000000..a1763c294
--- /dev/null
+++ b/mealie/schema/recipe/recipe_suggestion.py
@@ -0,0 +1,24 @@
+from mealie.schema._mealie.mealie_model import MealieModel
+from mealie.schema.recipe.recipe import RecipeSummary, RecipeTool
+from mealie.schema.recipe.recipe_ingredient import IngredientFood
+from mealie.schema.response.pagination import RequestQuery
+
+
+class RecipeSuggestionQuery(RequestQuery):
+ limit: int = 10
+
+ max_missing_foods: int = 5
+ max_missing_tools: int = 5
+
+ include_foods_on_hand: bool = True
+ include_tools_on_hand: bool = True
+
+
+class RecipeSuggestionResponseItem(MealieModel):
+ recipe: RecipeSummary
+ missing_foods: list[IngredientFood]
+ missing_tools: list[RecipeTool]
+
+
+class RecipeSuggestionResponse(MealieModel):
+ items: list[RecipeSuggestionResponseItem]
diff --git a/mealie/schema/response/__init__.py b/mealie/schema/response/__init__.py
index 69caa347a..c513794c5 100644
--- a/mealie/schema/response/__init__.py
+++ b/mealie/schema/response/__init__.py
@@ -1,5 +1,12 @@
# This file is auto-generated by gen_schema_exports.py
-from .pagination import OrderByNullPosition, OrderDirection, PaginationBase, PaginationQuery, RecipeSearchQuery
+from .pagination import (
+ OrderByNullPosition,
+ OrderDirection,
+ PaginationBase,
+ PaginationQuery,
+ RecipeSearchQuery,
+ RequestQuery,
+)
from .query_filter import (
LogicalOperator,
QueryFilterBuilder,
@@ -27,6 +34,7 @@ __all__ = [
"PaginationBase",
"PaginationQuery",
"RecipeSearchQuery",
+ "RequestQuery",
"SearchFilter",
"ErrorResponse",
"FileTokenResponse",
diff --git a/mealie/schema/response/pagination.py b/mealie/schema/response/pagination.py
index 27aae76e9..67b69a103 100644
--- a/mealie/schema/response/pagination.py
+++ b/mealie/schema/response/pagination.py
@@ -31,9 +31,7 @@ class RecipeSearchQuery(MealieModel):
_search_seed: str | None = None
-class PaginationQuery(MealieModel):
- page: int = 1
- per_page: int = 50
+class RequestQuery(MealieModel):
order_by: str | None = None
order_by_null_position: OrderByNullPosition | None = None
order_direction: OrderDirection = OrderDirection.desc
@@ -47,6 +45,11 @@ class PaginationQuery(MealieModel):
return pagination_seed
+class PaginationQuery(RequestQuery):
+ page: int = 1
+ per_page: int = 50
+
+
class PaginationBase(BaseModel, Generic[DataT]):
page: int = 1
per_page: int = 10
diff --git a/tests/integration_tests/public_explorer_tests/test_public_recipes.py b/tests/integration_tests/public_explorer_tests/test_public_recipes.py
index f76577497..ac6311555 100644
--- a/tests/integration_tests/public_explorer_tests/test_public_recipes.py
+++ b/tests/integration_tests/public_explorer_tests/test_public_recipes.py
@@ -1,5 +1,6 @@
import random
from typing import Any
+from uuid import uuid4
import pytest
from fastapi.testclient import TestClient
@@ -8,6 +9,7 @@ from pydantic import UUID4
from mealie.schema.cookbook.cookbook import SaveCookBook
from mealie.schema.recipe.recipe import Recipe
from mealie.schema.recipe.recipe_category import TagSave
+from mealie.schema.recipe.recipe_ingredient import RecipeIngredient, SaveIngredientFood
from tests.utils import api_routes
from tests.utils.factories import random_int, random_string
from tests.utils.fixture_schemas import TestUser
@@ -335,3 +337,71 @@ def test_public_recipe_cookbook_filter_with_recipes(
assert str(other_household_recipe.id) not in recipe_ids
else:
assert str(other_household_recipe.id) in recipe_ids
+
+
+@pytest.mark.parametrize("is_private_group", [True, False])
+@pytest.mark.parametrize("is_private_household", [True, False])
+@pytest.mark.parametrize("is_private_recipe", [True, False])
+def test_get_suggested_recipes(
+ api_client: TestClient,
+ unique_user: TestUser,
+ random_recipe: Recipe,
+ is_private_group: bool,
+ is_private_household: bool,
+ is_private_recipe: bool,
+):
+ database = unique_user.repos
+
+ ## Set Up Group
+ group = database.groups.get_one(unique_user.group_id)
+ assert group and group.preferences
+
+ group.preferences.private_group = is_private_group
+ database.group_preferences.update(group.id, group.preferences)
+
+ ## Set Up Household
+ household = database.households.get_one(unique_user.household_id)
+ assert household and household.preferences
+
+ household.preferences.private_household = is_private_household
+ household.preferences.recipe_public = not is_private_household
+ database.household_preferences.update(household.id, household.preferences)
+
+ ## Set Recipe `settings.public` attribute
+ assert random_recipe.settings
+ random_recipe.settings.public = not is_private_recipe
+ database.recipes.update(random_recipe.slug, random_recipe)
+
+ ## Add a known food to the recipe
+ known_food = database.ingredient_foods.create(
+ SaveIngredientFood(id=uuid4(), name=random_string(), group_id=unique_user.group_id)
+ )
+ random_recipe.recipe_ingredient = [RecipeIngredient(food_id=known_food.id, food=known_food)]
+ random_recipe.settings.disable_amount = False
+ database.recipes.update(random_recipe.slug, random_recipe)
+
+ ## Try to find suggested recipes
+ recipe_group = database.groups.get_by_slug_or_id(random_recipe.group_id)
+ recipe_household = database.households.get_by_slug_or_id(random_recipe.household_id)
+ assert recipe_group
+ assert recipe_household
+ response = api_client.get(
+ api_routes.explore_groups_group_slug_recipes_suggestions(recipe_group.slug),
+ params={"maxMissingFoods": 0, "foods": [str(known_food.id)], "includeFoodsOnHand": False},
+ )
+ if is_private_group:
+ assert response.status_code == 404
+ assert response.json()["detail"] == "group not found"
+ return
+
+ if is_private_household or is_private_recipe:
+ if is_private_group:
+ assert response.json()["detail"] == "group not found"
+ else:
+ assert response.json()["items"] == []
+ return
+
+ as_json = response.json()
+ assert len(as_json["items"]) == 1
+ assert as_json["items"][0]["recipe"]["name"] == random_recipe.name
+ assert as_json["items"][0]["recipe"]["slug"] == random_recipe.slug
diff --git a/tests/integration_tests/user_recipe_tests/test_recipe_suggestions.py b/tests/integration_tests/user_recipe_tests/test_recipe_suggestions.py
new file mode 100644
index 000000000..812d0223c
--- /dev/null
+++ b/tests/integration_tests/user_recipe_tests/test_recipe_suggestions.py
@@ -0,0 +1,581 @@
+import random
+from uuid import uuid4
+
+import pytest
+from fastapi.testclient import TestClient
+
+from mealie.schema.recipe.recipe import Recipe
+from mealie.schema.recipe.recipe_ingredient import IngredientFood, RecipeIngredient, SaveIngredientFood
+from mealie.schema.recipe.recipe_settings import RecipeSettings
+from mealie.schema.recipe.recipe_tool import RecipeToolOut, RecipeToolSave
+from tests.utils import api_routes
+from tests.utils.factories import random_int, random_string
+from tests.utils.fixture_schemas import TestUser
+
+
+def create_food(user: TestUser, on_hand: bool = False):
+ return user.repos.ingredient_foods.create(
+ SaveIngredientFood(id=uuid4(), name=random_string(), group_id=user.group_id, on_hand=on_hand)
+ )
+
+
+def create_tool(user: TestUser, on_hand: bool = False):
+ return user.repos.tools.create(
+ RecipeToolSave(id=uuid4(), name=random_string(), group_id=user.group_id, on_hand=on_hand)
+ )
+
+
+def create_recipe(
+ user: TestUser,
+ *,
+ foods: list[IngredientFood] | None = None,
+ tools: list[RecipeToolOut] | None = None,
+ disable_amount: bool = False,
+ **kwargs,
+):
+ if foods:
+ ingredients = [RecipeIngredient(food_id=food.id, food=food) for food in foods]
+ else:
+ ingredients = []
+
+ recipe = user.repos.recipes.create(
+ Recipe(
+ user_id=user.user_id,
+ group_id=user.group_id,
+ name=kwargs.pop("name", random_string()),
+ recipe_ingredient=ingredients,
+ tools=tools or [],
+ settings=RecipeSettings(disable_amount=disable_amount),
+ **kwargs,
+ )
+ )
+
+ return recipe
+
+
+@pytest.fixture(autouse=True)
+def base_recipes(unique_user: TestUser, h2_user: TestUser):
+ for user in [unique_user, h2_user]:
+ for _ in range(10):
+ create_recipe(
+ user,
+ foods=[create_food(user) for _ in range(random_int(5, 10))],
+ tools=[create_tool(user) for _ in range(random_int(5, 10))],
+ )
+
+
+@pytest.mark.parametrize("filter_foods", [True, False])
+@pytest.mark.parametrize("filter_tools", [True, False])
+def test_suggestion_filter(api_client: TestClient, unique_user: TestUser, filter_foods: bool, filter_tools: bool):
+ create_params: dict = {}
+ api_params: dict = {"maxMissingFoods": 0, "maxMissingTools": 0, "limit": 10}
+ if filter_foods:
+ known_food = create_food(unique_user)
+ create_params["foods"] = [known_food]
+ api_params["foods"] = [str(known_food.id)]
+ if filter_tools:
+ known_tool = create_tool(unique_user)
+ create_params["tools"] = [known_tool]
+ api_params["tools"] = [str(known_tool.id)]
+
+ recipes = [create_recipe(unique_user, **create_params) for _ in range(3)]
+ try:
+ expected_recipe_ids = {str(recipe.id) for recipe in recipes if recipe.id}
+ response = api_client.get(api_routes.recipes_suggestions, params=api_params, headers=unique_user.token)
+ response.raise_for_status()
+ data = response.json()
+
+ if not filter_foods and not filter_tools:
+ assert len(data["items"]) == 10
+ else:
+ assert len(data["items"]) == 3
+ for item in data["items"]:
+ assert item["recipe"]["id"] in expected_recipe_ids
+ assert item["missingFoods"] == []
+ assert item["missingTools"] == []
+ finally:
+ for recipe in recipes:
+ unique_user.repos.recipes.delete(recipe.slug)
+
+
+def test_food_suggestion_filter_with_max(api_client: TestClient, unique_user: TestUser):
+ food_1, food_2, food_3, food_4 = (create_food(unique_user) for _ in range(4))
+ recipe_exact = create_recipe(unique_user, foods=[food_1])
+ recipe_missing_one = create_recipe(unique_user, foods=[food_1, food_2])
+ recipe_missing_two = create_recipe(unique_user, foods=[food_1, food_2, food_3])
+ recipe_missing_three = create_recipe(unique_user, foods=[food_1, food_2, food_3, food_4])
+
+ try:
+ response = api_client.get(
+ api_routes.recipes_suggestions,
+ params={"maxMissingFoods": 1, "includeFoodsOnHand": False, "foods": [str(food_1.id)]},
+ headers=unique_user.token,
+ )
+ response.raise_for_status()
+ data = response.json()
+ fetched_recipe_ids = {item["recipe"]["id"] for item in data["items"]}
+ assert set(fetched_recipe_ids) == {str(recipe_exact.id), str(recipe_missing_one.id)}
+ for item in data["items"]:
+ missing_food_ids = [food["id"] for food in item["missingFoods"]]
+ if item["recipe"]["id"] == str(recipe_exact.id):
+ assert missing_food_ids == []
+ else:
+ assert missing_food_ids == [str(food_2.id)]
+
+ finally:
+ for recipe in [recipe_exact, recipe_missing_one, recipe_missing_two, recipe_missing_three]:
+ unique_user.repos.recipes.delete(recipe.slug)
+
+
+def test_tool_suggestion_filter_with_max(api_client: TestClient, unique_user: TestUser):
+ tool_1, tool_2, tool_3, tool_4 = (create_tool(unique_user) for _ in range(4))
+ recipe_exact = create_recipe(unique_user, tools=[tool_1])
+ recipe_missing_one = create_recipe(unique_user, tools=[tool_1, tool_2])
+ recipe_missing_two = create_recipe(unique_user, tools=[tool_1, tool_2, tool_3])
+ recipe_missing_three = create_recipe(unique_user, tools=[tool_1, tool_2, tool_3, tool_4])
+
+ try:
+ response = api_client.get(
+ api_routes.recipes_suggestions,
+ params={"maxMissingTools": 1, "includeToolsOnHand": False, "tools": [str(tool_1.id)]},
+ headers=unique_user.token,
+ )
+ response.raise_for_status()
+
+ data = response.json()
+ fetched_recipe_ids = {item["recipe"]["id"] for item in data["items"]}
+ assert set(fetched_recipe_ids) == {str(recipe_exact.id), str(recipe_missing_one.id)}
+ for item in data["items"]:
+ missing_tool_ids = [tool["id"] for tool in item["missingTools"]]
+ if item["recipe"]["id"] == str(recipe_exact.id):
+ assert missing_tool_ids == []
+ else:
+ assert missing_tool_ids == [str(tool_2.id)]
+
+ finally:
+ for recipe in [recipe_exact, recipe_missing_one, recipe_missing_two, recipe_missing_three]:
+ unique_user.repos.recipes.delete(recipe.slug)
+
+
+def test_ignore_empty_food_filter(api_client: TestClient, unique_user: TestUser):
+ known_tool = create_tool(unique_user)
+ recipe = create_recipe(
+ unique_user, foods=[create_food(unique_user) for _ in range(random_int(3, 5))], tools=[known_tool]
+ )
+
+ try:
+ response = api_client.get(
+ api_routes.recipes_suggestions,
+ params={"maxMissingFoods": 0, "maxMissingTools": 0, "tools": [str(known_tool.id)]},
+ headers=unique_user.token,
+ )
+ response.raise_for_status()
+
+ data = response.json()
+ assert len(data["items"]) == 1
+ item = data["items"][0]
+ assert item["recipe"]["id"] == str(recipe.id)
+ assert item["missingFoods"] == []
+ assert item["missingTools"] == []
+
+ finally:
+ unique_user.repos.recipes.delete(recipe.slug)
+
+
+def test_ignore_empty_tool_filter(api_client: TestClient, unique_user: TestUser):
+ known_food = create_food(unique_user)
+ recipe = create_recipe(
+ unique_user, foods=[known_food], tools=[create_tool(unique_user) for _ in range(random_int(3, 5))]
+ )
+
+ try:
+ response = api_client.get(
+ api_routes.recipes_suggestions,
+ params={"maxMissingFoods": 0, "maxMissingTools": 0, "foods": [str(known_food.id)]},
+ headers=unique_user.token,
+ )
+ response.raise_for_status()
+
+ data = response.json()
+ assert len(data["items"]) == 1
+ item = data["items"][0]
+ assert item["recipe"]["id"] == str(recipe.id)
+ assert item["missingFoods"] == []
+ assert item["missingTools"] == []
+
+ finally:
+ unique_user.repos.recipes.delete(recipe.slug)
+
+
+@pytest.mark.parametrize("include_on_hand", [True, False])
+def test_include_foods_on_hand(api_client: TestClient, unique_user: TestUser, include_on_hand: bool):
+ on_hand_food = create_food(unique_user, on_hand=True)
+ off_hand_food = create_food(unique_user, on_hand=False)
+ recipe = create_recipe(unique_user, foods=[on_hand_food, off_hand_food])
+
+ try:
+ response = api_client.get(
+ api_routes.recipes_suggestions,
+ params={
+ "maxMissingFoods": 0,
+ "maxMissingTools": 0,
+ "includeFoodsOnHand": include_on_hand,
+ "foods": [str(off_hand_food.id)],
+ },
+ headers=unique_user.token,
+ )
+ response.raise_for_status()
+
+ data = response.json()
+ if not include_on_hand:
+ assert len(data["items"]) == 0
+ else:
+ assert len(data["items"]) == 1
+ item = data["items"][0]
+ assert item["recipe"]["id"] == str(recipe.id)
+ assert item["missingFoods"] == []
+
+ finally:
+ unique_user.repos.recipes.delete(recipe.slug)
+
+
+@pytest.mark.parametrize("include_on_hand", [True, False])
+def test_include_tools_on_hand(api_client: TestClient, unique_user: TestUser, include_on_hand: bool):
+ on_hand_tool = create_tool(unique_user, on_hand=True)
+ off_hand_tool = create_tool(unique_user, on_hand=False)
+ recipe = create_recipe(unique_user, tools=[on_hand_tool, off_hand_tool])
+
+ try:
+ response = api_client.get(
+ api_routes.recipes_suggestions,
+ params={
+ "maxMissingFoods": 0,
+ "maxMissingTools": 0,
+ "includeToolsOnHand": include_on_hand,
+ "tools": [str(off_hand_tool.id)],
+ },
+ headers=unique_user.token,
+ )
+ response.raise_for_status()
+
+ data = response.json()
+ if not include_on_hand:
+ assert len(data["items"]) == 0
+ else:
+ assert len(data["items"]) == 1
+ item = data["items"][0]
+ assert item["recipe"]["id"] == str(recipe.id)
+ assert item["missingTools"] == []
+
+ finally:
+ unique_user.repos.recipes.delete(recipe.slug)
+
+
+def test_exclude_recipes_with_no_foods(api_client: TestClient, unique_user: TestUser):
+ known_food = create_food(unique_user)
+ recipe_with_foods = create_recipe(unique_user, foods=[known_food])
+ recipe_without_foods = create_recipe(unique_user, foods=[])
+
+ try:
+ response = api_client.get(
+ api_routes.recipes_suggestions,
+ params={"maxMissingFoods": 0, "maxMissingTools": 0, "foods": [str(known_food.id)]},
+ headers=unique_user.token,
+ )
+ response.raise_for_status()
+
+ data = response.json()
+ assert {item["recipe"]["id"] for item in data["items"]} == {str(recipe_with_foods.id)}
+ for item in data["items"]:
+ assert item["missingFoods"] == []
+
+ finally:
+ for recipe in [recipe_with_foods, recipe_without_foods]:
+ unique_user.repos.recipes.delete(recipe.slug)
+
+
+def test_include_recipes_with_no_tools(api_client: TestClient, unique_user: TestUser):
+ known_tool = create_tool(unique_user)
+ recipe_with_tools = create_recipe(unique_user, tools=[known_tool])
+ recipe_without_tools = create_recipe(unique_user, tools=[])
+
+ try:
+ response = api_client.get(
+ api_routes.recipes_suggestions,
+ params={"maxMissingFoods": 0, "maxMissingTools": 0, "tools": [str(known_tool.id)]},
+ headers=unique_user.token,
+ )
+ response.raise_for_status()
+
+ data = response.json()
+ assert {item["recipe"]["id"] for item in data["items"]} == {
+ str(recipe_with_tools.id),
+ str(recipe_without_tools.id),
+ }
+ for item in data["items"]:
+ assert item["missingTools"] == []
+
+ finally:
+ for recipe in [recipe_with_tools, recipe_without_tools]:
+ unique_user.repos.recipes.delete(recipe.slug)
+
+
+def test_ignore_recipes_with_ingredient_amounts_disabled_with_foods(api_client: TestClient, unique_user: TestUser):
+ known_food = create_food(unique_user)
+ recipe_with_amounts = create_recipe(unique_user, foods=[known_food])
+ recipe_without_amounts = create_recipe(unique_user, foods=[known_food], disable_amount=True)
+
+ try:
+ response = api_client.get(
+ api_routes.recipes_suggestions,
+ params={"maxMissingFoods": 0, "maxMissingTools": 0, "foods": [str(known_food.id)]},
+ headers=unique_user.token,
+ )
+ response.raise_for_status()
+
+ data = response.json()
+ assert {item["recipe"]["id"] for item in data["items"]} == {str(recipe_with_amounts.id)}
+ for item in data["items"]:
+ assert item["missingFoods"] == []
+
+ finally:
+ for recipe in [recipe_with_amounts, recipe_without_amounts]:
+ unique_user.repos.recipes.delete(recipe.slug)
+
+
+def test_include_recipes_with_ingredient_amounts_disabled_without_foods(api_client: TestClient, unique_user: TestUser):
+ known_tool = create_tool(unique_user)
+ recipe_with_amounts = create_recipe(unique_user, tools=[known_tool])
+ recipe_without_amounts = create_recipe(unique_user, tools=[known_tool], disable_amount=True)
+
+ try:
+ response = api_client.get(
+ api_routes.recipes_suggestions,
+ params={
+ "maxMissingFoods": 0,
+ "maxMissingTools": 0,
+ "includeFoodsOnHand": False,
+ "tools": [str(known_tool.id)],
+ },
+ headers=unique_user.token,
+ )
+ response.raise_for_status()
+
+ data = response.json()
+ assert {item["recipe"]["id"] for item in data["items"]} == {
+ str(recipe_with_amounts.id),
+ str(recipe_without_amounts.id),
+ }
+ for item in data["items"]:
+ assert item["missingFoods"] == []
+
+ finally:
+ for recipe in [recipe_with_amounts, recipe_without_amounts]:
+ unique_user.repos.recipes.delete(recipe.slug)
+
+
+def test_exclude_recipes_with_no_user_foods(api_client: TestClient, unique_user: TestUser):
+ known_food = create_food(unique_user)
+ food_on_hand = create_food(unique_user, on_hand=True)
+ recipe_with_user_food = create_recipe(unique_user, foods=[known_food])
+ recipe_with_on_hand_food = create_recipe(unique_user, foods=[food_on_hand])
+
+ try:
+ response = api_client.get(
+ api_routes.recipes_suggestions,
+ params={"maxMissingFoods": 10, "includeFoodsOnHand": True, "foods": [str(known_food.id)]},
+ headers=unique_user.token,
+ )
+ response.raise_for_status()
+
+ data = response.json()
+ assert {item["recipe"]["id"] for item in data["items"]} == {str(recipe_with_user_food.id)}
+ assert data["items"][0]["missingFoods"] == []
+
+ finally:
+ for recipe in [recipe_with_user_food, recipe_with_on_hand_food]:
+ unique_user.repos.recipes.delete(recipe.slug)
+
+
+def test_recipe_order(api_client: TestClient, unique_user: TestUser):
+ user_food_1, user_food_2, other_food_1, other_food_2, other_food_3 = (create_food(unique_user) for _ in range(5))
+ user_tool_1, other_tool_1, other_tool_2 = (create_tool(unique_user) for _ in range(3))
+ food_on_hand = create_food(unique_user, on_hand=True)
+
+ recipe_lambdas = [
+ # No missing tools or foods
+ (0, lambda: create_recipe(unique_user, tools=[user_tool_1], foods=[user_food_1])),
+ # No missing tools, one missing food
+ (1, lambda: create_recipe(unique_user, tools=[user_tool_1], foods=[user_food_1, other_food_1])),
+ # One missing tool, no missing foods
+ (2, lambda: create_recipe(unique_user, tools=[user_tool_1, other_tool_1], foods=[user_food_1])),
+ # One missing tool, one missing food
+ (3, lambda: create_recipe(unique_user, tools=[user_tool_1, other_tool_1], foods=[user_food_1, other_food_1])),
+ # Two missing tools, two missing foods, two user foods
+ (
+ 4,
+ lambda: create_recipe(
+ unique_user,
+ tools=[user_tool_1, other_tool_1, other_tool_2],
+ foods=[user_food_1, user_food_2, other_food_1, other_food_2],
+ ),
+ ),
+ # Two missing tools, two missing foods, one user food
+ (
+ 5,
+ lambda: create_recipe(
+ unique_user,
+ tools=[user_tool_1, other_tool_1, other_tool_2],
+ foods=[user_food_1, other_food_1, other_food_2],
+ ),
+ ),
+ # Two missing tools, three missing foods, two user foods, don't include food on hand
+ (
+ 6,
+ lambda: create_recipe(
+ unique_user,
+ tools=[user_tool_1, other_tool_1, other_tool_2],
+ foods=[user_food_1, user_food_2, other_food_1, other_food_2, other_food_3],
+ ),
+ ),
+ # Two missing tools, three missing foods, one user food, include food on hand
+ (
+ 7,
+ lambda: create_recipe(
+ unique_user,
+ tools=[user_tool_1, other_tool_1, other_tool_2],
+ foods=[food_on_hand, user_food_1, other_food_1, other_food_2, other_food_3],
+ ),
+ ),
+ ]
+
+ # create recipes in a random order
+ random.shuffle(recipe_lambdas)
+ recipe_tuples: list[tuple[int, Recipe]] = []
+ for i, recipe_lambda in recipe_lambdas:
+ recipe_tuples.append((i, recipe_lambda()))
+
+ recipe_tuples.sort(key=lambda x: x[0])
+ recipes = [recipe_tuple[1] for recipe_tuple in recipe_tuples]
+
+ try:
+ response = api_client.get(
+ api_routes.recipes_suggestions,
+ params={
+ "maxMissingFoods": 3,
+ "maxMissingTools": 3,
+ "includeFoodsOnHand": True,
+ "includeToolsOnHand": True,
+ "limit": 10,
+ "foods": [str(user_food_1.id), str(user_food_2.id)],
+ "tools": [str(user_tool_1.id)],
+ },
+ headers=unique_user.token,
+ )
+ response.raise_for_status()
+
+ data = response.json()
+ assert len(data["items"]) == len(recipes)
+ for i, (item, recipe) in enumerate(zip(data["items"], recipes, strict=True)):
+ try:
+ assert item["recipe"]["id"] == str(recipe.id)
+ except AssertionError as e:
+ raise AssertionError(f"Recipe in position {i} was incorrect") from e
+
+ finally:
+ for recipe in recipes:
+ unique_user.repos.recipes.delete(recipe.slug)
+
+
+def test_respect_user_sort(api_client: TestClient, unique_user: TestUser):
+ known_food = create_food(unique_user)
+
+ # Create recipes with names A, B, C, D out of order
+ recipe_b = create_recipe(unique_user, foods=[known_food], name="B")
+ recipe_c = create_recipe(unique_user, foods=[known_food, create_food(unique_user)], name="C")
+ recipe_a = create_recipe(unique_user, foods=[known_food, create_food(unique_user)], name="A")
+ recipe_d = create_recipe(unique_user, foods=[known_food, create_food(unique_user)], name="D")
+
+ try:
+ response = api_client.get(
+ api_routes.recipes_suggestions,
+ params={"maxMissingFoods": 1, "foods": [str(known_food.id)], "orderBy": "name", "orderDirection": "desc"},
+ headers=unique_user.token,
+ )
+ response.raise_for_status()
+
+ data = response.json()
+ assert len(data["items"]) == 4
+
+ # "B" should come first because it matches all foods, even though the user sort would put it last
+ assert [item["recipe"]["name"] for item in data["items"]] == ["B", "D", "C", "A"]
+
+ finally:
+ for recipe in [recipe_a, recipe_b, recipe_c, recipe_d]:
+ unique_user.repos.recipes.delete(recipe.slug)
+
+
+def test_limit_param(api_client: TestClient, unique_user: TestUser):
+ known_food = create_food(unique_user)
+ limit = random_int(12, 20)
+ recipes = [create_recipe(unique_user, foods=[known_food]) for _ in range(limit)]
+
+ try:
+ response = api_client.get(
+ api_routes.recipes_suggestions,
+ params={"maxMissingFoods": 0, "foods": [str(known_food.id)], "limit": limit},
+ headers=unique_user.token,
+ )
+ response.raise_for_status()
+ assert len(response.json()["items"]) == limit
+
+ finally:
+ for recipe in recipes:
+ unique_user.repos.recipes.delete(recipe.slug)
+
+
+def test_query_filter(api_client: TestClient, unique_user: TestUser):
+ known_food = create_food(unique_user)
+ recipes_with_prefix = [
+ create_recipe(unique_user, foods=[known_food], name=f"MY_PREFIX{random_string()}") for _ in range(10)
+ ]
+ recipes_without_prefix = [
+ create_recipe(unique_user, foods=[known_food], name=f"MY_OTHER_PREFIX{random_string()}") for _ in range(10)
+ ]
+
+ try:
+ response = api_client.get(
+ api_routes.recipes_suggestions,
+ params={"maxMissingFoods": 0, "foods": [str(known_food.id)], "queryFilter": 'name LIKE "MY_PREFIX%"'},
+ headers=unique_user.token,
+ )
+ response.raise_for_status()
+ assert len(response.json()["items"]) == len(recipes_with_prefix)
+ assert {item["recipe"]["id"] for item in response.json()["items"]} == {
+ str(recipe.id) for recipe in recipes_with_prefix
+ }
+
+ finally:
+ for recipe in recipes_with_prefix + recipes_without_prefix:
+ unique_user.repos.recipes.delete(recipe.slug)
+
+
+def test_include_cross_household_recipes(api_client: TestClient, unique_user: TestUser, h2_user: TestUser):
+ known_food = create_food(unique_user)
+ recipe = create_recipe(unique_user, foods=[known_food])
+ other_recipe = create_recipe(h2_user, foods=[known_food])
+
+ try:
+ response = api_client.get(
+ api_routes.recipes_suggestions,
+ params={"maxMissingFoods": 0, "foods": [str(known_food.id)], "includeCrossHousehold": True},
+ headers=h2_user.token,
+ )
+ response.raise_for_status()
+ data = response.json()
+ assert len(data["items"]) == 2
+ assert {item["recipe"]["id"] for item in data["items"]} == {str(recipe.id), str(other_recipe.id)}
+
+ finally:
+ unique_user.repos.recipes.delete(recipe.slug)
+ h2_user.repos.recipes.delete(other_recipe.slug)
diff --git a/tests/unit_tests/repository_tests/test_pagination.py b/tests/unit_tests/repository_tests/test_pagination.py
index 2ff5b3b8f..528d77419 100644
--- a/tests/unit_tests/repository_tests/test_pagination.py
+++ b/tests/unit_tests/repository_tests/test_pagination.py
@@ -634,7 +634,8 @@ def test_pagination_order_by_multiple(unique_user: TestUser, order_direction: Or
random.shuffle(abbreviations)
random.shuffle(descriptions)
- assert abbreviations != descriptions
+ while abbreviations == descriptions:
+ random.shuffle(descriptions)
units_to_create: list[SaveIngredientUnit] = []
for abbreviation in abbreviations:
@@ -694,7 +695,8 @@ def test_pagination_order_by_multiple_directions(
random.shuffle(abbreviations)
random.shuffle(descriptions)
- assert abbreviations != descriptions
+ while abbreviations == descriptions:
+ random.shuffle(descriptions)
units_to_create: list[SaveIngredientUnit] = []
for abbreviation in abbreviations:
diff --git a/tests/utils/api_routes/__init__.py b/tests/utils/api_routes/__init__.py
index ce08bc98b..9bbe65619 100644
--- a/tests/utils/api_routes/__init__.py
+++ b/tests/utils/api_routes/__init__.py
@@ -161,6 +161,8 @@ recipes_create_zip = "/api/recipes/create/zip"
"""`/api/recipes/create/zip`"""
recipes_exports = "/api/recipes/exports"
"""`/api/recipes/exports`"""
+recipes_suggestions = "/api/recipes/suggestions"
+"""`/api/recipes/suggestions`"""
recipes_test_scrape_url = "/api/recipes/test-scrape-url"
"""`/api/recipes/test-scrape-url`"""
recipes_timeline_events = "/api/recipes/timeline/events"
@@ -303,6 +305,11 @@ def explore_groups_group_slug_recipes_recipe_slug(group_slug, recipe_slug):
return f"{prefix}/explore/groups/{group_slug}/recipes/{recipe_slug}"
+def explore_groups_group_slug_recipes_suggestions(group_slug):
+ """`/api/explore/groups/{group_slug}/recipes/suggestions`"""
+ return f"{prefix}/explore/groups/{group_slug}/recipes/suggestions"
+
+
def foods_item_id(item_id):
"""`/api/foods/{item_id}`"""
return f"{prefix}/foods/{item_id}"
From 393a1e4c1aadf7af7ba008989b5cc1c6f67a5995 Mon Sep 17 00:00:00 2001
From: Michael Genson <71845777+michael-genson@users.noreply.github.com>
Date: Tue, 3 Dec 2024 11:47:05 -0600
Subject: [PATCH 065/238] fix: Cocktail Builder Enhancements (#4672)
---
.../g/_groupSlug/recipes/finder/index.vue | 24 +++++++++++++------
1 file changed, 17 insertions(+), 7 deletions(-)
diff --git a/frontend/pages/g/_groupSlug/recipes/finder/index.vue b/frontend/pages/g/_groupSlug/recipes/finder/index.vue
index cd0248b4c..86219f35a 100644
--- a/frontend/pages/g/_groupSlug/recipes/finder/index.vue
+++ b/frontend/pages/g/_groupSlug/recipes/finder/index.vue
@@ -220,7 +220,7 @@
-
+
([]);
function addFood(food: IngredientFood) {
selectedFoods.value.push(food);
+ handleFoodUpdates();
}
function removeFood(food: IngredientFood) {
selectedFoods.value = selectedFoods.value.filter((f) => f.id !== food.id);
+ handleFoodUpdates();
+ }
+ function handleFoodUpdates() {
+ selectedFoods.value.sort((a, b) => (a.pluralName || a.name).localeCompare(b.pluralName || b.name));
+ preferences.value.foodIds = selectedFoods.value.map((food) => food.id);
}
watch(
() => selectedFoods.value,
() => {
- selectedFoods.value.sort((a, b) => (a.pluralName || a.name).localeCompare(b.pluralName || b.name));
- preferences.value.foodIds = selectedFoods.value.map((food) => food.id);
- }
+ handleFoodUpdates();
+ },
)
const toolStore = isOwnGroup.value ? useToolStore() : usePublicToolStore(groupSlug.value);
const selectedTools = ref([]);
function addTool(tool: RecipeTool) {
selectedTools.value.push(tool);
+ handleToolUpdates();
}
function removeTool(tool: RecipeTool) {
selectedTools.value = selectedTools.value.filter((t) => t.id !== tool.id);
+ handleToolUpdates();
+ }
+ function handleToolUpdates() {
+ selectedTools.value.sort((a, b) => a.name.localeCompare(b.name));
+ preferences.value.toolIds = selectedTools.value.map((tool) => tool.id);
}
watch(
() => selectedTools.value,
() => {
- selectedTools.value.sort((a, b) => a.name.localeCompare(b.name));
- preferences.value.toolIds = selectedTools.value.map((tool) => tool.id);
+ handleToolUpdates();
}
)
From da3e1b4c00b0f0f6d02c2f5c3c14a564f1744062 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 3 Dec 2024 19:03:35 +0100
Subject: [PATCH 066/238] fix(deps): update dependency openai to v1.56.1
(#4673)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index cb0d50724..55eaab943 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1602,13 +1602,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "openai"
-version = "1.56.0"
+version = "1.56.1"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.8"
files = [
- {file = "openai-1.56.0-py3-none-any.whl", hash = "sha256:0751a6e139a09fca2e9cbbe8a62bfdab901b5865249d2555d005decf966ef9c3"},
- {file = "openai-1.56.0.tar.gz", hash = "sha256:f7fa159c8e18e7f9a8d71ff4b8052452ae70a4edc6b76a6e97eda00d5364923f"},
+ {file = "openai-1.56.1-py3-none-any.whl", hash = "sha256:38e61183c2a98fedebbbb04a909a052d9f897358b070483fc0caff17300a227c"},
+ {file = "openai-1.56.1.tar.gz", hash = "sha256:8b0449f22a0c318441eae8a8a789753c3b2cac86542be51ca45df788e26aa180"},
]
[package.dependencies]
From 497424528dbd7d09f6469c3602cdbcf76a9d30e0 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 3 Dec 2024 15:09:12 -0600
Subject: [PATCH 067/238] fix(deps): update dependency pydantic to v2.10.3
(#4674)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 55eaab943..45f414fa3 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2071,13 +2071,13 @@ files = [
[[package]]
name = "pydantic"
-version = "2.10.2"
+version = "2.10.3"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e"},
- {file = "pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa"},
+ {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"},
+ {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"},
]
[package.dependencies]
From f1e9615efda1aa6066079ee1ae111f63e3af072e Mon Sep 17 00:00:00 2001
From: Arshad Basha <43876926+Arshad561@users.noreply.github.com>
Date: Tue, 3 Dec 2024 19:07:54 -0700
Subject: [PATCH 068/238] fix: Meal Plan Notes allow Submission Without
Title/Note Text (#4615)
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
---
.../pages/household/mealplan/planner/edit.vue | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/frontend/pages/household/mealplan/planner/edit.vue b/frontend/pages/household/mealplan/planner/edit.vue
index a03f8c595..a94f44661 100644
--- a/frontend/pages/household/mealplan/planner/edit.vue
+++ b/frontend/pages/household/mealplan/planner/edit.vue
@@ -22,6 +22,7 @@
resetDialog();
"
@close="resetDialog()"
+ :submitDisabled="isCreateDisabled"
>
-
+
@@ -253,6 +255,7 @@ export default defineComponent({
const api = useUserApi();
const { $auth } = useContext();
const { household } = useHouseholdSelf();
+ const requiredRule = (value: any) => !!value || "Required."
const state = ref({
dialog: false,
@@ -315,6 +318,14 @@ export default defineComponent({
userId: $auth.user?.id || "",
});
+ const isCreateDisabled = computed(() => {
+ if (dialog.note) {
+ return !newMeal.title.trim();
+ }
+ return !newMeal.recipeId;
+ });
+
+
function openDialog(date: Date) {
newMeal.date = format(date, "yyyy-MM-dd");
state.value.dialog = true;
@@ -373,6 +384,8 @@ export default defineComponent({
onMoveCallback,
planTypeOptions,
getEntryTypeText,
+ requiredRule,
+ isCreateDisabled,
// Dialog
dialog,
From a6cbf1308e36a91ce67a2af354b6f005ddb095fc Mon Sep 17 00:00:00 2001
From: Michael Chisholm
Date: Thu, 5 Dec 2024 02:57:57 +1000
Subject: [PATCH 069/238] feat: Move alembic config into mealie package for
easier distribution (#4329)
---
Taskfile.yml | 2 +-
docker/Dockerfile | 4 ----
{alembic => mealie/alembic}/README | 0
alembic.ini => mealie/alembic/alembic.ini | 2 +-
{alembic => mealie/alembic}/env.py | 2 +-
{alembic => mealie/alembic}/script.py.mako | 2 +-
...22-02-21-19.56.24_6b0f5f32d602_initial_tables.py | 0
...707191_convert_quantity_from_integer_to_float.py | 0
...dbee5fe9_add_original_text_column_to_recipes_.py | 0
...1-19.19.55_59eb59135381_add_tags_to_cookbooks.py | 0
...c897ad62_add_require_all_for_cookbook_filters.py | 0
..._ab0bae02578f_add_use_abbreviation_column_to_.py | 0
...-21.05.34_f30cf048c228_add_new_webhook_fields.py | 0
...910655_add_login_attemps_and_locked_at_field_.py | 0
...bfa50d0ed_add_is_ocr_recipe_column_to_recipes.py | 0
...719d_add_extras_to_shopping_lists_list_items_.py | 0
...2ea7a807915c_add_recipe_timeline_events_table.py | 0
...3519381ad_renamed_timeline_event_message_and_.py | 0
...066ad_add_recipe_scale_to_shopping_list_item_.py | 0
....44_165d943c64ee_add_related_user_to_mealplan.py | 0
...5f73b01a7a_add_missing_foreign_key_and_order_.py | 0
...bf731a0_add_more_indices_necessary_for_search.py | 0
...5ab195a474eb_add_normalized_search_properties.py | 0
...4a08da2108_added_shopping_list_label_settings.py | 0
...52_38514b39a824_add_auth_method_to_user_table.py | 0
...3-06.47.04_b3dbb554ba53_postgres_fuzzy_search.py | 0
...-08-06-21.00.34_04ac51cbe9a4_added_group_slug.py | 0
...5b5225403_added_recipe_note_to_shopping_list_.py | 0
...cfdad6b7355_remove_tool_name_and_slug_unique_.py | 0
...b154f79a_added_normalized_unit_and_food_names.py | 0
...4.29.26_dded3119c1fe_added_unique_constraints.py | 0
...fe99_added_plural_names_and_alias_tables_for_.py | 0
...5.07_2298bb460ffd_added_user_to_shopping_list.py | 2 +-
...10-05.08.32_09aba125b57a_add_oidc_auth_method.py | 0
...d2de42_migrate_favorites_and_ratings_to_user_.py | 2 +-
...1.05.20_7788478a0338_add_group_recipe_actions.py | 2 +-
...0.17.03_32d69327997b_add_staple_flag_to_foods.py | 3 +--
...24-07-12-16.16.29_feecc8ffb956_add_households.py | 2 +-
...ffdf_added_household_recipe_lock_setting_and_.py | 1 -
...4bd37ccc8_add_households_filter_to_meal_plans.py | 2 +-
..._602927e1013e_add_the_rest_of_the_schema_org_.py | 1 -
...40fd06_added_query_filter_string_to_cookbook_.py | 2 +-
...97397b4631_add_summary_to_recipe_instructions.py | 1 -
....50.59_b1020f328e98_add_recipe_yield_quantity.py | 2 +-
mealie/db/init_db.py | 8 ++++----
mealie/services/backups_v2/alchemy_exporter.py | 13 ++++---------
pyproject.toml | 4 ++--
tests/utils/alembic_reader.py | 4 ++--
48 files changed, 24 insertions(+), 37 deletions(-)
rename {alembic => mealie/alembic}/README (100%)
rename alembic.ini => mealie/alembic/alembic.ini (98%)
rename {alembic => mealie/alembic}/env.py (100%)
rename {alembic => mealie/alembic}/script.py.mako (100%)
rename {alembic => mealie/alembic}/versions/2022-02-21-19.56.24_6b0f5f32d602_initial_tables.py (100%)
rename {alembic => mealie/alembic}/versions/2022-03-23-17.43.34_263dd6707191_convert_quantity_from_integer_to_float.py (100%)
rename {alembic => mealie/alembic}/versions/2022-03-27-19.30.28_f1a2dbee5fe9_add_original_text_column_to_recipes_.py (100%)
rename {alembic => mealie/alembic}/versions/2022-03-31-19.19.55_59eb59135381_add_tags_to_cookbooks.py (100%)
rename {alembic => mealie/alembic}/versions/2022-04-03-10.48.51_09dfc897ad62_add_require_all_for_cookbook_filters.py (100%)
rename {alembic => mealie/alembic}/versions/2022-06-01-11.12.06_ab0bae02578f_add_use_abbreviation_column_to_.py (100%)
rename {alembic => mealie/alembic}/versions/2022-06-15-21.05.34_f30cf048c228_add_new_webhook_fields.py (100%)
rename {alembic => mealie/alembic}/versions/2022-08-12-19.05.59_188374910655_add_login_attemps_and_locked_at_field_.py (100%)
rename {alembic => mealie/alembic}/versions/2022-08-13-17.07.07_089bfa50d0ed_add_is_ocr_recipe_column_to_recipes.py (100%)
rename {alembic => mealie/alembic}/versions/2022-08-29-13.57.40_44e8d670719d_add_extras_to_shopping_lists_list_items_.py (100%)
rename {alembic => mealie/alembic}/versions/2022-09-27-14.53.14_2ea7a807915c_add_recipe_timeline_events_table.py (100%)
rename {alembic => mealie/alembic}/versions/2022-11-03-13.10.24_1923519381ad_renamed_timeline_event_message_and_.py (100%)
rename {alembic => mealie/alembic}/versions/2022-11-22-03.42.45_167eb69066ad_add_recipe_scale_to_shopping_list_item_.py (100%)
rename {alembic => mealie/alembic}/versions/2023-01-21-16.54.44_165d943c64ee_add_related_user_to_mealplan.py (100%)
rename {alembic => mealie/alembic}/versions/2023-02-07-20.57.21_ff5f73b01a7a_add_missing_foreign_key_and_order_.py (100%)
rename {alembic => mealie/alembic}/versions/2023-02-10-21.18.32_16160bf731a0_add_more_indices_necessary_for_search.py (100%)
rename {alembic => mealie/alembic}/versions/2023-02-14-20.45.41_5ab195a474eb_add_normalized_search_properties.py (100%)
rename {alembic => mealie/alembic}/versions/2023-02-21-22.03.19_b04a08da2108_added_shopping_list_label_settings.py (100%)
rename {alembic => mealie/alembic}/versions/2023-02-22-21.45.52_38514b39a824_add_auth_method_to_user_table.py (100%)
rename {alembic => mealie/alembic}/versions/2023-04-13-06.47.04_b3dbb554ba53_postgres_fuzzy_search.py (100%)
rename {alembic => mealie/alembic}/versions/2023-08-06-21.00.34_04ac51cbe9a4_added_group_slug.py (100%)
rename {alembic => mealie/alembic}/versions/2023-08-14-19.30.49_1825b5225403_added_recipe_note_to_shopping_list_.py (100%)
rename {alembic => mealie/alembic}/versions/2023-08-15-16.25.07_bcfdad6b7355_remove_tool_name_and_slug_unique_.py (100%)
rename {alembic => mealie/alembic}/versions/2023-09-01-14.55.42_0341b154f79a_added_normalized_unit_and_food_names.py (100%)
rename {alembic => mealie/alembic}/versions/2023-10-04-14.29.26_dded3119c1fe_added_unique_constraints.py (100%)
rename {alembic => mealie/alembic}/versions/2023-10-19-19.22.55_ba1e4a6cfe99_added_plural_names_and_alias_tables_for_.py (100%)
rename {alembic => mealie/alembic}/versions/2024-02-23-16.15.07_2298bb460ffd_added_user_to_shopping_list.py (100%)
rename {alembic => mealie/alembic}/versions/2024-03-10-05.08.32_09aba125b57a_add_oidc_auth_method.py (100%)
rename {alembic => mealie/alembic}/versions/2024-03-18-02.28.15_d7c6efd2de42_migrate_favorites_and_ratings_to_user_.py (100%)
rename {alembic => mealie/alembic}/versions/2024-04-07-01.05.20_7788478a0338_add_group_recipe_actions.py (100%)
rename {alembic => mealie/alembic}/versions/2024-06-22-10.17.03_32d69327997b_add_staple_flag_to_foods.py (99%)
rename {alembic => mealie/alembic}/versions/2024-07-12-16.16.29_feecc8ffb956_add_households.py (100%)
rename {alembic => mealie/alembic}/versions/2024-09-02-21.39.49_be568e39ffdf_added_household_recipe_lock_setting_and_.py (99%)
rename {alembic => mealie/alembic}/versions/2024-09-18-14.52.55_1fe4bd37ccc8_add_households_filter_to_meal_plans.py (100%)
rename {alembic => mealie/alembic}/versions/2024-10-01-14.17.00_602927e1013e_add_the_rest_of_the_schema_org_.py (99%)
rename {alembic => mealie/alembic}/versions/2024-10-08-21.17.31_86054b40fd06_added_query_filter_string_to_cookbook_.py (100%)
rename {alembic => mealie/alembic}/versions/2024-10-20-09.47.46_3897397b4631_add_summary_to_recipe_instructions.py (99%)
rename {alembic => mealie/alembic}/versions/2024-10-23-15.50.59_b1020f328e98_add_recipe_yield_quantity.py (100%)
diff --git a/Taskfile.yml b/Taskfile.yml
index a9ca44ac0..70f6f23dd 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -151,7 +151,7 @@ tasks:
py:migrate:
desc: generates a new database migration file e.g. task py:migrate -- "add new column"
cmds:
- - poetry run alembic revision --autogenerate -m "{{ .CLI_ARGS }}"
+ - poetry run alembic --config mealie/alembic/alembic.ini revision --autogenerate -m "{{ .CLI_ARGS }}"
- task: py:format
ui:build:
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 6c9c09066..056825c41 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -109,10 +109,6 @@ COPY --from=crfpp /usr/local/bin/crf_test /usr/local/bin/crf_test
COPY ./mealie $MEALIE_HOME/mealie
COPY ./poetry.lock ./pyproject.toml $MEALIE_HOME/
-# Alembic
-COPY ./alembic $MEALIE_HOME/alembic
-COPY ./alembic.ini $MEALIE_HOME/
-
# venv already has runtime deps installed we get a quicker install
WORKDIR $MEALIE_HOME
RUN . $VENV_PATH/bin/activate && poetry install -E pgsql --only main
diff --git a/alembic/README b/mealie/alembic/README
similarity index 100%
rename from alembic/README
rename to mealie/alembic/README
diff --git a/alembic.ini b/mealie/alembic/alembic.ini
similarity index 98%
rename from alembic.ini
rename to mealie/alembic/alembic.ini
index c068265c8..d955e61be 100644
--- a/alembic.ini
+++ b/mealie/alembic/alembic.ini
@@ -2,7 +2,7 @@
[alembic]
# path to migration scripts
-script_location = alembic
+script_location = %(here)s
# template used to generate migration files
file_template = %%(year)d-%%(month).2d-%%(day).2d-%%(hour).2d.%%(minute).2d.%%(second).2d_%%(rev)s_%%(slug)s
diff --git a/alembic/env.py b/mealie/alembic/env.py
similarity index 100%
rename from alembic/env.py
rename to mealie/alembic/env.py
index 4b38d2830..bff312261 100644
--- a/alembic/env.py
+++ b/mealie/alembic/env.py
@@ -1,9 +1,9 @@
from typing import Any
import sqlalchemy as sa
+from alembic import context
import mealie.db.models._all_models # noqa: F401
-from alembic import context
from mealie.core.config import get_app_settings
from mealie.db.models._model_base import SqlAlchemyBase
diff --git a/alembic/script.py.mako b/mealie/alembic/script.py.mako
similarity index 100%
rename from alembic/script.py.mako
rename to mealie/alembic/script.py.mako
index 3bf7e9e4d..ccd9c8a36 100644
--- a/alembic/script.py.mako
+++ b/mealie/alembic/script.py.mako
@@ -6,9 +6,9 @@ Create Date: ${create_date}
"""
import sqlalchemy as sa
+from alembic import op
import mealie.db.migration_types
-from alembic import op
% if imports:
${imports}
% endif
diff --git a/alembic/versions/2022-02-21-19.56.24_6b0f5f32d602_initial_tables.py b/mealie/alembic/versions/2022-02-21-19.56.24_6b0f5f32d602_initial_tables.py
similarity index 100%
rename from alembic/versions/2022-02-21-19.56.24_6b0f5f32d602_initial_tables.py
rename to mealie/alembic/versions/2022-02-21-19.56.24_6b0f5f32d602_initial_tables.py
diff --git a/alembic/versions/2022-03-23-17.43.34_263dd6707191_convert_quantity_from_integer_to_float.py b/mealie/alembic/versions/2022-03-23-17.43.34_263dd6707191_convert_quantity_from_integer_to_float.py
similarity index 100%
rename from alembic/versions/2022-03-23-17.43.34_263dd6707191_convert_quantity_from_integer_to_float.py
rename to mealie/alembic/versions/2022-03-23-17.43.34_263dd6707191_convert_quantity_from_integer_to_float.py
diff --git a/alembic/versions/2022-03-27-19.30.28_f1a2dbee5fe9_add_original_text_column_to_recipes_.py b/mealie/alembic/versions/2022-03-27-19.30.28_f1a2dbee5fe9_add_original_text_column_to_recipes_.py
similarity index 100%
rename from alembic/versions/2022-03-27-19.30.28_f1a2dbee5fe9_add_original_text_column_to_recipes_.py
rename to mealie/alembic/versions/2022-03-27-19.30.28_f1a2dbee5fe9_add_original_text_column_to_recipes_.py
diff --git a/alembic/versions/2022-03-31-19.19.55_59eb59135381_add_tags_to_cookbooks.py b/mealie/alembic/versions/2022-03-31-19.19.55_59eb59135381_add_tags_to_cookbooks.py
similarity index 100%
rename from alembic/versions/2022-03-31-19.19.55_59eb59135381_add_tags_to_cookbooks.py
rename to mealie/alembic/versions/2022-03-31-19.19.55_59eb59135381_add_tags_to_cookbooks.py
diff --git a/alembic/versions/2022-04-03-10.48.51_09dfc897ad62_add_require_all_for_cookbook_filters.py b/mealie/alembic/versions/2022-04-03-10.48.51_09dfc897ad62_add_require_all_for_cookbook_filters.py
similarity index 100%
rename from alembic/versions/2022-04-03-10.48.51_09dfc897ad62_add_require_all_for_cookbook_filters.py
rename to mealie/alembic/versions/2022-04-03-10.48.51_09dfc897ad62_add_require_all_for_cookbook_filters.py
diff --git a/alembic/versions/2022-06-01-11.12.06_ab0bae02578f_add_use_abbreviation_column_to_.py b/mealie/alembic/versions/2022-06-01-11.12.06_ab0bae02578f_add_use_abbreviation_column_to_.py
similarity index 100%
rename from alembic/versions/2022-06-01-11.12.06_ab0bae02578f_add_use_abbreviation_column_to_.py
rename to mealie/alembic/versions/2022-06-01-11.12.06_ab0bae02578f_add_use_abbreviation_column_to_.py
diff --git a/alembic/versions/2022-06-15-21.05.34_f30cf048c228_add_new_webhook_fields.py b/mealie/alembic/versions/2022-06-15-21.05.34_f30cf048c228_add_new_webhook_fields.py
similarity index 100%
rename from alembic/versions/2022-06-15-21.05.34_f30cf048c228_add_new_webhook_fields.py
rename to mealie/alembic/versions/2022-06-15-21.05.34_f30cf048c228_add_new_webhook_fields.py
diff --git a/alembic/versions/2022-08-12-19.05.59_188374910655_add_login_attemps_and_locked_at_field_.py b/mealie/alembic/versions/2022-08-12-19.05.59_188374910655_add_login_attemps_and_locked_at_field_.py
similarity index 100%
rename from alembic/versions/2022-08-12-19.05.59_188374910655_add_login_attemps_and_locked_at_field_.py
rename to mealie/alembic/versions/2022-08-12-19.05.59_188374910655_add_login_attemps_and_locked_at_field_.py
diff --git a/alembic/versions/2022-08-13-17.07.07_089bfa50d0ed_add_is_ocr_recipe_column_to_recipes.py b/mealie/alembic/versions/2022-08-13-17.07.07_089bfa50d0ed_add_is_ocr_recipe_column_to_recipes.py
similarity index 100%
rename from alembic/versions/2022-08-13-17.07.07_089bfa50d0ed_add_is_ocr_recipe_column_to_recipes.py
rename to mealie/alembic/versions/2022-08-13-17.07.07_089bfa50d0ed_add_is_ocr_recipe_column_to_recipes.py
diff --git a/alembic/versions/2022-08-29-13.57.40_44e8d670719d_add_extras_to_shopping_lists_list_items_.py b/mealie/alembic/versions/2022-08-29-13.57.40_44e8d670719d_add_extras_to_shopping_lists_list_items_.py
similarity index 100%
rename from alembic/versions/2022-08-29-13.57.40_44e8d670719d_add_extras_to_shopping_lists_list_items_.py
rename to mealie/alembic/versions/2022-08-29-13.57.40_44e8d670719d_add_extras_to_shopping_lists_list_items_.py
diff --git a/alembic/versions/2022-09-27-14.53.14_2ea7a807915c_add_recipe_timeline_events_table.py b/mealie/alembic/versions/2022-09-27-14.53.14_2ea7a807915c_add_recipe_timeline_events_table.py
similarity index 100%
rename from alembic/versions/2022-09-27-14.53.14_2ea7a807915c_add_recipe_timeline_events_table.py
rename to mealie/alembic/versions/2022-09-27-14.53.14_2ea7a807915c_add_recipe_timeline_events_table.py
diff --git a/alembic/versions/2022-11-03-13.10.24_1923519381ad_renamed_timeline_event_message_and_.py b/mealie/alembic/versions/2022-11-03-13.10.24_1923519381ad_renamed_timeline_event_message_and_.py
similarity index 100%
rename from alembic/versions/2022-11-03-13.10.24_1923519381ad_renamed_timeline_event_message_and_.py
rename to mealie/alembic/versions/2022-11-03-13.10.24_1923519381ad_renamed_timeline_event_message_and_.py
diff --git a/alembic/versions/2022-11-22-03.42.45_167eb69066ad_add_recipe_scale_to_shopping_list_item_.py b/mealie/alembic/versions/2022-11-22-03.42.45_167eb69066ad_add_recipe_scale_to_shopping_list_item_.py
similarity index 100%
rename from alembic/versions/2022-11-22-03.42.45_167eb69066ad_add_recipe_scale_to_shopping_list_item_.py
rename to mealie/alembic/versions/2022-11-22-03.42.45_167eb69066ad_add_recipe_scale_to_shopping_list_item_.py
diff --git a/alembic/versions/2023-01-21-16.54.44_165d943c64ee_add_related_user_to_mealplan.py b/mealie/alembic/versions/2023-01-21-16.54.44_165d943c64ee_add_related_user_to_mealplan.py
similarity index 100%
rename from alembic/versions/2023-01-21-16.54.44_165d943c64ee_add_related_user_to_mealplan.py
rename to mealie/alembic/versions/2023-01-21-16.54.44_165d943c64ee_add_related_user_to_mealplan.py
diff --git a/alembic/versions/2023-02-07-20.57.21_ff5f73b01a7a_add_missing_foreign_key_and_order_.py b/mealie/alembic/versions/2023-02-07-20.57.21_ff5f73b01a7a_add_missing_foreign_key_and_order_.py
similarity index 100%
rename from alembic/versions/2023-02-07-20.57.21_ff5f73b01a7a_add_missing_foreign_key_and_order_.py
rename to mealie/alembic/versions/2023-02-07-20.57.21_ff5f73b01a7a_add_missing_foreign_key_and_order_.py
diff --git a/alembic/versions/2023-02-10-21.18.32_16160bf731a0_add_more_indices_necessary_for_search.py b/mealie/alembic/versions/2023-02-10-21.18.32_16160bf731a0_add_more_indices_necessary_for_search.py
similarity index 100%
rename from alembic/versions/2023-02-10-21.18.32_16160bf731a0_add_more_indices_necessary_for_search.py
rename to mealie/alembic/versions/2023-02-10-21.18.32_16160bf731a0_add_more_indices_necessary_for_search.py
diff --git a/alembic/versions/2023-02-14-20.45.41_5ab195a474eb_add_normalized_search_properties.py b/mealie/alembic/versions/2023-02-14-20.45.41_5ab195a474eb_add_normalized_search_properties.py
similarity index 100%
rename from alembic/versions/2023-02-14-20.45.41_5ab195a474eb_add_normalized_search_properties.py
rename to mealie/alembic/versions/2023-02-14-20.45.41_5ab195a474eb_add_normalized_search_properties.py
diff --git a/alembic/versions/2023-02-21-22.03.19_b04a08da2108_added_shopping_list_label_settings.py b/mealie/alembic/versions/2023-02-21-22.03.19_b04a08da2108_added_shopping_list_label_settings.py
similarity index 100%
rename from alembic/versions/2023-02-21-22.03.19_b04a08da2108_added_shopping_list_label_settings.py
rename to mealie/alembic/versions/2023-02-21-22.03.19_b04a08da2108_added_shopping_list_label_settings.py
diff --git a/alembic/versions/2023-02-22-21.45.52_38514b39a824_add_auth_method_to_user_table.py b/mealie/alembic/versions/2023-02-22-21.45.52_38514b39a824_add_auth_method_to_user_table.py
similarity index 100%
rename from alembic/versions/2023-02-22-21.45.52_38514b39a824_add_auth_method_to_user_table.py
rename to mealie/alembic/versions/2023-02-22-21.45.52_38514b39a824_add_auth_method_to_user_table.py
diff --git a/alembic/versions/2023-04-13-06.47.04_b3dbb554ba53_postgres_fuzzy_search.py b/mealie/alembic/versions/2023-04-13-06.47.04_b3dbb554ba53_postgres_fuzzy_search.py
similarity index 100%
rename from alembic/versions/2023-04-13-06.47.04_b3dbb554ba53_postgres_fuzzy_search.py
rename to mealie/alembic/versions/2023-04-13-06.47.04_b3dbb554ba53_postgres_fuzzy_search.py
diff --git a/alembic/versions/2023-08-06-21.00.34_04ac51cbe9a4_added_group_slug.py b/mealie/alembic/versions/2023-08-06-21.00.34_04ac51cbe9a4_added_group_slug.py
similarity index 100%
rename from alembic/versions/2023-08-06-21.00.34_04ac51cbe9a4_added_group_slug.py
rename to mealie/alembic/versions/2023-08-06-21.00.34_04ac51cbe9a4_added_group_slug.py
diff --git a/alembic/versions/2023-08-14-19.30.49_1825b5225403_added_recipe_note_to_shopping_list_.py b/mealie/alembic/versions/2023-08-14-19.30.49_1825b5225403_added_recipe_note_to_shopping_list_.py
similarity index 100%
rename from alembic/versions/2023-08-14-19.30.49_1825b5225403_added_recipe_note_to_shopping_list_.py
rename to mealie/alembic/versions/2023-08-14-19.30.49_1825b5225403_added_recipe_note_to_shopping_list_.py
diff --git a/alembic/versions/2023-08-15-16.25.07_bcfdad6b7355_remove_tool_name_and_slug_unique_.py b/mealie/alembic/versions/2023-08-15-16.25.07_bcfdad6b7355_remove_tool_name_and_slug_unique_.py
similarity index 100%
rename from alembic/versions/2023-08-15-16.25.07_bcfdad6b7355_remove_tool_name_and_slug_unique_.py
rename to mealie/alembic/versions/2023-08-15-16.25.07_bcfdad6b7355_remove_tool_name_and_slug_unique_.py
diff --git a/alembic/versions/2023-09-01-14.55.42_0341b154f79a_added_normalized_unit_and_food_names.py b/mealie/alembic/versions/2023-09-01-14.55.42_0341b154f79a_added_normalized_unit_and_food_names.py
similarity index 100%
rename from alembic/versions/2023-09-01-14.55.42_0341b154f79a_added_normalized_unit_and_food_names.py
rename to mealie/alembic/versions/2023-09-01-14.55.42_0341b154f79a_added_normalized_unit_and_food_names.py
diff --git a/alembic/versions/2023-10-04-14.29.26_dded3119c1fe_added_unique_constraints.py b/mealie/alembic/versions/2023-10-04-14.29.26_dded3119c1fe_added_unique_constraints.py
similarity index 100%
rename from alembic/versions/2023-10-04-14.29.26_dded3119c1fe_added_unique_constraints.py
rename to mealie/alembic/versions/2023-10-04-14.29.26_dded3119c1fe_added_unique_constraints.py
diff --git a/alembic/versions/2023-10-19-19.22.55_ba1e4a6cfe99_added_plural_names_and_alias_tables_for_.py b/mealie/alembic/versions/2023-10-19-19.22.55_ba1e4a6cfe99_added_plural_names_and_alias_tables_for_.py
similarity index 100%
rename from alembic/versions/2023-10-19-19.22.55_ba1e4a6cfe99_added_plural_names_and_alias_tables_for_.py
rename to mealie/alembic/versions/2023-10-19-19.22.55_ba1e4a6cfe99_added_plural_names_and_alias_tables_for_.py
diff --git a/alembic/versions/2024-02-23-16.15.07_2298bb460ffd_added_user_to_shopping_list.py b/mealie/alembic/versions/2024-02-23-16.15.07_2298bb460ffd_added_user_to_shopping_list.py
similarity index 100%
rename from alembic/versions/2024-02-23-16.15.07_2298bb460ffd_added_user_to_shopping_list.py
rename to mealie/alembic/versions/2024-02-23-16.15.07_2298bb460ffd_added_user_to_shopping_list.py
index d9afe0bed..311e376db 100644
--- a/alembic/versions/2024-02-23-16.15.07_2298bb460ffd_added_user_to_shopping_list.py
+++ b/mealie/alembic/versions/2024-02-23-16.15.07_2298bb460ffd_added_user_to_shopping_list.py
@@ -9,10 +9,10 @@ Create Date: 2024-02-23 16:15:07.115641
from uuid import UUID
import sqlalchemy as sa
+from alembic import op
from sqlalchemy import orm
import mealie.db.migration_types
-from alembic import op
from mealie.core.root_logger import get_logger
logger = get_logger()
diff --git a/alembic/versions/2024-03-10-05.08.32_09aba125b57a_add_oidc_auth_method.py b/mealie/alembic/versions/2024-03-10-05.08.32_09aba125b57a_add_oidc_auth_method.py
similarity index 100%
rename from alembic/versions/2024-03-10-05.08.32_09aba125b57a_add_oidc_auth_method.py
rename to mealie/alembic/versions/2024-03-10-05.08.32_09aba125b57a_add_oidc_auth_method.py
diff --git a/alembic/versions/2024-03-18-02.28.15_d7c6efd2de42_migrate_favorites_and_ratings_to_user_.py b/mealie/alembic/versions/2024-03-18-02.28.15_d7c6efd2de42_migrate_favorites_and_ratings_to_user_.py
similarity index 100%
rename from alembic/versions/2024-03-18-02.28.15_d7c6efd2de42_migrate_favorites_and_ratings_to_user_.py
rename to mealie/alembic/versions/2024-03-18-02.28.15_d7c6efd2de42_migrate_favorites_and_ratings_to_user_.py
index 357f660f5..8873532bc 100644
--- a/alembic/versions/2024-03-18-02.28.15_d7c6efd2de42_migrate_favorites_and_ratings_to_user_.py
+++ b/mealie/alembic/versions/2024-03-18-02.28.15_d7c6efd2de42_migrate_favorites_and_ratings_to_user_.py
@@ -12,10 +12,10 @@ from typing import Any
from uuid import uuid4
import sqlalchemy as sa
+from alembic import op
from sqlalchemy import orm
import mealie.db.migration_types
-from alembic import op
# revision identifiers, used by Alembic.
revision = "d7c6efd2de42"
diff --git a/alembic/versions/2024-04-07-01.05.20_7788478a0338_add_group_recipe_actions.py b/mealie/alembic/versions/2024-04-07-01.05.20_7788478a0338_add_group_recipe_actions.py
similarity index 100%
rename from alembic/versions/2024-04-07-01.05.20_7788478a0338_add_group_recipe_actions.py
rename to mealie/alembic/versions/2024-04-07-01.05.20_7788478a0338_add_group_recipe_actions.py
index 39810210b..ad390b172 100644
--- a/alembic/versions/2024-04-07-01.05.20_7788478a0338_add_group_recipe_actions.py
+++ b/mealie/alembic/versions/2024-04-07-01.05.20_7788478a0338_add_group_recipe_actions.py
@@ -7,9 +7,9 @@ Create Date: 2024-04-07 01:05:20.816270
"""
import sqlalchemy as sa
+from alembic import op
import mealie.db.migration_types
-from alembic import op
# revision identifiers, used by Alembic.
revision = "7788478a0338"
diff --git a/alembic/versions/2024-06-22-10.17.03_32d69327997b_add_staple_flag_to_foods.py b/mealie/alembic/versions/2024-06-22-10.17.03_32d69327997b_add_staple_flag_to_foods.py
similarity index 99%
rename from alembic/versions/2024-06-22-10.17.03_32d69327997b_add_staple_flag_to_foods.py
rename to mealie/alembic/versions/2024-06-22-10.17.03_32d69327997b_add_staple_flag_to_foods.py
index bd8cf64f5..8537a8c0d 100644
--- a/alembic/versions/2024-06-22-10.17.03_32d69327997b_add_staple_flag_to_foods.py
+++ b/mealie/alembic/versions/2024-06-22-10.17.03_32d69327997b_add_staple_flag_to_foods.py
@@ -7,9 +7,8 @@ Create Date: 2024-06-22 10:17:03.323966
"""
import sqlalchemy as sa
-from sqlalchemy import orm
-
from alembic import op
+from sqlalchemy import orm
# revision identifiers, used by Alembic.
revision = "32d69327997b"
diff --git a/alembic/versions/2024-07-12-16.16.29_feecc8ffb956_add_households.py b/mealie/alembic/versions/2024-07-12-16.16.29_feecc8ffb956_add_households.py
similarity index 100%
rename from alembic/versions/2024-07-12-16.16.29_feecc8ffb956_add_households.py
rename to mealie/alembic/versions/2024-07-12-16.16.29_feecc8ffb956_add_households.py
index 2ad26f093..0578a7643 100644
--- a/alembic/versions/2024-07-12-16.16.29_feecc8ffb956_add_households.py
+++ b/mealie/alembic/versions/2024-07-12-16.16.29_feecc8ffb956_add_households.py
@@ -12,11 +12,11 @@ from typing import Any
from uuid import uuid4
import sqlalchemy as sa
+from alembic import op
from slugify import slugify
from sqlalchemy import orm
import mealie.db.migration_types
-from alembic import op
from mealie.core.config import get_app_settings
# revision identifiers, used by Alembic.
diff --git a/alembic/versions/2024-09-02-21.39.49_be568e39ffdf_added_household_recipe_lock_setting_and_.py b/mealie/alembic/versions/2024-09-02-21.39.49_be568e39ffdf_added_household_recipe_lock_setting_and_.py
similarity index 99%
rename from alembic/versions/2024-09-02-21.39.49_be568e39ffdf_added_household_recipe_lock_setting_and_.py
rename to mealie/alembic/versions/2024-09-02-21.39.49_be568e39ffdf_added_household_recipe_lock_setting_and_.py
index 83f2e5518..be8f54ce6 100644
--- a/alembic/versions/2024-09-02-21.39.49_be568e39ffdf_added_household_recipe_lock_setting_and_.py
+++ b/mealie/alembic/versions/2024-09-02-21.39.49_be568e39ffdf_added_household_recipe_lock_setting_and_.py
@@ -9,7 +9,6 @@ Create Date: 2024-09-02 21:39:49.210355
from textwrap import dedent
import sqlalchemy as sa
-
from alembic import op
# revision identifiers, used by Alembic.
diff --git a/alembic/versions/2024-09-18-14.52.55_1fe4bd37ccc8_add_households_filter_to_meal_plans.py b/mealie/alembic/versions/2024-09-18-14.52.55_1fe4bd37ccc8_add_households_filter_to_meal_plans.py
similarity index 100%
rename from alembic/versions/2024-09-18-14.52.55_1fe4bd37ccc8_add_households_filter_to_meal_plans.py
rename to mealie/alembic/versions/2024-09-18-14.52.55_1fe4bd37ccc8_add_households_filter_to_meal_plans.py
index a127e72f4..6460aa5bb 100644
--- a/alembic/versions/2024-09-18-14.52.55_1fe4bd37ccc8_add_households_filter_to_meal_plans.py
+++ b/mealie/alembic/versions/2024-09-18-14.52.55_1fe4bd37ccc8_add_households_filter_to_meal_plans.py
@@ -7,9 +7,9 @@ Create Date: 2024-09-18 14:52:55.831540
"""
import sqlalchemy as sa
+from alembic import op
import mealie.db.migration_types
-from alembic import op
# revision identifiers, used by Alembic.
revision = "1fe4bd37ccc8"
diff --git a/alembic/versions/2024-10-01-14.17.00_602927e1013e_add_the_rest_of_the_schema_org_.py b/mealie/alembic/versions/2024-10-01-14.17.00_602927e1013e_add_the_rest_of_the_schema_org_.py
similarity index 99%
rename from alembic/versions/2024-10-01-14.17.00_602927e1013e_add_the_rest_of_the_schema_org_.py
rename to mealie/alembic/versions/2024-10-01-14.17.00_602927e1013e_add_the_rest_of_the_schema_org_.py
index dbeea910f..cca6ec7d9 100644
--- a/alembic/versions/2024-10-01-14.17.00_602927e1013e_add_the_rest_of_the_schema_org_.py
+++ b/mealie/alembic/versions/2024-10-01-14.17.00_602927e1013e_add_the_rest_of_the_schema_org_.py
@@ -7,7 +7,6 @@ Create Date: 2024-10-01 14:17:00.611398
"""
import sqlalchemy as sa
-
from alembic import op
# revision identifiers, used by Alembic.
diff --git a/alembic/versions/2024-10-08-21.17.31_86054b40fd06_added_query_filter_string_to_cookbook_.py b/mealie/alembic/versions/2024-10-08-21.17.31_86054b40fd06_added_query_filter_string_to_cookbook_.py
similarity index 100%
rename from alembic/versions/2024-10-08-21.17.31_86054b40fd06_added_query_filter_string_to_cookbook_.py
rename to mealie/alembic/versions/2024-10-08-21.17.31_86054b40fd06_added_query_filter_string_to_cookbook_.py
index 3f5eb908c..6737cdf59 100644
--- a/alembic/versions/2024-10-08-21.17.31_86054b40fd06_added_query_filter_string_to_cookbook_.py
+++ b/mealie/alembic/versions/2024-10-08-21.17.31_86054b40fd06_added_query_filter_string_to_cookbook_.py
@@ -7,9 +7,9 @@ Create Date: 2024-10-08 21:17:31.601903
"""
import sqlalchemy as sa
+from alembic import op
from sqlalchemy import orm
-from alembic import op
from mealie.db.models._model_utils import guid
# revision identifiers, used by Alembic.
diff --git a/alembic/versions/2024-10-20-09.47.46_3897397b4631_add_summary_to_recipe_instructions.py b/mealie/alembic/versions/2024-10-20-09.47.46_3897397b4631_add_summary_to_recipe_instructions.py
similarity index 99%
rename from alembic/versions/2024-10-20-09.47.46_3897397b4631_add_summary_to_recipe_instructions.py
rename to mealie/alembic/versions/2024-10-20-09.47.46_3897397b4631_add_summary_to_recipe_instructions.py
index 1175ca6ef..eba3b1b1a 100644
--- a/alembic/versions/2024-10-20-09.47.46_3897397b4631_add_summary_to_recipe_instructions.py
+++ b/mealie/alembic/versions/2024-10-20-09.47.46_3897397b4631_add_summary_to_recipe_instructions.py
@@ -7,7 +7,6 @@ Create Date: 2024-10-20 09:47:46.844436
"""
import sqlalchemy as sa
-
from alembic import op
# revision identifiers, used by Alembic.
diff --git a/alembic/versions/2024-10-23-15.50.59_b1020f328e98_add_recipe_yield_quantity.py b/mealie/alembic/versions/2024-10-23-15.50.59_b1020f328e98_add_recipe_yield_quantity.py
similarity index 100%
rename from alembic/versions/2024-10-23-15.50.59_b1020f328e98_add_recipe_yield_quantity.py
rename to mealie/alembic/versions/2024-10-23-15.50.59_b1020f328e98_add_recipe_yield_quantity.py
index 4aee9ab79..ba9b21db2 100644
--- a/alembic/versions/2024-10-23-15.50.59_b1020f328e98_add_recipe_yield_quantity.py
+++ b/mealie/alembic/versions/2024-10-23-15.50.59_b1020f328e98_add_recipe_yield_quantity.py
@@ -7,9 +7,9 @@ Create Date: 2024-10-23 15:50:59.888793
"""
import sqlalchemy as sa
+from alembic import op
from sqlalchemy import orm
-from alembic import op
from mealie.db.models._model_utils.guid import GUID
from mealie.services.scraper.cleaner import clean_yield
diff --git a/mealie/db/init_db.py b/mealie/db/init_db.py
index 9dc3b1398..75950a5a2 100644
--- a/mealie/db/init_db.py
+++ b/mealie/db/init_db.py
@@ -3,11 +3,11 @@ from collections.abc import Callable
from pathlib import Path
from time import sleep
-from sqlalchemy import engine, orm, text
-
from alembic import command, config, script
from alembic.config import Config
from alembic.runtime import migration
+from sqlalchemy import engine, orm, text
+
from mealie.core import root_logger
from mealie.core.config import get_app_settings
from mealie.db.db_setup import session_context
@@ -22,7 +22,7 @@ from mealie.schema.user.user import GroupBase, GroupInDB
from mealie.services.group_services.group_service import GroupService
from mealie.services.household_services.household_service import HouseholdService
-PROJECT_DIR = Path(__file__).parent.parent.parent
+ALEMBIC_DIR = Path(__file__).parent.parent / "alembic"
logger = root_logger.get_logger()
@@ -101,7 +101,7 @@ def main():
if max_retry == 0:
raise ConnectionError("Database connection failed - exiting application.")
- alembic_cfg_path = os.getenv("ALEMBIC_CONFIG_FILE", default=str(PROJECT_DIR / "alembic.ini"))
+ alembic_cfg_path = os.getenv("ALEMBIC_CONFIG_FILE", default=str(ALEMBIC_DIR / "alembic.ini"))
if not os.path.isfile(alembic_cfg_path):
raise Exception("Provided alembic config path doesn't exist")
diff --git a/mealie/services/backups_v2/alchemy_exporter.py b/mealie/services/backups_v2/alchemy_exporter.py
index 95dc1d37a..24fd37544 100644
--- a/mealie/services/backups_v2/alchemy_exporter.py
+++ b/mealie/services/backups_v2/alchemy_exporter.py
@@ -3,25 +3,23 @@ import os
import uuid
from logging import Logger
from os import path
-from pathlib import Path
from textwrap import dedent
from typing import Any
+from alembic import command
+from alembic.config import Config
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel
from sqlalchemy import Connection, ForeignKey, ForeignKeyConstraint, MetaData, Table, create_engine, insert, text
from sqlalchemy.engine import base
from sqlalchemy.orm import sessionmaker
-from alembic import command
-from alembic.config import Config
from mealie.db import init_db
from mealie.db.fixes.fix_migration_data import fix_migration_data
+from mealie.db.init_db import ALEMBIC_DIR
from mealie.db.models._model_utils.guid import GUID
from mealie.services._base_service import BaseService
-PROJECT_DIR = Path(__file__).parent.parent.parent.parent
-
class ForeignKeyDisabler:
def __init__(self, connection: Connection, dialect_name: str, *, logger: Logger | None = None):
@@ -193,15 +191,12 @@ class AlchemyExporter(BaseService):
alembic_data = db_dump["alembic_version"]
alembic_version = alembic_data[0]["version_num"]
- alembic_cfg_path = os.getenv("ALEMBIC_CONFIG_FILE", default=str(PROJECT_DIR / "alembic.ini"))
+ alembic_cfg_path = os.getenv("ALEMBIC_CONFIG_FILE", default=str(ALEMBIC_DIR / "alembic.ini"))
if not path.isfile(alembic_cfg_path):
raise Exception("Provided alembic config path doesn't exist")
alembic_cfg = Config(alembic_cfg_path)
- # alembic's file resolver wants to use the "mealie" subdirectory when called from within the server package
- # Just override this to use the correct migrations path
- alembic_cfg.set_main_option("script_location", path.join(PROJECT_DIR, "alembic"))
command.upgrade(alembic_cfg, alembic_version)
del db_dump["alembic_version"]
diff --git a/pyproject.toml b/pyproject.toml
index 73c86eca9..0171889c9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -158,8 +158,8 @@ select = [
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["E402", "E501"]
-"alembic/versions/2022*" = ["E501"]
-"alembic/versions/2023*" = ["E501"]
+"mealie/alembic/versions/2022*" = ["E501", "I001"]
+"mealie/alembic/versions/2023*" = ["E501", "I001"]
"dev/scripts/all_recipes_stress_test.py" = ["E501"]
"ldap_provider.py" = ["UP032"]
"tests/conftest.py" = ["E402"]
diff --git a/tests/utils/alembic_reader.py b/tests/utils/alembic_reader.py
index 2bba446f4..e8fda4494 100644
--- a/tests/utils/alembic_reader.py
+++ b/tests/utils/alembic_reader.py
@@ -2,9 +2,9 @@ import importlib.util
import pathlib
from functools import lru_cache
-from mealie.db.init_db import PROJECT_DIR
+from mealie.db.init_db import ALEMBIC_DIR
-ALEMBIC_MIGRATIONS = PROJECT_DIR / "alembic" / "versions"
+ALEMBIC_MIGRATIONS = ALEMBIC_DIR / "versions"
def import_file(module_name: str, file_path: pathlib.Path):
From 0e6a40e210d3adb0c1194c977718bab17f85ce48 Mon Sep 17 00:00:00 2001
From: Hayden <64056131+hay-kot@users.noreply.github.com>
Date: Wed, 4 Dec 2024 14:41:02 -0600
Subject: [PATCH 070/238] fix: remove import button from recipe data view
(#4680)
---
frontend/pages/group/data/recipes.vue | 6 ------
1 file changed, 6 deletions(-)
diff --git a/frontend/pages/group/data/recipes.vue b/frontend/pages/group/data/recipes.vue
index 37bc47760..112b8795d 100644
--- a/frontend/pages/group/data/recipes.vue
+++ b/frontend/pages/group/data/recipes.vue
@@ -136,12 +136,6 @@
-
-
- {{ $globals.icons.database }}
-
- {{ $t('general.import') }}
-
Date: Wed, 4 Dec 2024 22:31:26 -0600
Subject: [PATCH 071/238] feat: Upgrade to Python 3.12 (#4675)
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
---
.devcontainer/Dockerfile | 4 +-
.devcontainer/devcontainer.json | 2 +-
.github/workflows/partial-backend.yml | 2 +-
.github/workflows/scheduled-checks.yml | 2 +-
docker/Dockerfile | 2 +-
.../developers-guide/starting-dev-server.md | 2 +-
..._migrate_favorites_and_ratings_to_user_.py | 4 +-
...12-16.16.29_feecc8ffb956_add_households.py | 4 +-
mealie/core/release_checker.py | 4 +-
.../core/security/providers/auth_provider.py | 4 +-
mealie/core/security/security.py | 4 +-
mealie/core/settings/settings.py | 4 +-
mealie/db/models/_model_utils/datetime.py | 10 +-
mealie/db/models/household/shopping_list.py | 4 +-
mealie/db/models/household/webhooks.py | 4 +-
mealie/db/models/recipe/recipe.py | 4 +-
mealie/db/models/recipe/recipe_timeline.py | 4 +-
mealie/db/models/recipe/shared.py | 4 +-
mealie/repos/repository_generic.py | 4 +-
mealie/repos/repository_meals.py | 4 +-
mealie/repos/repository_recipes.py | 3 +-
.../routes/households/controller_webhooks.py | 4 +-
mealie/schema/_mealie/datetime_parse.py | 8 +-
mealie/schema/_mealie/mealie_model.py | 7 +-
mealie/schema/household/webhook.py | 2 +-
mealie/schema/recipe/recipe_share_token.py | 4 +-
.../schema/recipe/recipe_timeline_events.py | 4 +-
mealie/schema/user/user.py | 4 +-
mealie/services/backups_v2/backup_v2.py | 4 +-
.../event_bus_service/event_bus_listeners.py | 6 +-
.../services/event_bus_service/event_types.py | 4 +-
mealie/services/exporter/exporter.py | 2 +-
mealie/services/migrations/copymethat.py | 4 +-
mealie/services/recipe/recipe_service.py | 4 +-
.../services/scheduler/scheduler_service.py | 4 +-
.../scheduler/tasks/create_timeline_events.py | 6 +-
.../services/scheduler/tasks/post_webhooks.py | 8 +-
.../scheduler/tasks/purge_group_exports.py | 4 +-
.../scheduler/tasks/purge_password_reset.py | 2 +-
.../scheduler/tasks/purge_registration.py | 2 +-
mealie/services/user_services/user_service.py | 4 +-
poetry.lock | 117 +++++++-----------
pyproject.toml | 8 +-
43 files changed, 128 insertions(+), 163 deletions(-)
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index cbac5071b..07fd5786e 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -1,8 +1,8 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.224.2/containers/python-3/.devcontainer/base.Dockerfile
# [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster
-ARG VARIANT="3.10-bullseye"
-FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}
+ARG VARIANT="3.12-bullseye"
+FROM mcr.microsoft.com/devcontainers/python:${VARIANT}
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
ARG NODE_VERSION="none"
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index fed52b188..31d8f3427 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -9,7 +9,7 @@
// Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local on arm64/Apple Silicon.
- "VARIANT": "3.10-bullseye",
+ "VARIANT": "3.12-bullseye",
// Options
"NODE_VERSION": "16"
}
diff --git a/.github/workflows/partial-backend.yml b/.github/workflows/partial-backend.yml
index fae5dfd8b..b0772d181 100644
--- a/.github/workflows/partial-backend.yml
+++ b/.github/workflows/partial-backend.yml
@@ -47,7 +47,7 @@ jobs:
- name: Set up python
uses: actions/setup-python@v5
with:
- python-version: "3.10"
+ python-version: "3.12"
- name: Install Poetry
uses: snok/install-poetry@v1
diff --git a/.github/workflows/scheduled-checks.yml b/.github/workflows/scheduled-checks.yml
index 0134d60cd..337907418 100644
--- a/.github/workflows/scheduled-checks.yml
+++ b/.github/workflows/scheduled-checks.yml
@@ -18,7 +18,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v5
with:
- python-version: "3.10"
+ python-version: "3.12"
- name: Set PY
shell: bash
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 056825c41..bdee7416e 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -17,7 +17,7 @@ RUN yarn generate
###############################################
# Base Image - Python
###############################################
-FROM python:3.10-slim as python-base
+FROM python:3.12-slim as python-base
ENV MEALIE_HOME="/app"
diff --git a/docs/docs/contributors/developers-guide/starting-dev-server.md b/docs/docs/contributors/developers-guide/starting-dev-server.md
index d17772473..4edac0b18 100644
--- a/docs/docs/contributors/developers-guide/starting-dev-server.md
+++ b/docs/docs/contributors/developers-guide/starting-dev-server.md
@@ -32,7 +32,7 @@ Make sure the VSCode Dev Containers extension is installed, then select "Dev Con
### Prerequisites
-- [Python 3.10](https://www.python.org/downloads/)
+- [Python 3.12](https://www.python.org/downloads/)
- [Poetry](https://python-poetry.org/docs/#installation)
- [Node v16.x](https://nodejs.org/en/)
- [yarn](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable)
diff --git a/mealie/alembic/versions/2024-03-18-02.28.15_d7c6efd2de42_migrate_favorites_and_ratings_to_user_.py b/mealie/alembic/versions/2024-03-18-02.28.15_d7c6efd2de42_migrate_favorites_and_ratings_to_user_.py
index 8873532bc..6aa1c371f 100644
--- a/mealie/alembic/versions/2024-03-18-02.28.15_d7c6efd2de42_migrate_favorites_and_ratings_to_user_.py
+++ b/mealie/alembic/versions/2024-03-18-02.28.15_d7c6efd2de42_migrate_favorites_and_ratings_to_user_.py
@@ -6,7 +6,7 @@ Create Date: 2024-03-18 02:28:15.896959
"""
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from textwrap import dedent
from typing import Any
from uuid import uuid4
@@ -34,7 +34,7 @@ def new_user_rating(user_id: Any, recipe_id: Any, rating: float | None = None, i
else:
id = "%.32x" % uuid4().int # noqa: UP031
- now = datetime.now(timezone.utc).isoformat()
+ now = datetime.now(UTC).isoformat()
return {
"id": id,
"user_id": user_id,
diff --git a/mealie/alembic/versions/2024-07-12-16.16.29_feecc8ffb956_add_households.py b/mealie/alembic/versions/2024-07-12-16.16.29_feecc8ffb956_add_households.py
index 0578a7643..7c4b2a2fc 100644
--- a/mealie/alembic/versions/2024-07-12-16.16.29_feecc8ffb956_add_households.py
+++ b/mealie/alembic/versions/2024-07-12-16.16.29_feecc8ffb956_add_households.py
@@ -6,7 +6,7 @@ Create Date: 2024-07-12 16:16:29.973929
"""
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from textwrap import dedent
from typing import Any
from uuid import uuid4
@@ -89,7 +89,7 @@ def dedupe_cookbook_slugs():
def create_household(session: orm.Session, group_id: str) -> str:
# create/insert household
household_id = generate_id()
- timestamp = datetime.now(timezone.utc).isoformat()
+ timestamp = datetime.now(UTC).isoformat()
household_data = {
"id": household_id,
"name": settings.DEFAULT_HOUSEHOLD,
diff --git a/mealie/core/release_checker.py b/mealie/core/release_checker.py
index 53afa3fba..b5d2bf9e2 100644
--- a/mealie/core/release_checker.py
+++ b/mealie/core/release_checker.py
@@ -3,7 +3,7 @@ from functools import lru_cache
import requests
-_LAST_RESET = None
+_LAST_RESET: datetime.datetime | None = None
@lru_cache(maxsize=1)
@@ -32,7 +32,7 @@ def get_latest_version() -> str:
global _LAST_RESET
- now = datetime.datetime.now(datetime.timezone.utc)
+ now = datetime.datetime.now(datetime.UTC)
if not _LAST_RESET or now - _LAST_RESET > datetime.timedelta(days=MAX_DAYS_OLD):
_LAST_RESET = now
diff --git a/mealie/core/security/providers/auth_provider.py b/mealie/core/security/providers/auth_provider.py
index 4c98879eb..ce10afd7d 100644
--- a/mealie/core/security/providers/auth_provider.py
+++ b/mealie/core/security/providers/auth_provider.py
@@ -1,5 +1,5 @@
import abc
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
from typing import Generic, TypeVar
import jwt
@@ -45,7 +45,7 @@ class AuthProvider(Generic[T], metaclass=abc.ABCMeta):
to_encode = data.copy()
expires_delta = expires_delta or timedelta(hours=settings.TOKEN_TIME)
- expire = datetime.now(timezone.utc) + expires_delta
+ expire = datetime.now(UTC) + expires_delta
to_encode["exp"] = expire
to_encode["iss"] = ISS
diff --git a/mealie/core/security/security.py b/mealie/core/security/security.py
index 34893cc12..bdc364e55 100644
--- a/mealie/core/security/security.py
+++ b/mealie/core/security/security.py
@@ -1,5 +1,5 @@
import secrets
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
from pathlib import Path
import jwt
@@ -34,7 +34,7 @@ def create_access_token(data: dict, expires_delta: timedelta | None = None) -> s
to_encode = data.copy()
expires_delta = expires_delta or timedelta(hours=settings.TOKEN_TIME)
- expire = datetime.now(timezone.utc) + expires_delta
+ expire = datetime.now(UTC) + expires_delta
to_encode["exp"] = expire
return jwt.encode(to_encode, settings.SECRET, algorithm=ALGORITHM)
diff --git a/mealie/core/settings/settings.py b/mealie/core/settings/settings.py
index a0fb6e800..7558a29c5 100644
--- a/mealie/core/settings/settings.py
+++ b/mealie/core/settings/settings.py
@@ -1,7 +1,7 @@
import logging
import os
import secrets
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from pathlib import Path
from typing import Annotated, Any, NamedTuple
@@ -160,7 +160,7 @@ class AppSettings(AppLoggingSettings):
local_tz = tzlocal()
now = datetime.now(local_tz)
local_time = now.replace(hour=local_hour, minute=local_minute)
- utc_time = local_time.astimezone(timezone.utc)
+ utc_time = local_time.astimezone(UTC)
self.logger.debug(f"Local time: {local_hour}:{local_minute} | UTC time: {utc_time.hour}:{utc_time.minute}")
return ScheduleTime(utc_time.hour, utc_time.minute)
diff --git a/mealie/db/models/_model_utils/datetime.py b/mealie/db/models/_model_utils/datetime.py
index 5c24d1d52..69222f838 100644
--- a/mealie/db/models/_model_utils/datetime.py
+++ b/mealie/db/models/_model_utils/datetime.py
@@ -1,4 +1,4 @@
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from sqlalchemy.types import DateTime, TypeDecorator
@@ -7,14 +7,14 @@ def get_utc_now():
"""
Returns the current time in UTC.
"""
- return datetime.now(timezone.utc)
+ return datetime.now(UTC)
def get_utc_today():
"""
Returns the current date in UTC.
"""
- return datetime.now(timezone.utc).date()
+ return datetime.now(UTC).date()
class NaiveDateTime(TypeDecorator):
@@ -35,7 +35,7 @@ class NaiveDateTime(TypeDecorator):
try:
if value.tzinfo is not None:
- value = value.astimezone(timezone.utc)
+ value = value.astimezone(UTC)
return value.replace(tzinfo=None)
except Exception:
return value
@@ -43,7 +43,7 @@ class NaiveDateTime(TypeDecorator):
def process_result_value(self, value: datetime | None, dialect):
try:
if value is not None:
- value = value.replace(tzinfo=timezone.utc)
+ value = value.replace(tzinfo=UTC)
except Exception:
pass
diff --git a/mealie/db/models/household/shopping_list.py b/mealie/db/models/household/shopping_list.py
index 02f60a56a..f07610742 100644
--- a/mealie/db/models/household/shopping_list.py
+++ b/mealie/db/models/household/shopping_list.py
@@ -1,5 +1,5 @@
from contextvars import ContextVar
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from typing import TYPE_CHECKING, Optional
from pydantic import ConfigDict
@@ -227,7 +227,7 @@ def update_shopping_lists(session: orm.Session, _):
if not shopping_list:
continue
- shopping_list.updated_at = datetime.now(timezone.utc)
+ shopping_list.updated_at = datetime.now(UTC)
local_session.commit()
except Exception:
local_session.rollback()
diff --git a/mealie/db/models/household/webhooks.py b/mealie/db/models/household/webhooks.py
index 5b52f8ec6..9c8c6e58f 100644
--- a/mealie/db/models/household/webhooks.py
+++ b/mealie/db/models/household/webhooks.py
@@ -1,4 +1,4 @@
-from datetime import datetime, time, timezone
+from datetime import UTC, datetime, time
from typing import TYPE_CHECKING, Optional
from sqlalchemy import Boolean, ForeignKey, String, Time, orm
@@ -30,7 +30,7 @@ class GroupWebhooksModel(SqlAlchemyBase, BaseMixins):
# New Fields
webhook_type: Mapped[str | None] = mapped_column(String, default="") # Future use for different types of webhooks
- scheduled_time: Mapped[time | None] = mapped_column(Time, default=lambda: datetime.now(timezone.utc).time())
+ scheduled_time: Mapped[time | None] = mapped_column(Time, default=lambda: datetime.now(UTC).time())
# Column is no longer used but is kept for since it's super annoying to
# delete a column in SQLite and it's not a big deal to keep it around
diff --git a/mealie/db/models/recipe/recipe.py b/mealie/db/models/recipe/recipe.py
index ebbb0bfc9..60dc8c86f 100644
--- a/mealie/db/models/recipe/recipe.py
+++ b/mealie/db/models/recipe/recipe.py
@@ -1,4 +1,4 @@
-from datetime import date, datetime, timezone
+from datetime import UTC, date, datetime
from typing import TYPE_CHECKING
import sqlalchemy as sa
@@ -207,7 +207,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
if notes:
self.notes = [Note(**n) for n in notes]
- self.date_updated = datetime.now(timezone.utc)
+ self.date_updated = datetime.now(UTC)
# SQLAlchemy events do not seem to register things that are set during auto_init
if name is not None:
diff --git a/mealie/db/models/recipe/recipe_timeline.py b/mealie/db/models/recipe/recipe_timeline.py
index 402606e50..57fdc9f22 100644
--- a/mealie/db/models/recipe/recipe_timeline.py
+++ b/mealie/db/models/recipe/recipe_timeline.py
@@ -1,4 +1,4 @@
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from typing import TYPE_CHECKING
from sqlalchemy import ForeignKey, String
@@ -48,4 +48,4 @@ class RecipeTimelineEvent(SqlAlchemyBase, BaseMixins):
timestamp=None,
**_,
) -> None:
- self.timestamp = timestamp or datetime.now(timezone.utc)
+ self.timestamp = timestamp or datetime.now(UTC)
diff --git a/mealie/db/models/recipe/shared.py b/mealie/db/models/recipe/shared.py
index 9d90bcc17..7f2634a57 100644
--- a/mealie/db/models/recipe/shared.py
+++ b/mealie/db/models/recipe/shared.py
@@ -1,4 +1,4 @@
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
from typing import TYPE_CHECKING
from uuid import uuid4
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
def defaut_expires_at_time() -> datetime:
- return datetime.now(timezone.utc) + timedelta(days=30)
+ return datetime.now(UTC) + timedelta(days=30)
class RecipeShareTokenModel(SqlAlchemyBase, BaseMixins):
diff --git a/mealie/repos/repository_generic.py b/mealie/repos/repository_generic.py
index 87d406d3b..50dfa801c 100644
--- a/mealie/repos/repository_generic.py
+++ b/mealie/repos/repository_generic.py
@@ -2,7 +2,7 @@ from __future__ import annotations
import random
from collections.abc import Iterable
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from math import ceil
from typing import Any, Generic, TypeVar
@@ -70,7 +70,7 @@ class RepositoryGeneric(Generic[Schema, Model]):
return self._household_id
def _random_seed(self) -> str:
- return str(datetime.now(tz=timezone.utc))
+ return str(datetime.now(tz=UTC))
def _log_exception(self, e: Exception) -> None:
self.logger.error(f"Error processing query for Repo model={self.model.__name__} schema={self.schema.__name__}")
diff --git a/mealie/repos/repository_meals.py b/mealie/repos/repository_meals.py
index 34187e35f..ec6a35076 100644
--- a/mealie/repos/repository_meals.py
+++ b/mealie/repos/repository_meals.py
@@ -1,4 +1,4 @@
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from sqlalchemy import select
@@ -13,7 +13,7 @@ class RepositoryMeals(HouseholdRepositoryGeneric[ReadPlanEntry, GroupMealPlan]):
if not self.household_id:
raise Exception("household_id not set")
- today = datetime.now(tz=timezone.utc).date()
+ today = datetime.now(tz=UTC).date()
stmt = select(GroupMealPlan).filter(
GroupMealPlan.date == today, GroupMealPlan.household_id == self.household_id
)
diff --git a/mealie/repos/repository_recipes.py b/mealie/repos/repository_recipes.py
index 4b907ee3f..099790250 100644
--- a/mealie/repos/repository_recipes.py
+++ b/mealie/repos/repository_recipes.py
@@ -1,7 +1,7 @@
import re as re
from collections.abc import Sequence
from random import randint
-from typing import cast
+from typing import Self, cast
from uuid import UUID
import sqlalchemy as sa
@@ -10,7 +10,6 @@ from pydantic import UUID4
from slugify import slugify
from sqlalchemy import orm
from sqlalchemy.exc import IntegrityError
-from typing_extensions import Self
from mealie.db.models.household.household import Household
from mealie.db.models.recipe.category import Category
diff --git a/mealie/routes/households/controller_webhooks.py b/mealie/routes/households/controller_webhooks.py
index 8c8c25600..251bb3617 100644
--- a/mealie/routes/households/controller_webhooks.py
+++ b/mealie/routes/households/controller_webhooks.py
@@ -1,4 +1,4 @@
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from functools import cached_property
from fastapi import APIRouter, BackgroundTasks, Depends
@@ -45,7 +45,7 @@ class ReadWebhookController(BaseUserController):
"""Manually re-fires all previously scheduled webhooks for today"""
start_time = datetime.min.time()
- start_dt = datetime.combine(datetime.now(timezone.utc).date(), start_time)
+ start_dt = datetime.combine(datetime.now(UTC).date(), start_time)
post_group_webhooks(start_dt=start_dt, group_id=self.group.id, household_id=self.household.id)
@router.get("/{item_id}", response_model=ReadWebhook)
diff --git a/mealie/schema/_mealie/datetime_parse.py b/mealie/schema/_mealie/datetime_parse.py
index 0e482d9a8..9e0042bc6 100644
--- a/mealie/schema/_mealie/datetime_parse.py
+++ b/mealie/schema/_mealie/datetime_parse.py
@@ -3,7 +3,7 @@ From Pydantic V1: https://github.com/pydantic/pydantic/blob/abcf81ec104d2da70894
"""
import re
-from datetime import date, datetime, time, timedelta, timezone
+from datetime import UTC, date, datetime, time, timedelta, timezone
date_expr = r"(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})"
time_expr = (
@@ -39,7 +39,7 @@ iso8601_duration_re = re.compile(
r"$"
)
-EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
+EPOCH = datetime(1970, 1, 1, tzinfo=UTC)
# if greater than this, the number is in ms, if less than or equal it's in seconds
# (in seconds this is 11th October 2603, in ms it's 20th August 1970)
MS_WATERSHED = int(2e10)
@@ -87,12 +87,12 @@ def from_unix_seconds(seconds: int | float) -> datetime:
while abs(seconds) > MS_WATERSHED:
seconds /= 1000
dt = EPOCH + timedelta(seconds=seconds)
- return dt.replace(tzinfo=timezone.utc)
+ return dt.replace(tzinfo=UTC)
def _parse_timezone(value: str | None, error: type[Exception]) -> None | int | timezone:
if value == "Z":
- return timezone.utc
+ return UTC
elif value is not None:
offset_mins = int(value[-2:]) if len(value) > 3 else 0
offset = 60 * int(value[1:3]) + offset_mins
diff --git a/mealie/schema/_mealie/mealie_model.py b/mealie/schema/_mealie/mealie_model.py
index 4f670ba22..d7d70a6dc 100644
--- a/mealie/schema/_mealie/mealie_model.py
+++ b/mealie/schema/_mealie/mealie_model.py
@@ -2,16 +2,15 @@ from __future__ import annotations
import re
from collections.abc import Sequence
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from enum import Enum
-from typing import ClassVar, Protocol, TypeVar
+from typing import ClassVar, Protocol, Self, TypeVar
from humps.main import camelize
from pydantic import UUID4, AliasChoices, BaseModel, ConfigDict, Field, model_validator
from sqlalchemy import Select, desc, func, or_, text
from sqlalchemy.orm import InstrumentedAttribute, Session
from sqlalchemy.orm.interfaces import LoaderOption
-from typing_extensions import Self
from mealie.db.models._model_base import SqlAlchemyBase
@@ -88,7 +87,7 @@ class MealieModel(BaseModel):
if not isinstance(val, datetime):
continue
if not val.tzinfo:
- setattr(self, field, val.replace(tzinfo=timezone.utc))
+ setattr(self, field, val.replace(tzinfo=UTC))
return self
diff --git a/mealie/schema/household/webhook.py b/mealie/schema/household/webhook.py
index 6c6510b0a..f42054ed5 100644
--- a/mealie/schema/household/webhook.py
+++ b/mealie/schema/household/webhook.py
@@ -32,7 +32,7 @@ class CreateWebhook(MealieModel):
type: datetime is treated as a value with a timezone
"""
parser_funcs = [
- lambda x: parse_datetime(x).astimezone(datetime.timezone.utc).time(),
+ lambda x: parse_datetime(x).astimezone(datetime.UTC).time(),
parse_time,
]
diff --git a/mealie/schema/recipe/recipe_share_token.py b/mealie/schema/recipe/recipe_share_token.py
index fd4e376cf..0e876a05a 100644
--- a/mealie/schema/recipe/recipe_share_token.py
+++ b/mealie/schema/recipe/recipe_share_token.py
@@ -1,4 +1,4 @@
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
from pydantic import UUID4, ConfigDict, Field
from sqlalchemy.orm import selectinload
@@ -11,7 +11,7 @@ from .recipe import Recipe
def defaut_expires_at_time() -> datetime:
- return datetime.now(timezone.utc) + timedelta(days=30)
+ return datetime.now(UTC) + timedelta(days=30)
class RecipeShareTokenCreate(MealieModel):
diff --git a/mealie/schema/recipe/recipe_timeline_events.py b/mealie/schema/recipe/recipe_timeline_events.py
index 02de699ce..88d1072f8 100644
--- a/mealie/schema/recipe/recipe_timeline_events.py
+++ b/mealie/schema/recipe/recipe_timeline_events.py
@@ -1,4 +1,4 @@
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from enum import Enum
from pathlib import Path
from typing import Annotated
@@ -40,7 +40,7 @@ class RecipeTimelineEventIn(MealieModel):
message: str | None = Field(None, alias="eventMessage")
image: Annotated[TimelineEventImage | None, Field(validate_default=True)] = TimelineEventImage.does_not_have_image
- timestamp: datetime = datetime.now(timezone.utc)
+ timestamp: datetime = datetime.now(UTC)
model_config = ConfigDict(use_enum_values=True)
diff --git a/mealie/schema/user/user.py b/mealie/schema/user/user.py
index 8bada9194..f4e1228b4 100644
--- a/mealie/schema/user/user.py
+++ b/mealie/schema/user/user.py
@@ -1,4 +1,4 @@
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
from pathlib import Path
from typing import Annotated, Any, Generic, TypeVar
from uuid import UUID
@@ -218,7 +218,7 @@ class PrivateUser(UserOut):
return False
lockout_expires_at = self.locked_at + timedelta(hours=get_app_settings().SECURITY_USER_LOCKOUT_TIME)
- return lockout_expires_at > datetime.now(timezone.utc)
+ return lockout_expires_at > datetime.now(UTC)
def directory(self) -> Path:
return PrivateUser.get_directory(self.id)
diff --git a/mealie/services/backups_v2/backup_v2.py b/mealie/services/backups_v2/backup_v2.py
index fe8289ed7..01c192448 100644
--- a/mealie/services/backups_v2/backup_v2.py
+++ b/mealie/services/backups_v2/backup_v2.py
@@ -25,7 +25,7 @@ class BackupV2(BaseService):
db_file = self.settings.DB_URL.removeprefix("sqlite:///") # type: ignore
# Create a backup of the SQLite database
- timestamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y.%m.%d")
+ timestamp = datetime.datetime.now(datetime.UTC).strftime("%Y.%m.%d")
shutil.copy(db_file, self.directories.DATA_DIR.joinpath(f"mealie_{timestamp}.bak.db"))
def _postgres(self) -> None:
@@ -37,7 +37,7 @@ class BackupV2(BaseService):
exclude_ext = {".zip"}
exclude_dirs = {"backups", ".temp"}
- timestamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y.%m.%d.%H.%M.%S")
+ timestamp = datetime.datetime.now(datetime.UTC).strftime("%Y.%m.%d.%H.%M.%S")
backup_name = f"mealie_{timestamp}.zip"
backup_file = self.directories.BACKUP_DIR / backup_name
diff --git a/mealie/services/event_bus_service/event_bus_listeners.py b/mealie/services/event_bus_service/event_bus_listeners.py
index dd19992d9..586876c63 100644
--- a/mealie/services/event_bus_service/event_bus_listeners.py
+++ b/mealie/services/event_bus_service/event_bus_listeners.py
@@ -2,7 +2,7 @@ import contextlib
import json
from abc import ABC, abstractmethod
from collections.abc import Generator
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from typing import cast
from urllib.parse import parse_qs, urlencode, urlsplit, urlunsplit
@@ -163,8 +163,8 @@ class WebhookEventListener(EventListenerBase):
with self.ensure_session() as session:
stmt = select(GroupWebhooksModel).where(
GroupWebhooksModel.enabled == True, # noqa: E712 - required for SQLAlchemy comparison
- GroupWebhooksModel.scheduled_time > start_dt.astimezone(timezone.utc).time(),
- GroupWebhooksModel.scheduled_time <= end_dt.astimezone(timezone.utc).time(),
+ GroupWebhooksModel.scheduled_time > start_dt.astimezone(UTC).time(),
+ GroupWebhooksModel.scheduled_time <= end_dt.astimezone(UTC).time(),
GroupWebhooksModel.group_id == self.group_id,
GroupWebhooksModel.household_id == self.household_id,
)
diff --git a/mealie/services/event_bus_service/event_types.py b/mealie/services/event_bus_service/event_types.py
index 56bf6350d..6e3e54b05 100644
--- a/mealie/services/event_bus_service/event_types.py
+++ b/mealie/services/event_bus_service/event_types.py
@@ -1,5 +1,5 @@
import uuid
-from datetime import date, datetime, timezone
+from datetime import UTC, date, datetime
from enum import Enum, auto
from typing import Any
@@ -193,4 +193,4 @@ class Event(MealieModel):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.event_id = uuid.uuid4()
- self.timestamp = datetime.now(timezone.utc)
+ self.timestamp = datetime.now(UTC)
diff --git a/mealie/services/exporter/exporter.py b/mealie/services/exporter/exporter.py
index 1b66cc549..056f680fb 100644
--- a/mealie/services/exporter/exporter.py
+++ b/mealie/services/exporter/exporter.py
@@ -43,7 +43,7 @@ class Exporter(BaseService):
name="Data Export",
size=pretty_size(export_path.stat().st_size),
filename=export_path.name,
- expires=datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1),
+ expires=datetime.datetime.now(datetime.UTC) + datetime.timedelta(days=1),
)
db.group_exports.create(group_data_export)
diff --git a/mealie/services/migrations/copymethat.py b/mealie/services/migrations/copymethat.py
index 6b9c925c5..fd00e4266 100644
--- a/mealie/services/migrations/copymethat.py
+++ b/mealie/services/migrations/copymethat.py
@@ -1,6 +1,6 @@
import tempfile
import zipfile
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from pathlib import Path
from bs4 import BeautifulSoup
@@ -35,7 +35,7 @@ class CopyMeThatMigrator(BaseMigrator):
self.name = "copymethat"
self.key_aliases = [
- MigrationAlias(key="last_made", alias="made_this", func=lambda x: datetime.now(timezone.utc)),
+ MigrationAlias(key="last_made", alias="made_this", func=lambda x: datetime.now(UTC)),
MigrationAlias(key="notes", alias="recipeNotes"),
MigrationAlias(key="orgURL", alias="original_link"),
MigrationAlias(key="rating", alias="ratingValue"),
diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py
index 8bf3f1bba..02eb2fa9a 100644
--- a/mealie/services/recipe/recipe_service.py
+++ b/mealie/services/recipe/recipe_service.py
@@ -1,7 +1,7 @@
import json
import os
import shutil
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from pathlib import Path
from shutil import copytree, rmtree
from typing import Any
@@ -192,7 +192,7 @@ class RecipeService(RecipeServiceBase):
recipe_id=new_recipe.id,
subject=self.t("recipe.recipe-created"),
event_type=TimelineEventType.system,
- timestamp=new_recipe.created_at or datetime.now(timezone.utc),
+ timestamp=new_recipe.created_at or datetime.now(UTC),
)
self.repos.recipe_timeline_events.create(timeline_event_data)
diff --git a/mealie/services/scheduler/scheduler_service.py b/mealie/services/scheduler/scheduler_service.py
index 893912205..5ee5eb1a3 100644
--- a/mealie/services/scheduler/scheduler_service.py
+++ b/mealie/services/scheduler/scheduler_service.py
@@ -1,5 +1,5 @@
import asyncio
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
from pathlib import Path
from mealie.core import root_logger
@@ -28,7 +28,7 @@ class SchedulerService:
async def schedule_daily():
- now = datetime.now(timezone.utc)
+ now = datetime.now(UTC)
daily_schedule_time = get_app_settings().DAILY_SCHEDULE_TIME_UTC
logger.debug(f"Current time is {now} and DAILY_SCHEDULE_TIME (in UTC) is {daily_schedule_time}")
diff --git a/mealie/services/scheduler/tasks/create_timeline_events.py b/mealie/services/scheduler/tasks/create_timeline_events.py
index 3f08ba647..1806e1217 100644
--- a/mealie/services/scheduler/tasks/create_timeline_events.py
+++ b/mealie/services/scheduler/tasks/create_timeline_events.py
@@ -1,4 +1,4 @@
-from datetime import datetime, time, timedelta, timezone
+from datetime import UTC, datetime, time, timedelta
from pydantic import UUID4
from sqlalchemy.orm import Session
@@ -45,7 +45,7 @@ def _create_mealplan_timeline_events_for_household(
else:
event_subject = f"{user.full_name} made this for {mealplan.entry_type.value}"
- query_start_time = datetime.combine(datetime.now(timezone.utc).date(), time.min)
+ query_start_time = datetime.combine(datetime.now(UTC).date(), time.min)
query_end_time = query_start_time + timedelta(days=1)
query = PaginationQuery(
query_filter=(
@@ -116,7 +116,7 @@ def _create_mealplan_timeline_events_for_group(event_time: datetime, session: Se
def create_mealplan_timeline_events() -> None:
- event_time = datetime.now(timezone.utc)
+ event_time = datetime.now(UTC)
with session_context() as session:
repos = get_repositories(session)
diff --git a/mealie/services/scheduler/tasks/post_webhooks.py b/mealie/services/scheduler/tasks/post_webhooks.py
index e3a57459a..5298fa2c8 100644
--- a/mealie/services/scheduler/tasks/post_webhooks.py
+++ b/mealie/services/scheduler/tasks/post_webhooks.py
@@ -1,4 +1,4 @@
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from pydantic import UUID4
@@ -18,7 +18,7 @@ from mealie.services.event_bus_service.event_types import (
EventWebhookData,
)
-last_ran = datetime.now(timezone.utc)
+last_ran = datetime.now(UTC)
def post_group_webhooks(
@@ -32,7 +32,7 @@ def post_group_webhooks(
start_dt = start_dt or last_ran
# end the query at the current time
- last_ran = end_dt = datetime.now(timezone.utc)
+ last_ran = end_dt = datetime.now(UTC)
if group_id is None:
# publish the webhook event to each group's event bus
@@ -80,7 +80,7 @@ def post_group_webhooks(
def post_single_webhook(webhook: ReadWebhook, message: str = "") -> None:
- dt = datetime.min.replace(tzinfo=timezone.utc)
+ dt = datetime.min.replace(tzinfo=UTC)
event_type = EventTypes.webhook_task
event_document_data = EventWebhookData(
diff --git a/mealie/services/scheduler/tasks/purge_group_exports.py b/mealie/services/scheduler/tasks/purge_group_exports.py
index ea69821f1..a92dd2d5e 100644
--- a/mealie/services/scheduler/tasks/purge_group_exports.py
+++ b/mealie/services/scheduler/tasks/purge_group_exports.py
@@ -17,7 +17,7 @@ def purge_group_data_exports(max_minutes_old=ONE_DAY_AS_MINUTES):
logger = root_logger.get_logger()
logger.debug("purging group data exports")
- limit = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(minutes=max_minutes_old)
+ limit = datetime.datetime.now(datetime.UTC) - datetime.timedelta(minutes=max_minutes_old)
with session_context() as session:
stmt = select(GroupDataExportsModel).filter(cast(GroupDataExportsModel.expires, NaiveDateTime) <= limit)
@@ -39,7 +39,7 @@ def purge_excess_files() -> None:
directories = get_app_dirs()
logger = root_logger.get_logger()
- limit = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(minutes=ONE_DAY_AS_MINUTES * 2)
+ limit = datetime.datetime.now(datetime.UTC) - datetime.timedelta(minutes=ONE_DAY_AS_MINUTES * 2)
for file in directories.GROUPS_DIR.glob("**/export/*.zip"):
# TODO: fix comparison types
diff --git a/mealie/services/scheduler/tasks/purge_password_reset.py b/mealie/services/scheduler/tasks/purge_password_reset.py
index f783e9591..99aab0f6b 100644
--- a/mealie/services/scheduler/tasks/purge_password_reset.py
+++ b/mealie/services/scheduler/tasks/purge_password_reset.py
@@ -14,7 +14,7 @@ MAX_DAYS_OLD = 2
def purge_password_reset_tokens():
"""Purges all events after x days"""
logger.debug("purging password reset tokens")
- limit = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=MAX_DAYS_OLD)
+ limit = datetime.datetime.now(datetime.UTC) - datetime.timedelta(days=MAX_DAYS_OLD)
with session_context() as session:
stmt = delete(PasswordResetModel).filter(PasswordResetModel.created_at <= limit)
diff --git a/mealie/services/scheduler/tasks/purge_registration.py b/mealie/services/scheduler/tasks/purge_registration.py
index 58e09c9eb..855a03581 100644
--- a/mealie/services/scheduler/tasks/purge_registration.py
+++ b/mealie/services/scheduler/tasks/purge_registration.py
@@ -14,7 +14,7 @@ MAX_DAYS_OLD = 4
def purge_group_registration():
"""Purges all events after x days"""
logger.debug("purging expired registration tokens")
- limit = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=MAX_DAYS_OLD)
+ limit = datetime.datetime.now(datetime.UTC) - datetime.timedelta(days=MAX_DAYS_OLD)
with session_context() as session:
stmt = delete(GroupInviteToken).filter(GroupInviteToken.created_at <= limit)
diff --git a/mealie/services/user_services/user_service.py b/mealie/services/user_services/user_service.py
index 51db3f6e0..b73c1b417 100644
--- a/mealie/services/user_services/user_service.py
+++ b/mealie/services/user_services/user_service.py
@@ -1,4 +1,4 @@
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.user.user import PrivateUser
@@ -30,7 +30,7 @@ class UserService(BaseService):
return unlocked
def lock_user(self, user: PrivateUser) -> PrivateUser:
- user.locked_at = datetime.now(timezone.utc)
+ user.locked_at = datetime.now(UTC)
return self.repos.users.update(user.id, user)
def unlock_user(self, user: PrivateUser) -> PrivateUser:
diff --git a/poetry.lock b/poetry.lock
index 45f414fa3..f4f4a951b 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
[[package]]
name = "aiofiles"
@@ -67,7 +67,6 @@ files = [
]
[package.dependencies]
-exceptiongroup = {version = "*", markers = "python_version < \"3.11\""}
idna = ">=2.8"
sniffio = ">=1.1"
@@ -117,9 +116,6 @@ files = [
{file = "astroid-3.3.5.tar.gz", hash = "sha256:5cfc40ae9f68311075d27ef68a4841bdc5cc7f6cf86671b49f00607d30188e2d"},
]
-[package.dependencies]
-typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""}
-
[[package]]
name = "authlib"
version = "1.3.2"
@@ -565,13 +561,13 @@ graph = ["objgraph (>=1.7.2)"]
[[package]]
name = "distlib"
-version = "0.3.6"
+version = "0.3.9"
description = "Distribution utilities"
optional = false
python-versions = "*"
files = [
- {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"},
- {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"},
+ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"},
+ {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"},
]
[[package]]
@@ -585,20 +581,6 @@ files = [
{file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"},
]
-[[package]]
-name = "exceptiongroup"
-version = "1.1.0"
-description = "Backport of PEP 654 (exception groups)"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"},
- {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"},
-]
-
-[package.extras]
-test = ["pytest (>=6)"]
-
[[package]]
name = "extruct"
version = "0.18.0"
@@ -645,18 +627,19 @@ standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "htt
[[package]]
name = "filelock"
-version = "3.9.0"
+version = "3.16.1"
description = "A platform independent file lock."
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"},
- {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"},
+ {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"},
+ {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"},
]
[package.extras]
-docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
-testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"]
+docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"]
+typing = ["typing-extensions (>=4.12.2)"]
[[package]]
name = "freezegun"
@@ -1549,7 +1532,6 @@ files = [
[package.dependencies]
mypy-extensions = ">=1.0.0"
-tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = ">=4.6.0"
[package.extras]
@@ -1911,18 +1893,19 @@ tests-min = ["defusedxml", "packaging", "pytest"]
[[package]]
name = "platformdirs"
-version = "2.6.2"
-description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+version = "4.3.6"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"},
- {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"},
+ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
+ {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
]
[package.extras]
-docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
-test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
+docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"]
+type = ["mypy (>=1.11.2)"]
[[package]]
name = "pluggy"
@@ -2295,15 +2278,10 @@ files = [
[package.dependencies]
astroid = ">=3.3.5,<=3.4.0-dev0"
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
-dill = [
- {version = ">=0.2", markers = "python_version < \"3.11\""},
- {version = ">=0.3.7", markers = "python_version >= \"3.12\""},
- {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""},
-]
+dill = {version = ">=0.3.7", markers = "python_version >= \"3.12\""}
isort = ">=4.2.5,<5.13.0 || >5.13.0,<6"
mccabe = ">=0.6,<0.8"
platformdirs = ">=2.2.0"
-tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
tomlkit = ">=0.10.1"
[package.extras]
@@ -2371,11 +2349,9 @@ files = [
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
-exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=1.5,<2"
-tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
@@ -2838,7 +2814,6 @@ files = [
[package.dependencies]
markdown-it-py = ">=2.2.0"
pygments = ">=2.13.0,<3.0.0"
-typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""}
[package.extras]
jupyter = ["ipywidgets (>=7.5.1,<9)"]
@@ -2872,19 +2847,23 @@ files = [
[[package]]
name = "setuptools"
-version = "67.1.0"
+version = "75.6.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.9"
files = [
- {file = "setuptools-67.1.0-py3-none-any.whl", hash = "sha256:a7687c12b444eaac951ea87a9627c4f904ac757e7abdc5aac32833234af90378"},
- {file = "setuptools-67.1.0.tar.gz", hash = "sha256:e261cdf010c11a41cb5cb5f1bf3338a7433832029f559a6a7614bd42a967c300"},
+ {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"},
+ {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"},
]
[package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
-testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
-testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"]
+core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
+cover = ["pytest-cov"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
+enabler = ["pytest-enabler (>=2.2)"]
+test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
+type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"]
[[package]]
name = "six"
@@ -3042,17 +3021,6 @@ files = [
{file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
]
-[[package]]
-name = "tomli"
-version = "1.2.3"
-description = "A lil' TOML parser"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"},
- {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"},
-]
-
[[package]]
name = "tomlkit"
version = "0.11.6"
@@ -3198,7 +3166,6 @@ h11 = ">=0.8"
httptools = {version = ">=0.6.3", optional = true, markers = "extra == \"standard\""}
python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
-typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""}
@@ -3252,23 +3219,23 @@ test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)"
[[package]]
name = "virtualenv"
-version = "20.17.1"
+version = "20.28.0"
description = "Virtual Python Environment builder"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.8"
files = [
- {file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"},
- {file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"},
+ {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"},
+ {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"},
]
[package.dependencies]
-distlib = ">=0.3.6,<1"
-filelock = ">=3.4.1,<4"
-platformdirs = ">=2.4,<3"
+distlib = ">=0.3.7,<1"
+filelock = ">=3.12.2,<4"
+platformdirs = ">=3.9.1,<5"
[package.extras]
-docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"]
-testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
+docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
+test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
[[package]]
name = "w3lib"
@@ -3445,5 +3412,5 @@ pgsql = ["psycopg2-binary"]
[metadata]
lock-version = "2.0"
-python-versions = "^3.10"
-content-hash = "55dbb0d6a3e28964743f87ae1d3a4ead8428cf1051ea97839edb325f39c526ba"
+python-versions = "^3.12"
+content-hash = "70a06c4bc96fda6284e61a84db5770d969ea06e78caaa5860966b53768607929"
diff --git a/pyproject.toml b/pyproject.toml
index 0171889c9..5c9eacf8f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -27,7 +27,7 @@ orjson = "^3.8.0"
psycopg2-binary = { version = "^2.9.1", optional = true }
pydantic = "^2.6.1"
pyhumps = "^3.5.3"
-python = "^3.10"
+python = "^3.12"
python-dateutil = "^2.8.2"
python-dotenv = "^1.0.0"
python-ldap = "^3.3.1"
@@ -105,7 +105,7 @@ pgsql = ["psycopg2-binary"]
follow_imports = "skip"
ignore_missing_imports = true
plugins = "pydantic.mypy"
-python_version = "3.10"
+python_version = "3.12"
strict_optional = true
[tool.ruff]
@@ -135,8 +135,8 @@ exclude = [
"venv",
]
-# Assume Python 3.10.
-target-version = "py310"
+# Assume Python 3.12.
+target-version = "py312"
[tool.ruff.lint]
# Enable Pyflakes `E` and `F` codes by default.
From d9a1db557cca1ba70ca25c147597110ebeb164be Mon Sep 17 00:00:00 2001
From: niteflyunicorns <86175805+niteflyunicorns@users.noreply.github.com>
Date: Wed, 4 Dec 2024 21:59:17 -0700
Subject: [PATCH 072/238] feat: Add 'No Shopping Lists Found' message (#4661)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
---
.../Domain/Recipe/RecipeDialogAddToShoppingList.vue | 5 +++++
frontend/lang/messages/en-US.json | 3 ++-
frontend/pages/shopping-lists/index.vue | 6 ++++++
3 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue b/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue
index 37e10ed5b..789d59bd4 100644
--- a/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue
+++ b/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue
@@ -1,6 +1,11 @@
+
+
+ {{ $t('shopping-list.no-shopping-lists-found') }}
+
+
+
+
+ {{ $t('shopping-list.no-shopping-lists-found') }}
+
+
+
Date: Thu, 5 Dec 2024 13:25:07 +0100
Subject: [PATCH 073/238] fix(deps): update dependency openai to v1.56.2
(#4679)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index f4f4a951b..d5821548f 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1584,13 +1584,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "openai"
-version = "1.56.1"
+version = "1.56.2"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.8"
files = [
- {file = "openai-1.56.1-py3-none-any.whl", hash = "sha256:38e61183c2a98fedebbbb04a909a052d9f897358b070483fc0caff17300a227c"},
- {file = "openai-1.56.1.tar.gz", hash = "sha256:8b0449f22a0c318441eae8a8a789753c3b2cac86542be51ca45df788e26aa180"},
+ {file = "openai-1.56.2-py3-none-any.whl", hash = "sha256:82d0c48f9504e04c7797e9b799dcf7f49a246d99b6cbfd90f3193ea80815b69e"},
+ {file = "openai-1.56.2.tar.gz", hash = "sha256:17312af69bc7670d4048f98ab5849f8784d98c39ac64fcde19406e3774a0c1e5"},
]
[package.dependencies]
From c691418cfb77121b5f00f79c0f0ac431bdbbf6f2 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 5 Dec 2024 15:54:38 +0000
Subject: [PATCH 074/238] chore(deps): update dependency ruff to v0.8.2 (#4683)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 38 +++++++++++++++++++-------------------
1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index d5821548f..371aef43e 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2820,29 +2820,29 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "ruff"
-version = "0.8.1"
+version = "0.8.2"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
- {file = "ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5"},
- {file = "ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087"},
- {file = "ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209"},
- {file = "ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871"},
- {file = "ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1"},
- {file = "ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5"},
- {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d"},
- {file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26"},
- {file = "ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1"},
- {file = "ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c"},
- {file = "ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa"},
- {file = "ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540"},
- {file = "ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9"},
- {file = "ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5"},
- {file = "ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790"},
- {file = "ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6"},
- {file = "ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737"},
- {file = "ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f"},
+ {file = "ruff-0.8.2-py3-none-linux_armv6l.whl", hash = "sha256:c49ab4da37e7c457105aadfd2725e24305ff9bc908487a9bf8d548c6dad8bb3d"},
+ {file = "ruff-0.8.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ec016beb69ac16be416c435828be702ee694c0d722505f9c1f35e1b9c0cc1bf5"},
+ {file = "ruff-0.8.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f05cdf8d050b30e2ba55c9b09330b51f9f97d36d4673213679b965d25a785f3c"},
+ {file = "ruff-0.8.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60f578c11feb1d3d257b2fb043ddb47501ab4816e7e221fbb0077f0d5d4e7b6f"},
+ {file = "ruff-0.8.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbd5cf9b0ae8f30eebc7b360171bd50f59ab29d39f06a670b3e4501a36ba5897"},
+ {file = "ruff-0.8.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b402ddee3d777683de60ff76da801fa7e5e8a71038f57ee53e903afbcefdaa58"},
+ {file = "ruff-0.8.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:705832cd7d85605cb7858d8a13d75993c8f3ef1397b0831289109e953d833d29"},
+ {file = "ruff-0.8.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:32096b41aaf7a5cc095fa45b4167b890e4c8d3fd217603f3634c92a541de7248"},
+ {file = "ruff-0.8.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e769083da9439508833cfc7c23e351e1809e67f47c50248250ce1ac52c21fb93"},
+ {file = "ruff-0.8.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fe716592ae8a376c2673fdfc1f5c0c193a6d0411f90a496863c99cd9e2ae25d"},
+ {file = "ruff-0.8.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:81c148825277e737493242b44c5388a300584d73d5774defa9245aaef55448b0"},
+ {file = "ruff-0.8.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d261d7850c8367704874847d95febc698a950bf061c9475d4a8b7689adc4f7fa"},
+ {file = "ruff-0.8.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1ca4e3a87496dc07d2427b7dd7ffa88a1e597c28dad65ae6433ecb9f2e4f022f"},
+ {file = "ruff-0.8.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:729850feed82ef2440aa27946ab39c18cb4a8889c1128a6d589ffa028ddcfc22"},
+ {file = "ruff-0.8.2-py3-none-win32.whl", hash = "sha256:ac42caaa0411d6a7d9594363294416e0e48fc1279e1b0e948391695db2b3d5b1"},
+ {file = "ruff-0.8.2-py3-none-win_amd64.whl", hash = "sha256:2aae99ec70abf43372612a838d97bfe77d45146254568d94926e8ed5bbb409ea"},
+ {file = "ruff-0.8.2-py3-none-win_arm64.whl", hash = "sha256:fb88e2a506b70cfbc2de6fae6681c4f944f7dd5f2fe87233a7233d888bad73e8"},
+ {file = "ruff-0.8.2.tar.gz", hash = "sha256:b84f4f414dda8ac7f75075c1fa0b905ac0ff25361f42e6d5da681a465e0f78e5"},
]
[[package]]
From 61934537c9cb9e037cdf3ebaf3922135a58fa4e6 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 5 Dec 2024 21:42:52 +0100
Subject: [PATCH 075/238] fix(deps): update dependency openai to v1.57.0
(#4685)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 371aef43e..30a1b80c4 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1584,13 +1584,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "openai"
-version = "1.56.2"
+version = "1.57.0"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.8"
files = [
- {file = "openai-1.56.2-py3-none-any.whl", hash = "sha256:82d0c48f9504e04c7797e9b799dcf7f49a246d99b6cbfd90f3193ea80815b69e"},
- {file = "openai-1.56.2.tar.gz", hash = "sha256:17312af69bc7670d4048f98ab5849f8784d98c39ac64fcde19406e3774a0c1e5"},
+ {file = "openai-1.57.0-py3-none-any.whl", hash = "sha256:972e36960b821797952da3dc4532f486c28e28a2a332d7d0c5407f242e9d9c39"},
+ {file = "openai-1.57.0.tar.gz", hash = "sha256:76f91971c4bdbd78380c9970581075e0337b5d497c2fbf7b5255078f4b31abf9"},
]
[package.dependencies]
From 17d74c451e59ad7f31f2eb483c615f69f861eecc Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 6 Dec 2024 21:27:35 +0100
Subject: [PATCH 076/238] fix(deps): update dependency httpx to v0.28.1 (#4690)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 30a1b80c4..6a0b2e9bc 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -877,13 +877,13 @@ test = ["Cython (>=0.29.24)"]
[[package]]
name = "httpx"
-version = "0.28.0"
+version = "0.28.1"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
files = [
- {file = "httpx-0.28.0-py3-none-any.whl", hash = "sha256:dc0b419a0cfeb6e8b34e85167c0da2671206f5095f1baa9663d23bcfd6b535fc"},
- {file = "httpx-0.28.0.tar.gz", hash = "sha256:0858d3bab51ba7e386637f22a61d8ccddaeec5f3fe4209da3a6168dbb91573e0"},
+ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
+ {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
]
[package.dependencies]
From c8d3bd449524227ce524690bfd028bcf5a1d9aec Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 8 Dec 2024 15:42:08 -0600
Subject: [PATCH 077/238] chore(deps): update dependency mkdocs-material to
v9.5.48 (#4696)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 6a0b2e9bc..906b5977a 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1451,13 +1451,13 @@ pyyaml = ">=5.1"
[[package]]
name = "mkdocs-material"
-version = "9.5.47"
+version = "9.5.48"
description = "Documentation that simply works"
optional = false
python-versions = ">=3.8"
files = [
- {file = "mkdocs_material-9.5.47-py3-none-any.whl", hash = "sha256:53fb9c9624e7865da6ec807d116cd7be24b3cb36ab31b1d1d1a9af58c56009a2"},
- {file = "mkdocs_material-9.5.47.tar.gz", hash = "sha256:fc3b7a8e00ad896660bd3a5cc12ca0cb28bdc2bcbe2a946b5714c23ac91b0ede"},
+ {file = "mkdocs_material-9.5.48-py3-none-any.whl", hash = "sha256:b695c998f4b939ce748adbc0d3bff73fa886a670ece948cf27818fa115dc16f8"},
+ {file = "mkdocs_material-9.5.48.tar.gz", hash = "sha256:a582531e8b34f4c7ed38c29d5c44763053832cf2a32f7409567e0c74749a47db"},
]
[package.dependencies]
From 1ec580342fc6a40d823a28c9a1077ae5b9a26119 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 9 Dec 2024 09:25:58 -0600
Subject: [PATCH 078/238] chore(deps): update dependency coverage to v7.6.9
(#4688)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 126 ++++++++++++++++++++++++++--------------------------
1 file changed, 63 insertions(+), 63 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 906b5977a..8603a6a99 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -413,73 +413,73 @@ files = [
[[package]]
name = "coverage"
-version = "7.6.8"
+version = "7.6.9"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.9"
files = [
- {file = "coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50"},
- {file = "coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf"},
- {file = "coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee"},
- {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6"},
- {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d"},
- {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331"},
- {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638"},
- {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed"},
- {file = "coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e"},
- {file = "coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a"},
- {file = "coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4"},
- {file = "coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94"},
- {file = "coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4"},
- {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1"},
- {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb"},
- {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8"},
- {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a"},
- {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0"},
- {file = "coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801"},
- {file = "coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"},
- {file = "coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee"},
- {file = "coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a"},
- {file = "coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d"},
- {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb"},
- {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649"},
- {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787"},
- {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c"},
- {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443"},
- {file = "coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad"},
- {file = "coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4"},
- {file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"},
- {file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"},
- {file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"},
- {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002"},
- {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3"},
- {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"},
- {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"},
- {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"},
- {file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"},
- {file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"},
- {file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"},
- {file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"},
- {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"},
- {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf"},
- {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5"},
- {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"},
- {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"},
- {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"},
- {file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"},
- {file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"},
- {file = "coverage-7.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e"},
- {file = "coverage-7.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c"},
- {file = "coverage-7.6.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0"},
- {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779"},
- {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92"},
- {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4"},
- {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc"},
- {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea"},
- {file = "coverage-7.6.8-cp39-cp39-win32.whl", hash = "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e"},
- {file = "coverage-7.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076"},
- {file = "coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce"},
- {file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"},
+ {file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"},
+ {file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"},
+ {file = "coverage-7.6.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa"},
+ {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1"},
+ {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec"},
+ {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3"},
+ {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5"},
+ {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073"},
+ {file = "coverage-7.6.9-cp310-cp310-win32.whl", hash = "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198"},
+ {file = "coverage-7.6.9-cp310-cp310-win_amd64.whl", hash = "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717"},
+ {file = "coverage-7.6.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9"},
+ {file = "coverage-7.6.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c"},
+ {file = "coverage-7.6.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7"},
+ {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9"},
+ {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4"},
+ {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1"},
+ {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b"},
+ {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3"},
+ {file = "coverage-7.6.9-cp311-cp311-win32.whl", hash = "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0"},
+ {file = "coverage-7.6.9-cp311-cp311-win_amd64.whl", hash = "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b"},
+ {file = "coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8"},
+ {file = "coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a"},
+ {file = "coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015"},
+ {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3"},
+ {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae"},
+ {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4"},
+ {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6"},
+ {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f"},
+ {file = "coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692"},
+ {file = "coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97"},
+ {file = "coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664"},
+ {file = "coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c"},
+ {file = "coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014"},
+ {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00"},
+ {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d"},
+ {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a"},
+ {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077"},
+ {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb"},
+ {file = "coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba"},
+ {file = "coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1"},
+ {file = "coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419"},
+ {file = "coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a"},
+ {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4"},
+ {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae"},
+ {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030"},
+ {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be"},
+ {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e"},
+ {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9"},
+ {file = "coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b"},
+ {file = "coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611"},
+ {file = "coverage-7.6.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902"},
+ {file = "coverage-7.6.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be"},
+ {file = "coverage-7.6.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599"},
+ {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08"},
+ {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464"},
+ {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845"},
+ {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf"},
+ {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678"},
+ {file = "coverage-7.6.9-cp39-cp39-win32.whl", hash = "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6"},
+ {file = "coverage-7.6.9-cp39-cp39-win_amd64.whl", hash = "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4"},
+ {file = "coverage-7.6.9-pp39.pp310-none-any.whl", hash = "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b"},
+ {file = "coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d"},
]
[package.extras]
From 1187a678bad62fd582ab4642e6de238ff98ca62f Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 9 Dec 2024 15:37:23 +0000
Subject: [PATCH 079/238] chore(auto): Update pre-commit hooks (#4697)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
---
.pre-commit-config.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index db821321b..2e8de8afb 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -12,7 +12,7 @@ repos:
exclude: ^tests/data/
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
- rev: v0.8.1
+ rev: v0.8.2
hooks:
- id: ruff
- id: ruff-format
From 65ab714c3824fcd7a206e84eb3105ba809a49663 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 9 Dec 2024 11:43:18 -0600
Subject: [PATCH 080/238] fix(deps): update dependency fastapi to v0.115.6
(#4677)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 8603a6a99..a40fceb6c 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -607,13 +607,13 @@ cli = ["requests"]
[[package]]
name = "fastapi"
-version = "0.115.5"
+version = "0.115.6"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false
python-versions = ">=3.8"
files = [
- {file = "fastapi-0.115.5-py3-none-any.whl", hash = "sha256:596b95adbe1474da47049e802f9a65ab2ffa9c2b07e7efee70eb8a66c9f2f796"},
- {file = "fastapi-0.115.5.tar.gz", hash = "sha256:0e7a4d0dc0d01c68df21887cce0945e72d3c48b9f4f79dfe7a7d53aa08fbb289"},
+ {file = "fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305"},
+ {file = "fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654"},
]
[package.dependencies]
From eecda1be4d2e48550050acc86cee306c1129dd71 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 10 Dec 2024 14:55:50 +0100
Subject: [PATCH 081/238] fix(deps): update dependency openai to v1.57.1
(#4700)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index a40fceb6c..e1efa9287 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1584,13 +1584,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "openai"
-version = "1.57.0"
+version = "1.57.1"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.8"
files = [
- {file = "openai-1.57.0-py3-none-any.whl", hash = "sha256:972e36960b821797952da3dc4532f486c28e28a2a332d7d0c5407f242e9d9c39"},
- {file = "openai-1.57.0.tar.gz", hash = "sha256:76f91971c4bdbd78380c9970581075e0337b5d497c2fbf7b5255078f4b31abf9"},
+ {file = "openai-1.57.1-py3-none-any.whl", hash = "sha256:3865686c927e93492d1145938d4a24b634951531c4b2769d43ca5dbd4b25d8fd"},
+ {file = "openai-1.57.1.tar.gz", hash = "sha256:a95f22e04ab3df26e64a15d958342265e802314131275908b3b3e36f8c5d4377"},
]
[package.dependencies]
From 02a545dcb5ae8ff441e60b11c01b41f944efb7e4 Mon Sep 17 00:00:00 2001
From: Michael Genson <71845777+michael-genson@users.noreply.github.com>
Date: Tue, 10 Dec 2024 08:10:07 -0600
Subject: [PATCH 082/238] fix: Sort Food by Label on Data Management Page
(#4631)
---
frontend/components/global/CrudTable.vue | 2 ++
frontend/pages/group/data/foods.vue | 6 ++++++
2 files changed, 8 insertions(+)
diff --git a/frontend/components/global/CrudTable.vue b/frontend/components/global/CrudTable.vue
index 1c7bfe3c9..6e427220d 100644
--- a/frontend/components/global/CrudTable.vue
+++ b/frontend/components/global/CrudTable.vue
@@ -99,6 +99,8 @@ export interface TableHeaders {
value: string;
show: boolean;
align?: string;
+ sortable?: boolean;
+ sort?: (a: any, b: any) => number;
}
export interface BulkAction {
diff --git a/frontend/pages/group/data/foods.vue b/frontend/pages/group/data/foods.vue
index cb41a09bd..bdc2c4848 100644
--- a/frontend/pages/group/data/foods.vue
+++ b/frontend/pages/group/data/foods.vue
@@ -290,6 +290,7 @@ import MultiPurposeLabel from "~/components/Domain/ShoppingList/MultiPurposeLabe
import { useLocales } from "~/composables/use-locales";
import { useFoodStore, useLabelStore } from "~/composables/store";
import { VForm } from "~/types/vuetify";
+import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
export default defineComponent({
components: { MultiPurposeLabel, RecipeDataAliasManagerDialog },
@@ -325,6 +326,11 @@ export default defineComponent({
text: i18n.tc("shopping-list.label"),
value: "label",
show: true,
+ sort: (label1: MultiPurposeLabelOut | null, label2: MultiPurposeLabelOut | null) => {
+ const label1Name = label1?.name || "";
+ const label2Name = label2?.name || "";
+ return label1Name.localeCompare(label2Name);
+ },
},
{
text: i18n.tc("tool.on-hand"),
From 945ffb34fe6eac7a7179059c2aeb4e741e088b8c Mon Sep 17 00:00:00 2001
From: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
Date: Tue, 10 Dec 2024 16:58:34 +0100
Subject: [PATCH 083/238] fix: code warnings (lint) (#4704)
---
frontend/pages/household/mealplan/planner/edit.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/pages/household/mealplan/planner/edit.vue b/frontend/pages/household/mealplan/planner/edit.vue
index a94f44661..0730705d3 100644
--- a/frontend/pages/household/mealplan/planner/edit.vue
+++ b/frontend/pages/household/mealplan/planner/edit.vue
@@ -13,6 +13,7 @@
)"
color="primary"
:icon="$globals.icons.foods"
+ :submit-disabled="isCreateDisabled"
@submit="
if (newMeal.existing) {
actions.updateOne(newMeal);
@@ -22,7 +23,6 @@
resetDialog();
"
@close="resetDialog()"
- :submitDisabled="isCreateDisabled"
>
Date: Wed, 11 Dec 2024 09:58:49 +0100
Subject: [PATCH 084/238] fix(deps): update dependency openai to v1.57.2
(#4705)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index e1efa9287..98ec8da59 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1584,13 +1584,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "openai"
-version = "1.57.1"
+version = "1.57.2"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.8"
files = [
- {file = "openai-1.57.1-py3-none-any.whl", hash = "sha256:3865686c927e93492d1145938d4a24b634951531c4b2769d43ca5dbd4b25d8fd"},
- {file = "openai-1.57.1.tar.gz", hash = "sha256:a95f22e04ab3df26e64a15d958342265e802314131275908b3b3e36f8c5d4377"},
+ {file = "openai-1.57.2-py3-none-any.whl", hash = "sha256:f7326283c156fdee875746e7e54d36959fb198eadc683952ee05e3302fbd638d"},
+ {file = "openai-1.57.2.tar.gz", hash = "sha256:5f49fd0f38e9f2131cda7deb45dafdd1aee4f52a637e190ce0ecf40147ce8cee"},
]
[package.dependencies]
From cc37147a34b104764eb09f32fa59771155bf7fc6 Mon Sep 17 00:00:00 2001
From: Michael Genson <71845777+michael-genson@users.noreply.github.com>
Date: Wed, 11 Dec 2024 03:33:43 -0600
Subject: [PATCH 085/238] fix: Add Formatting for Markdown Tables (#4703)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
---
frontend/components/global/SafeMarkdown.vue | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/frontend/components/global/SafeMarkdown.vue b/frontend/components/global/SafeMarkdown.vue
index 030080a4e..7979ff8ba 100644
--- a/frontend/components/global/SafeMarkdown.vue
+++ b/frontend/components/global/SafeMarkdown.vue
@@ -48,3 +48,20 @@ export default defineComponent({
},
});
+
+
From 09cf3466e3380525acc8b5953b684885e7e2e053 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 12 Dec 2024 14:38:35 -0600
Subject: [PATCH 086/238] fix(deps): update dependency openai to v1.57.3
(#4713)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 98ec8da59..0336f880e 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1584,13 +1584,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "openai"
-version = "1.57.2"
+version = "1.57.3"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.8"
files = [
- {file = "openai-1.57.2-py3-none-any.whl", hash = "sha256:f7326283c156fdee875746e7e54d36959fb198eadc683952ee05e3302fbd638d"},
- {file = "openai-1.57.2.tar.gz", hash = "sha256:5f49fd0f38e9f2131cda7deb45dafdd1aee4f52a637e190ce0ecf40147ce8cee"},
+ {file = "openai-1.57.3-py3-none-any.whl", hash = "sha256:c4034a5676eb252ef2e0ed1f46d040ca3bdde24bb61b432f50bb0b38d0cf9ecf"},
+ {file = "openai-1.57.3.tar.gz", hash = "sha256:2c98ca6532b30d8bc5029974d2fcbd793b650009c2b014f47ffd4f9fdfc1f9eb"},
]
[package.dependencies]
From 95aeb0ea0557a469be955488eae93e834776edde Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 12 Dec 2024 20:51:01 +0000
Subject: [PATCH 087/238] chore(deps): update dependency ruff to v0.8.3 (#4712)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 38 +++++++++++++++++++-------------------
1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 0336f880e..ba2e06d88 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2820,29 +2820,29 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "ruff"
-version = "0.8.2"
+version = "0.8.3"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
- {file = "ruff-0.8.2-py3-none-linux_armv6l.whl", hash = "sha256:c49ab4da37e7c457105aadfd2725e24305ff9bc908487a9bf8d548c6dad8bb3d"},
- {file = "ruff-0.8.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ec016beb69ac16be416c435828be702ee694c0d722505f9c1f35e1b9c0cc1bf5"},
- {file = "ruff-0.8.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f05cdf8d050b30e2ba55c9b09330b51f9f97d36d4673213679b965d25a785f3c"},
- {file = "ruff-0.8.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60f578c11feb1d3d257b2fb043ddb47501ab4816e7e221fbb0077f0d5d4e7b6f"},
- {file = "ruff-0.8.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbd5cf9b0ae8f30eebc7b360171bd50f59ab29d39f06a670b3e4501a36ba5897"},
- {file = "ruff-0.8.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b402ddee3d777683de60ff76da801fa7e5e8a71038f57ee53e903afbcefdaa58"},
- {file = "ruff-0.8.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:705832cd7d85605cb7858d8a13d75993c8f3ef1397b0831289109e953d833d29"},
- {file = "ruff-0.8.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:32096b41aaf7a5cc095fa45b4167b890e4c8d3fd217603f3634c92a541de7248"},
- {file = "ruff-0.8.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e769083da9439508833cfc7c23e351e1809e67f47c50248250ce1ac52c21fb93"},
- {file = "ruff-0.8.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fe716592ae8a376c2673fdfc1f5c0c193a6d0411f90a496863c99cd9e2ae25d"},
- {file = "ruff-0.8.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:81c148825277e737493242b44c5388a300584d73d5774defa9245aaef55448b0"},
- {file = "ruff-0.8.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d261d7850c8367704874847d95febc698a950bf061c9475d4a8b7689adc4f7fa"},
- {file = "ruff-0.8.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1ca4e3a87496dc07d2427b7dd7ffa88a1e597c28dad65ae6433ecb9f2e4f022f"},
- {file = "ruff-0.8.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:729850feed82ef2440aa27946ab39c18cb4a8889c1128a6d589ffa028ddcfc22"},
- {file = "ruff-0.8.2-py3-none-win32.whl", hash = "sha256:ac42caaa0411d6a7d9594363294416e0e48fc1279e1b0e948391695db2b3d5b1"},
- {file = "ruff-0.8.2-py3-none-win_amd64.whl", hash = "sha256:2aae99ec70abf43372612a838d97bfe77d45146254568d94926e8ed5bbb409ea"},
- {file = "ruff-0.8.2-py3-none-win_arm64.whl", hash = "sha256:fb88e2a506b70cfbc2de6fae6681c4f944f7dd5f2fe87233a7233d888bad73e8"},
- {file = "ruff-0.8.2.tar.gz", hash = "sha256:b84f4f414dda8ac7f75075c1fa0b905ac0ff25361f42e6d5da681a465e0f78e5"},
+ {file = "ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6"},
+ {file = "ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939"},
+ {file = "ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d"},
+ {file = "ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13"},
+ {file = "ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18"},
+ {file = "ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502"},
+ {file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d"},
+ {file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82"},
+ {file = "ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452"},
+ {file = "ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd"},
+ {file = "ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20"},
+ {file = "ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc"},
+ {file = "ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060"},
+ {file = "ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea"},
+ {file = "ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964"},
+ {file = "ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9"},
+ {file = "ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936"},
+ {file = "ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3"},
]
[[package]]
From ba3a999ed685acbe8d11a6d28dd4d74c82880082 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 13 Dec 2024 09:21:38 -0600
Subject: [PATCH 088/238] chore(deps): update dependency pytest-asyncio to
^0.25.0 (#4716)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 12 ++++++------
pyproject.toml | 2 +-
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index ba2e06d88..33fec2825 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2358,20 +2358,20 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments
[[package]]
name = "pytest-asyncio"
-version = "0.24.0"
+version = "0.25.0"
description = "Pytest support for asyncio"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
files = [
- {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"},
- {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"},
+ {file = "pytest_asyncio-0.25.0-py3-none-any.whl", hash = "sha256:db5432d18eac6b7e28b46dcd9b69921b55c3b1086e85febfe04e70b18d9e81b3"},
+ {file = "pytest_asyncio-0.25.0.tar.gz", hash = "sha256:8c0610303c9e0442a5db8604505fc0f545456ba1528824842b37b4a626cbf609"},
]
[package.dependencies]
pytest = ">=8.2,<9"
[package.extras]
-docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
+docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"]
testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
[[package]]
@@ -3413,4 +3413,4 @@ pgsql = ["psycopg2-binary"]
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
-content-hash = "70a06c4bc96fda6284e61a84db5770d969ea06e78caaa5860966b53768607929"
+content-hash = "f3d55a94c83be35bc1ff91044a10f63129dc788318b544fc0bb3a84b2fd82df5"
diff --git a/pyproject.toml b/pyproject.toml
index 5c9eacf8f..a687a6584 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -62,7 +62,7 @@ mypy = "^1.5.1"
pre-commit = "^4.0.0"
pylint = "^3.0.0"
pytest = "^8.0.0"
-pytest-asyncio = "^0.24.0"
+pytest-asyncio = "^0.25.0"
rich = "^13.5.2"
ruff = "^0.8.0"
types-PyYAML = "^6.0.4"
From e88779f9ec99ef227cec0b3e631e656c9661d57a Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 13 Dec 2024 15:34:20 +0000
Subject: [PATCH 089/238] fix(deps): update dependency pydantic-settings to
v2.7.0 (#4718)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 33fec2825..073fd65cf 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2186,13 +2186,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pydantic-settings"
-version = "2.6.1"
+version = "2.7.0"
description = "Settings management using Pydantic"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87"},
- {file = "pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0"},
+ {file = "pydantic_settings-2.7.0-py3-none-any.whl", hash = "sha256:e00c05d5fa6cbbb227c84bd7487c5c1065084119b750df7c8c1a554aed236eb5"},
+ {file = "pydantic_settings-2.7.0.tar.gz", hash = "sha256:ac4bfd4a36831a48dbf8b2d9325425b549a0a6f18cea118436d728eb4f1c4d66"},
]
[package.dependencies]
From 2abae02388ca605210baa76fc2db1ebdacc46628 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 13 Dec 2024 12:53:05 -0600
Subject: [PATCH 090/238] fix(deps): update dependency openai to v1.57.4
(#4721)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
poetry.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 073fd65cf..d25e25256 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1584,13 +1584,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "openai"
-version = "1.57.3"
+version = "1.57.4"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.8"
files = [
- {file = "openai-1.57.3-py3-none-any.whl", hash = "sha256:c4034a5676eb252ef2e0ed1f46d040ca3bdde24bb61b432f50bb0b38d0cf9ecf"},
- {file = "openai-1.57.3.tar.gz", hash = "sha256:2c98ca6532b30d8bc5029974d2fcbd793b650009c2b014f47ffd4f9fdfc1f9eb"},
+ {file = "openai-1.57.4-py3-none-any.whl", hash = "sha256:7def1ab2d52f196357ce31b9cfcf4181529ce00838286426bb35be81c035dafb"},
+ {file = "openai-1.57.4.tar.gz", hash = "sha256:a8f071a3e9198e2818f63aade68e759417b9f62c0971bdb83de82504b70b77f7"},
]
[package.dependencies]
From b3c0661f52c8e843f00a311bf2e335e3f9598fa3 Mon Sep 17 00:00:00 2001
From: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
Date: Sat, 14 Dec 2024 10:25:04 +1100
Subject: [PATCH 091/238] docs: Add October 2024 survey results (#4666)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
---
.../news/surveys/2024-october/Question2.png | Bin 0 -> 103330 bytes
.../news/surveys/2024-october/Question3.png | Bin 0 -> 131150 bytes
.../news/surveys/2024-october/Question4.png | Bin 0 -> 172208 bytes
.../news/surveys/2024-october/Question5.png | Bin 0 -> 111453 bytes
.../news/surveys/2024-october/Question6.png | Bin 0 -> 92520 bytes
.../news/surveys/2024-october/Question7.png | Bin 0 -> 138317 bytes
.../news/surveys/2024-october/Question8.png | Bin 0 -> 242083 bytes
.../news/surveys/2024-october/overview.md | 126 ++++++
docs/docs/news/surveys/2024-october/q10.md | 227 ++++++++++
docs/docs/news/surveys/2024-october/q11.md | 141 +++++++
docs/docs/news/surveys/2024-october/q12.md | 283 +++++++++++++
docs/docs/news/surveys/2024-october/q13.md | 391 ++++++++++++++++++
docs/docs/news/surveys/2024-october/q9.md | 319 ++++++++++++++
docs/docs/overrides/api.html | 2 +-
docs/mkdocs.yml | 4 +
15 files changed, 1492 insertions(+), 1 deletion(-)
create mode 100644 docs/docs/news/surveys/2024-october/Question2.png
create mode 100644 docs/docs/news/surveys/2024-october/Question3.png
create mode 100644 docs/docs/news/surveys/2024-october/Question4.png
create mode 100644 docs/docs/news/surveys/2024-october/Question5.png
create mode 100644 docs/docs/news/surveys/2024-october/Question6.png
create mode 100644 docs/docs/news/surveys/2024-october/Question7.png
create mode 100644 docs/docs/news/surveys/2024-october/Question8.png
create mode 100644 docs/docs/news/surveys/2024-october/overview.md
create mode 100644 docs/docs/news/surveys/2024-october/q10.md
create mode 100644 docs/docs/news/surveys/2024-october/q11.md
create mode 100644 docs/docs/news/surveys/2024-october/q12.md
create mode 100644 docs/docs/news/surveys/2024-october/q13.md
create mode 100644 docs/docs/news/surveys/2024-october/q9.md
diff --git a/docs/docs/news/surveys/2024-october/Question2.png b/docs/docs/news/surveys/2024-october/Question2.png
new file mode 100644
index 0000000000000000000000000000000000000000..c68aef5727f5777a37051b4e62c86d65b8835f57
GIT binary patch
literal 103330
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