mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-03-06 21:53:12 -05:00
Merge branch 'mealie-next' into fix/translation-issues-when-scraping
This commit is contained in:
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
import fastapi
|
||||
from fastapi import Depends, HTTPException, Request, status
|
||||
from fastapi import BackgroundTasks, Depends, HTTPException, Request, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from jose import JWTError, jwt
|
||||
from sqlalchemy.orm.session import Session
|
||||
@@ -215,14 +215,14 @@ async def temporary_zip_path() -> AsyncGenerator[Path, None]:
|
||||
temp_path.unlink(missing_ok=True)
|
||||
|
||||
|
||||
async def temporary_dir() -> AsyncGenerator[Path, None]:
|
||||
async def temporary_dir(background_tasks: BackgroundTasks) -> AsyncGenerator[Path, None]:
|
||||
temp_path = app_dirs.TEMP_DIR.joinpath(uuid4().hex)
|
||||
temp_path.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
try:
|
||||
yield temp_path
|
||||
finally:
|
||||
shutil.rmtree(temp_path)
|
||||
background_tasks.add_task(shutil.rmtree, temp_path)
|
||||
|
||||
|
||||
def temporary_file(ext: str = "") -> Callable[[], Generator[tempfile._TemporaryFileWrapper, None, None]]:
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
from functools import lru_cache
|
||||
from typing import Protocol
|
||||
|
||||
from passlib.context import CryptContext
|
||||
import bcrypt
|
||||
|
||||
from mealie.core.config import get_app_settings
|
||||
|
||||
|
||||
class Hasher(Protocol):
|
||||
def hash(self, password: str) -> str:
|
||||
...
|
||||
def hash(self, password: str) -> str: ...
|
||||
|
||||
def verify(self, password: str, hashed: str) -> bool:
|
||||
...
|
||||
def verify(self, password: str, hashed: str) -> bool: ...
|
||||
|
||||
|
||||
class FakeHasher:
|
||||
@@ -22,15 +20,16 @@ class FakeHasher:
|
||||
return password == hashed
|
||||
|
||||
|
||||
class PasslibHasher:
|
||||
def __init__(self) -> None:
|
||||
self.ctx = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
class BcryptHasher:
|
||||
def hash(self, password: str) -> str:
|
||||
return self.ctx.hash(password)
|
||||
password_bytes = password.encode("utf-8")
|
||||
hashed = bcrypt.hashpw(password_bytes, bcrypt.gensalt())
|
||||
return hashed.decode("utf-8")
|
||||
|
||||
def verify(self, password: str, hashed: str) -> bool:
|
||||
return self.ctx.verify(password, hashed)
|
||||
password_bytes = password.encode("utf-8")
|
||||
hashed_bytes = hashed.encode("utf-8")
|
||||
return bcrypt.checkpw(password_bytes, hashed_bytes)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
@@ -40,4 +39,4 @@ def get_hasher() -> Hasher:
|
||||
if settings.TESTING:
|
||||
return FakeHasher()
|
||||
|
||||
return PasslibHasher()
|
||||
return BcryptHasher()
|
||||
|
||||
@@ -18,8 +18,7 @@ ALGORITHM = "HS256"
|
||||
logger = root_logger.get_logger("security")
|
||||
|
||||
|
||||
class UserLockedOut(Exception):
|
||||
...
|
||||
class UserLockedOut(Exception): ...
|
||||
|
||||
|
||||
def create_access_token(data: dict, expires_delta: timedelta | None = None) -> str:
|
||||
|
||||
@@ -7,13 +7,11 @@ from pydantic import BaseModel, BaseSettings, PostgresDsn
|
||||
class AbstractDBProvider(ABC):
|
||||
@property
|
||||
@abstractmethod
|
||||
def db_url(self) -> str:
|
||||
...
|
||||
def db_url(self) -> str: ...
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def db_url_public(self) -> str:
|
||||
...
|
||||
def db_url_public(self) -> str: ...
|
||||
|
||||
|
||||
class SQLiteProvider(AbstractDBProvider, BaseModel):
|
||||
|
||||
@@ -31,5 +31,4 @@ class GroupWebhooksModel(SqlAlchemyBase, BaseMixins):
|
||||
time: Mapped[str | None] = mapped_column(String, default="00:00")
|
||||
|
||||
@auto_init()
|
||||
def __init__(self, **_) -> None:
|
||||
...
|
||||
def __init__(self, **_) -> None: ...
|
||||
|
||||
35
mealie/lang/messages/is-IS.json
Normal file
35
mealie/lang/messages/is-IS.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"generic": {
|
||||
"server-error": "An unexpected error occurred"
|
||||
},
|
||||
"recipe": {
|
||||
"unique-name-error": "Recipe names must be unique"
|
||||
},
|
||||
"mealplan": {
|
||||
"no-recipes-match-your-rules": "No recipes match your rules"
|
||||
},
|
||||
"user": {
|
||||
"user-updated": "User updated",
|
||||
"password-updated": "Password updated",
|
||||
"invalid-current-password": "Invalid current password",
|
||||
"ldap-update-password-unavailable": "Unable to update password, user is controlled by LDAP"
|
||||
},
|
||||
"group": {
|
||||
"report-deleted": "Report deleted."
|
||||
},
|
||||
"exceptions": {
|
||||
"permission_denied": "You do not have permission to perform this action",
|
||||
"no-entry-found": "The requested resource was not found",
|
||||
"integrity-error": "Database integrity error",
|
||||
"username-conflict-error": "This username is already taken",
|
||||
"email-conflict-error": "This email is already in use"
|
||||
},
|
||||
"notifications": {
|
||||
"generic-created": "{name} was created",
|
||||
"generic-updated": "{name} was updated",
|
||||
"generic-created-with-url": "{name} has been created, {url}",
|
||||
"generic-updated-with-url": "{name} has been updated, {url}",
|
||||
"generic-duplicated": "{name} has been duplicated",
|
||||
"generic-deleted": "{name} has been deleted"
|
||||
}
|
||||
}
|
||||
@@ -6,16 +6,16 @@
|
||||
"unique-name-error": "Ime recepta mora biti unikatno"
|
||||
},
|
||||
"mealplan": {
|
||||
"no-recipes-match-your-rules": "No recipes match your rules"
|
||||
"no-recipes-match-your-rules": "Noben recept ne ustreza vašim pogojem"
|
||||
},
|
||||
"user": {
|
||||
"user-updated": "User updated",
|
||||
"password-updated": "Password updated",
|
||||
"invalid-current-password": "Invalid current password",
|
||||
"ldap-update-password-unavailable": "Unable to update password, user is controlled by LDAP"
|
||||
"user-updated": "Uporabnik posodobljen",
|
||||
"password-updated": "Geslo posodobljeno",
|
||||
"invalid-current-password": "Neveljavno trenutno geslo",
|
||||
"ldap-update-password-unavailable": "Gesla ni mogoče posodobiti, uporabnik je nadzorovan preko LDAP"
|
||||
},
|
||||
"group": {
|
||||
"report-deleted": "Report deleted."
|
||||
"report-deleted": "Poročilo izbrisano."
|
||||
},
|
||||
"exceptions": {
|
||||
"permission_denied": "Nimate dovoljenja za izvedbo zahtevanega dejanja",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"user": {
|
||||
"user-updated": "User updated",
|
||||
"password-updated": "Password updated",
|
||||
"invalid-current-password": "Invalid current password",
|
||||
"invalid-current-password": "目前密碼無效",
|
||||
"ldap-update-password-unavailable": "Unable to update password, user is controlled by LDAP"
|
||||
},
|
||||
"group": {
|
||||
@@ -22,7 +22,7 @@
|
||||
"no-entry-found": "The requested resource was not found",
|
||||
"integrity-error": "Database integrity error",
|
||||
"username-conflict-error": "This username is already taken",
|
||||
"email-conflict-error": "This email is already in use"
|
||||
"email-conflict-error": "該電子郵件已被使用"
|
||||
},
|
||||
"notifications": {
|
||||
"generic-created": "{name} was created",
|
||||
|
||||
@@ -3,5 +3,4 @@ The img package is a collection of utilities for working with images. While it o
|
||||
within the img package should not be tightly coupled to Mealie.
|
||||
"""
|
||||
|
||||
|
||||
from .minify import *
|
||||
|
||||
@@ -46,8 +46,7 @@ class ABCMinifier(ABC):
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def minify(self, image: Path, force=True):
|
||||
...
|
||||
def minify(self, image: Path, force=True): ...
|
||||
|
||||
def purge(self, image: Path):
|
||||
if not self._purge:
|
||||
|
||||
@@ -14,7 +14,7 @@ from mealie.db.models.recipe.tag import Tag
|
||||
from mealie.db.models.recipe.tool import Tool
|
||||
from mealie.db.models.users.users import User
|
||||
from mealie.schema.group.group_statistics import GroupStatistics
|
||||
from mealie.schema.user.user import GroupBase, GroupInDB
|
||||
from mealie.schema.user.user import GroupBase, GroupInDB, UpdateGroup
|
||||
|
||||
from ..db.models._model_base import SqlAlchemyBase
|
||||
from .repository_generic import RepositoryGeneric
|
||||
@@ -45,6 +45,18 @@ class RepositoryGroup(RepositoryGeneric[GroupInDB, Group]):
|
||||
# since create uses special logic for resolving slugs, we don't want to use the standard create_many method
|
||||
return [self.create(new_group) for new_group in data]
|
||||
|
||||
def update(self, match_value: str | int | UUID4, new_data: UpdateGroup | dict) -> GroupInDB:
|
||||
if isinstance(new_data, GroupBase):
|
||||
new_data.slug = slugify(new_data.name)
|
||||
else:
|
||||
new_data["slug"] = slugify(new_data["name"])
|
||||
|
||||
return super().update(match_value, new_data)
|
||||
|
||||
def update_many(self, data: Iterable[UpdateGroup | dict]) -> list[GroupInDB]:
|
||||
# since update uses special logic for resolving slugs, we don't want to use the standard update_many method
|
||||
return [self.update(group["id"] if isinstance(group, dict) else group.id, group) for group in data]
|
||||
|
||||
def get_by_name(self, name: str) -> GroupInDB | None:
|
||||
dbgroup = self.session.execute(select(self.model).filter_by(name=name)).scalars().one_or_none()
|
||||
if dbgroup is None:
|
||||
|
||||
@@ -53,7 +53,7 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]):
|
||||
stmt = (
|
||||
select(self.model)
|
||||
.join(RecipeSettings)
|
||||
.filter(RecipeSettings.public == True) # noqa: 711
|
||||
.filter(RecipeSettings.public == True) # noqa: E712
|
||||
.order_by(order_attr.desc())
|
||||
.offset(start)
|
||||
.limit(limit)
|
||||
@@ -63,7 +63,7 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]):
|
||||
stmt = (
|
||||
select(self.model)
|
||||
.join(RecipeSettings)
|
||||
.filter(RecipeSettings.public == True) # noqa: 711
|
||||
.filter(RecipeSettings.public == True) # noqa: E712
|
||||
.offset(start)
|
||||
.limit(limit)
|
||||
)
|
||||
|
||||
@@ -25,5 +25,4 @@ class AbstractSeeder(ABC):
|
||||
self.resources = Path(__file__).parent / "resources"
|
||||
|
||||
@abstractmethod
|
||||
def seed(self, locale: str | None = None) -> None:
|
||||
...
|
||||
def seed(self, locale: str | None = None) -> None: ...
|
||||
|
||||
222
mealie/repos/seed/resources/foods/locales/is-IS.json
Normal file
222
mealie/repos/seed/resources/foods/locales/is-IS.json
Normal file
@@ -0,0 +1,222 @@
|
||||
{
|
||||
"acorn-squash": "acorn squash",
|
||||
"alfalfa-sprouts": "alfalfa sprouts",
|
||||
"anchovies": "anchovies",
|
||||
"apples": "apples",
|
||||
"artichoke": "artichoke",
|
||||
"arugula": "arugula",
|
||||
"asparagus": "asparagus",
|
||||
"aubergine": "aubergine",
|
||||
"avocado": "avocado",
|
||||
"bacon": "bacon",
|
||||
"baking-powder": "baking powder",
|
||||
"baking-soda": "baking soda",
|
||||
"baking-sugar": "baking sugar",
|
||||
"bar-sugar": "bar sugar",
|
||||
"basil": "basil",
|
||||
"bell-peppers": "bell peppers",
|
||||
"blackberries": "blackberries",
|
||||
"brassicas": "brassicas",
|
||||
"bok-choy": "bok choy",
|
||||
"broccoflower": "broccoflower",
|
||||
"broccoli": "broccoli",
|
||||
"broccolini": "broccolini",
|
||||
"broccoli-rabe": "broccoli rabe",
|
||||
"brussels-sprouts": "brussels sprouts",
|
||||
"cabbage": "cabbage",
|
||||
"cauliflower": "cauliflower",
|
||||
"chinese-leaves": "chinese leaves",
|
||||
"collard-greens": "collard greens",
|
||||
"kohlrabi": "kohlrabi",
|
||||
"bread": "bread",
|
||||
"breadfruit": "breadfruit",
|
||||
"broad-beans": "broad beans",
|
||||
"brown-sugar": "brown sugar",
|
||||
"butter": "butter",
|
||||
"butternut-pumpkin": "butternut pumpkin",
|
||||
"butternut-squash": "butternut squash",
|
||||
"cactus-edible": "cactus, edible",
|
||||
"calabrese": "calabrese",
|
||||
"cannabis": "cannabis",
|
||||
"capsicum": "capsicum",
|
||||
"caraway": "caraway",
|
||||
"carrot": "carrot",
|
||||
"castor-sugar": "castor sugar",
|
||||
"cayenne-pepper": "cayenne pepper",
|
||||
"celeriac": "celeriac",
|
||||
"celery": "celery",
|
||||
"cereal-grains": "cereal grains",
|
||||
"rice": "rice",
|
||||
"chard": "chard",
|
||||
"cheese": "cheese",
|
||||
"chicory": "chicory",
|
||||
"chilli-peppers": "chilli peppers",
|
||||
"chives": "chives",
|
||||
"chocolate": "chocolate",
|
||||
"cilantro": "cilantro",
|
||||
"cinnamon": "cinnamon",
|
||||
"clarified-butter": "clarified butter",
|
||||
"coconut": "coconut",
|
||||
"coconut-milk": "coconut milk",
|
||||
"coffee": "coffee",
|
||||
"confectioners-sugar": "confectioners' sugar",
|
||||
"coriander": "coriander",
|
||||
"corn": "corn",
|
||||
"corn-syrup": "corn syrup",
|
||||
"cottonseed-oil": "cottonseed oil",
|
||||
"courgette": "courgette",
|
||||
"cream-of-tartar": "cream of tartar",
|
||||
"cucumber": "cucumber",
|
||||
"cumin": "cumin",
|
||||
"daikon": "daikon",
|
||||
"dairy-products-and-dairy-substitutes": "dairy products and dairy substitutes",
|
||||
"eggs": "eggs",
|
||||
"ghee": "ghee",
|
||||
"milk": "milk",
|
||||
"dandelion": "dandelion",
|
||||
"demerara-sugar": "demerara sugar",
|
||||
"dough": "dough",
|
||||
"edible-cactus": "edible cactus",
|
||||
"eggplant": "eggplant",
|
||||
"endive": "endive",
|
||||
"fats": "fats",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fish": "fish",
|
||||
"catfish": "catfish ",
|
||||
"cod": "cod",
|
||||
"salt-cod": "salt cod",
|
||||
"salmon": "salmon",
|
||||
"skate": "skate",
|
||||
"stockfish": "stockfish",
|
||||
"trout": "trout",
|
||||
"tuna": "tuna",
|
||||
"five-spice-powder": "five spice powder",
|
||||
"flour": "flour",
|
||||
"frisee": "frisee",
|
||||
"fructose": "fructose",
|
||||
"fruit": "fruit",
|
||||
"apple": "apple",
|
||||
"oranges": "oranges",
|
||||
"pear": "pear",
|
||||
"tomato": "tomato ",
|
||||
"fruit-sugar": "fruit sugar",
|
||||
"garam-masala": "garam masala",
|
||||
"garlic": "garlic",
|
||||
"gem-squash": "gem squash",
|
||||
"ginger": "ginger",
|
||||
"giblets": "giblets",
|
||||
"grains": "grains",
|
||||
"maize": "maize",
|
||||
"sweetcorn": "sweetcorn",
|
||||
"teff": "teff",
|
||||
"grape-seed-oil": "grape seed oil",
|
||||
"green-onion": "green onion",
|
||||
"heart-of-palm": "heart of palm",
|
||||
"hemp": "hemp",
|
||||
"herbs": "herbs",
|
||||
"oregano": "oregano",
|
||||
"parsley": "parsley",
|
||||
"honey": "honey",
|
||||
"icing-sugar": "icing sugar",
|
||||
"isomalt": "isomalt",
|
||||
"jackfruit": "jackfruit",
|
||||
"jaggery": "jaggery",
|
||||
"jams": "jams",
|
||||
"jellies": "jellies",
|
||||
"jerusalem-artichoke": "jerusalem artichoke",
|
||||
"jicama": "jicama",
|
||||
"kale": "kale",
|
||||
"kumara": "kumara",
|
||||
"leavening-agents": "leavening agents",
|
||||
"leek": "leek",
|
||||
"legumes": "legumes ",
|
||||
"peas": "peas",
|
||||
"beans": "beans",
|
||||
"lentils": "lentils",
|
||||
"lemongrass": "lemongrass",
|
||||
"lettuce": "lettuce",
|
||||
"liver": "liver",
|
||||
"maple-syrup": "maple syrup",
|
||||
"meat": "meat",
|
||||
"mortadella": "mortadella",
|
||||
"mushroom": "mushroom",
|
||||
"white-mushroom": "white mushroom",
|
||||
"mussels": "mussels",
|
||||
"nori": "nori",
|
||||
"nutmeg": "nutmeg",
|
||||
"nutritional-yeast-flakes": "nutritional yeast flakes",
|
||||
"nuts": "nuts",
|
||||
"nanaimo-bar-mix": "nanaimo bar mix",
|
||||
"octopuses": "octopuses",
|
||||
"oils": "oils",
|
||||
"olive-oil": "olive oil",
|
||||
"okra": "okra",
|
||||
"olive": "olive",
|
||||
"onion-family": "onion family",
|
||||
"onion": "onion",
|
||||
"scallion": "scallion",
|
||||
"shallot": "shallot",
|
||||
"spring-onion": "spring onion",
|
||||
"orange-blossom-water": "orange blossom water",
|
||||
"oysters": "oysters",
|
||||
"panch-puran": "panch puran",
|
||||
"paprika": "paprika",
|
||||
"parsnip": "parsnip",
|
||||
"pepper": "pepper",
|
||||
"peppers": "peppers",
|
||||
"plantain": "plantain",
|
||||
"pineapple": "pineapple",
|
||||
"poppy-seeds": "poppy seeds",
|
||||
"potatoes": "potatoes",
|
||||
"poultry": "poultry",
|
||||
"powdered-sugar": "powdered sugar",
|
||||
"pumpkin": "pumpkin",
|
||||
"pumpkin-seeds": "pumpkin seeds",
|
||||
"radish": "radish",
|
||||
"raw-sugar": "raw sugar",
|
||||
"refined-sugar": "refined sugar",
|
||||
"rice-flour": "rice flour",
|
||||
"rock-sugar": "rock sugar",
|
||||
"rum": "rum",
|
||||
"salt": "salt",
|
||||
"seafood": "seafood",
|
||||
"seeds": "seeds",
|
||||
"sesame-seeds": "sesame seeds",
|
||||
"sunflower-seeds": "sunflower seeds",
|
||||
"soda": "soda",
|
||||
"soda-baking": "soda, baking",
|
||||
"soybean": "soybean",
|
||||
"spaghetti-squash": "spaghetti squash",
|
||||
"spices": "spices",
|
||||
"spinach": "spinach",
|
||||
"squash-family": "squash family",
|
||||
"squash": "squash",
|
||||
"zucchini": "zucchini",
|
||||
"sugar": "sugar",
|
||||
"caster-sugar": "caster sugar",
|
||||
"granulated-sugar": "granulated sugar",
|
||||
"superfine-sugar": "superfine sugar",
|
||||
"turbanado-sugar": "turbanado sugar",
|
||||
"unrefined-sugar": "unrefined sugar",
|
||||
"white-sugar": "white sugar",
|
||||
"sweet-potato": "sweet potato",
|
||||
"sweeteners": "sweeteners",
|
||||
"cane-sugar": "cane sugar",
|
||||
"tahini": "tahini",
|
||||
"tubers": "tubers",
|
||||
"potato": "potato",
|
||||
"sunchoke": "sunchoke",
|
||||
"taro": "taro",
|
||||
"yam": "yam",
|
||||
"turnip": "turnip",
|
||||
"vanilla": "vanilla",
|
||||
"vegetables": "vegetables",
|
||||
"fiddlehead-fern": "fiddlehead fern",
|
||||
"ful": "ful",
|
||||
"watercress": "watercress",
|
||||
"watermelon": "watermelon",
|
||||
"xanthan-gum": "xanthan gum",
|
||||
"yeast": "yeast"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"acorn-squash": "acorn squash",
|
||||
"alfalfa-sprouts": "alfalfa sprouts",
|
||||
"acorn-squash": "meşe palamudu kabağı",
|
||||
"alfalfa-sprouts": "yonca filizi",
|
||||
"anchovies": "hamsi",
|
||||
"apples": "elma",
|
||||
"artichoke": "enginar",
|
||||
@@ -10,14 +10,14 @@
|
||||
"avocado": "avokado",
|
||||
"bacon": "domuz pastırması",
|
||||
"baking-powder": "kabartma tozu",
|
||||
"baking-soda": "baking soda",
|
||||
"baking-soda": "karbonat",
|
||||
"baking-sugar": "baking sugar",
|
||||
"bar-sugar": "kesme şeker",
|
||||
"basil": "fesleğen",
|
||||
"bell-peppers": "kırmızı biber",
|
||||
"blackberries": "blackberries",
|
||||
"blackberries": "böğürtlen",
|
||||
"brassicas": "brassicas",
|
||||
"bok-choy": "bok choy",
|
||||
"bok-choy": "çin lahanası",
|
||||
"broccoflower": "broccoflower",
|
||||
"broccoli": "brokoli",
|
||||
"broccolini": "broccolini",
|
||||
@@ -27,7 +27,7 @@
|
||||
"cauliflower": "karnabahar",
|
||||
"chinese-leaves": "chinese leaves",
|
||||
"collard-greens": "collard greens",
|
||||
"kohlrabi": "kohlrabi",
|
||||
"kohlrabi": "alabaş",
|
||||
"bread": "ekmek",
|
||||
"breadfruit": "breadfruit",
|
||||
"broad-beans": "bakla",
|
||||
@@ -35,64 +35,64 @@
|
||||
"butter": "tereyağı",
|
||||
"butternut-pumpkin": "butternut pumpkin",
|
||||
"butternut-squash": "butternut squash",
|
||||
"cactus-edible": "cactus, edible",
|
||||
"cactus-edible": "yenilebilir kaktüs",
|
||||
"calabrese": "calabrese",
|
||||
"cannabis": "kenevir",
|
||||
"capsicum": "capsicum",
|
||||
"caraway": "caraway",
|
||||
"capsicum": "kırmızı biber",
|
||||
"caraway": "kimyon",
|
||||
"carrot": "havuç",
|
||||
"castor-sugar": "castor sugar",
|
||||
"cayenne-pepper": "cayenne pepper",
|
||||
"cayenne-pepper": "kırmızı biber",
|
||||
"celeriac": "kereviz",
|
||||
"celery": "kereviz",
|
||||
"cereal-grains": "cereal grains",
|
||||
"cereal-grains": "tam taneli tahıl",
|
||||
"rice": "pirinç",
|
||||
"chard": "chard",
|
||||
"chard": "pazı",
|
||||
"cheese": "peynir",
|
||||
"chicory": "chicory",
|
||||
"chilli-peppers": "chilli peppers",
|
||||
"chives": "chives",
|
||||
"chicory": "hindiba",
|
||||
"chilli-peppers": "acı biber",
|
||||
"chives": "frenk soğanı",
|
||||
"chocolate": "çikolata",
|
||||
"cilantro": "cilantro",
|
||||
"cilantro": "kişniş",
|
||||
"cinnamon": "tarçın",
|
||||
"clarified-butter": "clarified butter",
|
||||
"clarified-butter": "sade yağ",
|
||||
"coconut": "hindistan cevizi",
|
||||
"coconut-milk": "coconut milk",
|
||||
"coconut-milk": "hindistan cevizi sütü",
|
||||
"coffee": "kahve",
|
||||
"confectioners-sugar": "confectioners' sugar",
|
||||
"coriander": "coriander",
|
||||
"corn": "corn",
|
||||
"corn-syrup": "corn syrup",
|
||||
"cottonseed-oil": "cottonseed oil",
|
||||
"courgette": "courgette",
|
||||
"cream-of-tartar": "cream of tartar",
|
||||
"confectioners-sugar": "pudra şekeri",
|
||||
"coriander": "kişniş",
|
||||
"corn": "mısır",
|
||||
"corn-syrup": "mısır şurubu",
|
||||
"cottonseed-oil": "pamuk yağı",
|
||||
"courgette": "dolmalık kabak",
|
||||
"cream-of-tartar": "krem tartar",
|
||||
"cucumber": "salatalık",
|
||||
"cumin": "cumin",
|
||||
"daikon": "daikon",
|
||||
"dairy-products-and-dairy-substitutes": "dairy products and dairy substitutes",
|
||||
"cumin": "kimyon",
|
||||
"daikon": "beyaz turp",
|
||||
"dairy-products-and-dairy-substitutes": "süt ürünleri ve süt yerine geçen ürünler",
|
||||
"eggs": "yumurta",
|
||||
"ghee": "ghee",
|
||||
"ghee": "saf yağ",
|
||||
"milk": "süt",
|
||||
"dandelion": "dandelion",
|
||||
"demerara-sugar": "demerara sugar",
|
||||
"dandelion": "karahindiba",
|
||||
"demerara-sugar": "esmer şeker",
|
||||
"dough": "hamur",
|
||||
"edible-cactus": "edible cactus",
|
||||
"edible-cactus": "yenilebilir kaktüs",
|
||||
"eggplant": "patlıcan",
|
||||
"endive": "hindiba",
|
||||
"fats": "yağlar",
|
||||
"speck": "speck",
|
||||
"fava-beans": "fava beans",
|
||||
"fiddlehead": "fiddlehead",
|
||||
"fava-beans": "fava fasulyesi",
|
||||
"fiddlehead": "eğrelti otu filizi",
|
||||
"fish": "balık",
|
||||
"catfish": "kedibalığı ",
|
||||
"cod": "cod",
|
||||
"salt-cod": "salt cod",
|
||||
"cod": "morina",
|
||||
"salt-cod": "tuzlu morina",
|
||||
"salmon": "somon",
|
||||
"skate": "skate",
|
||||
"stockfish": "stockfish",
|
||||
"skate": "çemçe balığı",
|
||||
"stockfish": "kurutulmuş balık",
|
||||
"trout": "alabalık",
|
||||
"tuna": "ton balığı",
|
||||
"five-spice-powder": "five spice powder",
|
||||
"five-spice-powder": "beşli baharat",
|
||||
"flour": "un",
|
||||
"frisee": "frisee",
|
||||
"fructose": "fruktoz",
|
||||
@@ -101,7 +101,7 @@
|
||||
"oranges": "portakal",
|
||||
"pear": "armut",
|
||||
"tomato": "domates ",
|
||||
"fruit-sugar": "fruit sugar",
|
||||
"fruit-sugar": "meyve şekeri",
|
||||
"garam-masala": "garam masala",
|
||||
"garlic": "sarımsak",
|
||||
"gem-squash": "gem squash",
|
||||
@@ -109,7 +109,7 @@
|
||||
"giblets": "giblets",
|
||||
"grains": "tahıllar",
|
||||
"maize": "maize",
|
||||
"sweetcorn": "sweetcorn",
|
||||
"sweetcorn": "tatlı mısır",
|
||||
"teff": "teff",
|
||||
"grape-seed-oil": "üzüm çekirdeği yağı",
|
||||
"green-onion": "taze soğan",
|
||||
@@ -125,69 +125,69 @@
|
||||
"jaggery": "jaggery",
|
||||
"jams": "reçel",
|
||||
"jellies": "jellies",
|
||||
"jerusalem-artichoke": "jerusalem artichoke",
|
||||
"jicama": "jicama",
|
||||
"jerusalem-artichoke": "yerelması",
|
||||
"jicama": "meksika turpu",
|
||||
"kale": "kale",
|
||||
"kumara": "kumara",
|
||||
"kumara": "tatlı patates",
|
||||
"leavening-agents": "leavening agents",
|
||||
"leek": "leek",
|
||||
"legumes": "legumes ",
|
||||
"peas": "peas",
|
||||
"beans": "beans",
|
||||
"lentils": "lentils",
|
||||
"lemongrass": "lemongrass",
|
||||
"lettuce": "lettuce",
|
||||
"liver": "liver",
|
||||
"maple-syrup": "maple syrup",
|
||||
"meat": "meat",
|
||||
"legumes": "baklagiller ",
|
||||
"peas": "bezelye",
|
||||
"beans": "fasulye",
|
||||
"lentils": "mercimek",
|
||||
"lemongrass": "limon otu",
|
||||
"lettuce": "marul",
|
||||
"liver": "karaciğer",
|
||||
"maple-syrup": "akçaağaç şurubu",
|
||||
"meat": "et",
|
||||
"mortadella": "mortadella",
|
||||
"mushroom": "mushroom",
|
||||
"white-mushroom": "white mushroom",
|
||||
"mussels": "mussels",
|
||||
"mushroom": "mantar",
|
||||
"white-mushroom": "beyaz mantar",
|
||||
"mussels": "midye",
|
||||
"nori": "nori",
|
||||
"nutmeg": "nutmeg",
|
||||
"nutmeg": "muskat",
|
||||
"nutritional-yeast-flakes": "nutritional yeast flakes",
|
||||
"nuts": "nuts",
|
||||
"nanaimo-bar-mix": "nanaimo bar mix",
|
||||
"octopuses": "octopuses",
|
||||
"oils": "oils",
|
||||
"octopuses": "ahtapotlar",
|
||||
"oils": "yağ",
|
||||
"olive-oil": "zeytin yağı",
|
||||
"okra": "okra",
|
||||
"okra": "bamya",
|
||||
"olive": "zeytin",
|
||||
"onion-family": "onion family",
|
||||
"onion": "soğan",
|
||||
"scallion": "scallion",
|
||||
"shallot": "shallot",
|
||||
"spring-onion": "spring onion",
|
||||
"scallion": "taze soğan",
|
||||
"shallot": "arpacık soğan",
|
||||
"spring-onion": "yeşil soğan",
|
||||
"orange-blossom-water": "orange blossom water",
|
||||
"oysters": "istiridye",
|
||||
"panch-puran": "panch puran",
|
||||
"paprika": "kırmızı biber",
|
||||
"parsnip": "parsnip",
|
||||
"parsnip": "yaban havucu",
|
||||
"pepper": "biber",
|
||||
"peppers": "biber",
|
||||
"plantain": "plantain",
|
||||
"pineapple": "ananas",
|
||||
"poppy-seeds": "poppy seeds",
|
||||
"poppy-seeds": "haşhaş tohumu",
|
||||
"potatoes": "patates",
|
||||
"poultry": "kümes hayvanları",
|
||||
"powdered-sugar": "powdered sugar",
|
||||
"powdered-sugar": "pudra şekeri",
|
||||
"pumpkin": "balkabağı",
|
||||
"pumpkin-seeds": "kabak çekirdeği",
|
||||
"radish": "turp",
|
||||
"raw-sugar": "kesme şeker",
|
||||
"refined-sugar": "refined sugar",
|
||||
"refined-sugar": "rafine şeker",
|
||||
"rice-flour": "pirinç unu",
|
||||
"rock-sugar": "rock sugar",
|
||||
"rock-sugar": "kide şekeri",
|
||||
"rum": "rom",
|
||||
"salt": "tuz",
|
||||
"seafood": "deniz ürünleri",
|
||||
"seeds": "tohumlar",
|
||||
"sesame-seeds": "sesame seeds",
|
||||
"sesame-seeds": "susam",
|
||||
"sunflower-seeds": "ay çekirdeği",
|
||||
"soda": "soda",
|
||||
"soda-baking": "soda, baking",
|
||||
"soybean": "soybean",
|
||||
"soda-baking": "karbonat",
|
||||
"soybean": "soya fasulyesi",
|
||||
"spaghetti-squash": "spaghetti squash",
|
||||
"spices": "baharatlar",
|
||||
"spinach": "ıspanak",
|
||||
|
||||
65
mealie/repos/seed/resources/labels/locales/is-IS.json
Normal file
65
mealie/repos/seed/resources/labels/locales/is-IS.json
Normal file
@@ -0,0 +1,65 @@
|
||||
[
|
||||
{
|
||||
"name": "Produce"
|
||||
},
|
||||
{
|
||||
"name": "Grains"
|
||||
},
|
||||
{
|
||||
"name": "Fruits"
|
||||
},
|
||||
{
|
||||
"name": "Vegetables"
|
||||
},
|
||||
{
|
||||
"name": "Meat"
|
||||
},
|
||||
{
|
||||
"name": "Seafood"
|
||||
},
|
||||
{
|
||||
"name": "Beverages"
|
||||
},
|
||||
{
|
||||
"name": "Baked Goods"
|
||||
},
|
||||
{
|
||||
"name": "Canned Goods"
|
||||
},
|
||||
{
|
||||
"name": "Condiments"
|
||||
},
|
||||
{
|
||||
"name": "Confectionary"
|
||||
},
|
||||
{
|
||||
"name": "Dairy Products"
|
||||
},
|
||||
{
|
||||
"name": "Frozen Foods"
|
||||
},
|
||||
{
|
||||
"name": "Health Foods"
|
||||
},
|
||||
{
|
||||
"name": "Household"
|
||||
},
|
||||
{
|
||||
"name": "Meat Products"
|
||||
},
|
||||
{
|
||||
"name": "Snacks"
|
||||
},
|
||||
{
|
||||
"name": "Spices"
|
||||
},
|
||||
{
|
||||
"name": "Sweets"
|
||||
},
|
||||
{
|
||||
"name": "Alcohol"
|
||||
},
|
||||
{
|
||||
"name": "Other"
|
||||
}
|
||||
]
|
||||
102
mealie/repos/seed/resources/units/locales/is-IS.json
Normal file
102
mealie/repos/seed/resources/units/locales/is-IS.json
Normal file
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"teaspoon": {
|
||||
"name": "teaspoon",
|
||||
"description": "",
|
||||
"abbreviation": "tsp"
|
||||
},
|
||||
"tablespoon": {
|
||||
"name": "tablespoon",
|
||||
"description": "",
|
||||
"abbreviation": "tbsp"
|
||||
},
|
||||
"cup": {
|
||||
"name": "cup",
|
||||
"description": "",
|
||||
"abbreviation": "cup"
|
||||
},
|
||||
"fluid-ounce": {
|
||||
"name": "fluid ounce",
|
||||
"description": "",
|
||||
"abbreviation": "fl oz"
|
||||
},
|
||||
"pint": {
|
||||
"name": "pint",
|
||||
"description": "",
|
||||
"abbreviation": "pt"
|
||||
},
|
||||
"quart": {
|
||||
"name": "quart",
|
||||
"description": "",
|
||||
"abbreviation": "qt"
|
||||
},
|
||||
"gallon": {
|
||||
"name": "gallon",
|
||||
"description": "",
|
||||
"abbreviation": "gal"
|
||||
},
|
||||
"milliliter": {
|
||||
"name": "milliliter",
|
||||
"description": "",
|
||||
"abbreviation": "ml"
|
||||
},
|
||||
"liter": {
|
||||
"name": "liter",
|
||||
"description": "",
|
||||
"abbreviation": "l"
|
||||
},
|
||||
"pound": {
|
||||
"name": "pound",
|
||||
"description": "",
|
||||
"abbreviation": "lb"
|
||||
},
|
||||
"ounce": {
|
||||
"name": "ounce",
|
||||
"description": "",
|
||||
"abbreviation": "oz"
|
||||
},
|
||||
"gram": {
|
||||
"name": "gram",
|
||||
"description": "",
|
||||
"abbreviation": "g"
|
||||
},
|
||||
"kilogram": {
|
||||
"name": "kilogram",
|
||||
"description": "",
|
||||
"abbreviation": "kg"
|
||||
},
|
||||
"milligram": {
|
||||
"name": "milligram",
|
||||
"description": "",
|
||||
"abbreviation": "mg"
|
||||
},
|
||||
"splash": {
|
||||
"name": "splash",
|
||||
"description": "",
|
||||
"abbreviation": ""
|
||||
},
|
||||
"dash": {
|
||||
"name": "dash",
|
||||
"description": "",
|
||||
"abbreviation": ""
|
||||
},
|
||||
"serving": {
|
||||
"name": "serving",
|
||||
"description": "",
|
||||
"abbreviation": ""
|
||||
},
|
||||
"head": {
|
||||
"name": "head",
|
||||
"description": "",
|
||||
"abbreviation": ""
|
||||
},
|
||||
"clove": {
|
||||
"name": "clove",
|
||||
"description": "",
|
||||
"abbreviation": ""
|
||||
},
|
||||
"can": {
|
||||
"name": "can",
|
||||
"description": "",
|
||||
"abbreviation": ""
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
"cup": {
|
||||
"name": "cup",
|
||||
"description": "",
|
||||
"abbreviation": "cup"
|
||||
"abbreviation": "cană"
|
||||
},
|
||||
"fluid-ounce": {
|
||||
"name": "fluid ounce",
|
||||
@@ -20,7 +20,7 @@
|
||||
"abbreviation": "fl oz"
|
||||
},
|
||||
"pint": {
|
||||
"name": "pint",
|
||||
"name": "halbă",
|
||||
"description": "",
|
||||
"abbreviation": "pt"
|
||||
},
|
||||
@@ -30,12 +30,12 @@
|
||||
"abbreviation": "qt"
|
||||
},
|
||||
"gallon": {
|
||||
"name": "gallon",
|
||||
"name": "galon",
|
||||
"description": "",
|
||||
"abbreviation": "gal"
|
||||
},
|
||||
"milliliter": {
|
||||
"name": "milliliter",
|
||||
"name": "mililitru",
|
||||
"description": "",
|
||||
"abbreviation": "ml"
|
||||
},
|
||||
@@ -45,19 +45,19 @@
|
||||
"abbreviation": "l"
|
||||
},
|
||||
"pound": {
|
||||
"name": "pound",
|
||||
"name": "livră",
|
||||
"description": "",
|
||||
"abbreviation": "lb"
|
||||
},
|
||||
"ounce": {
|
||||
"name": "ounce",
|
||||
"name": "uncie",
|
||||
"description": "",
|
||||
"abbreviation": "oz"
|
||||
"abbreviation": "uncii"
|
||||
},
|
||||
"gram": {
|
||||
"name": "gram",
|
||||
"description": "",
|
||||
"abbreviation": "g"
|
||||
"abbreviation": "h"
|
||||
},
|
||||
"kilogram": {
|
||||
"name": "kilogram",
|
||||
@@ -65,7 +65,7 @@
|
||||
"abbreviation": "kg"
|
||||
},
|
||||
"milligram": {
|
||||
"name": "milligram",
|
||||
"name": "miligram",
|
||||
"description": "",
|
||||
"abbreviation": "mg"
|
||||
},
|
||||
@@ -95,7 +95,7 @@
|
||||
"abbreviation": ""
|
||||
},
|
||||
"can": {
|
||||
"name": "can",
|
||||
"name": "cutie",
|
||||
"description": "",
|
||||
"abbreviation": ""
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ This file contains code taken from fastapi-utils project. The code is licensed u
|
||||
|
||||
See their repository for details -> https://github.com/dmontagu/fastapi-utils
|
||||
"""
|
||||
|
||||
import inspect
|
||||
from collections.abc import Callable
|
||||
from typing import Any, TypeVar, cast, get_type_hints
|
||||
|
||||
@@ -34,7 +34,7 @@ class MealieCrudRoute(APIRoute):
|
||||
with contextlib.suppress(JSONDecodeError):
|
||||
response = await original_route_handler(request)
|
||||
response_body = json.loads(response.body)
|
||||
if type(response_body) == dict:
|
||||
if isinstance(response_body, dict):
|
||||
if last_modified := response_body.get("updateAt"):
|
||||
response.headers["last-modified"] = last_modified
|
||||
|
||||
|
||||
@@ -67,7 +67,8 @@ def get_token(
|
||||
if "," in ip: # if there are multiple IPs, the first one is canonically the true client
|
||||
ip = str(ip.split(",")[0])
|
||||
else:
|
||||
ip = request.client.host
|
||||
# request.client should never be null, except sometimes during testing
|
||||
ip = request.client.host if request.client else "unknown"
|
||||
|
||||
try:
|
||||
user = authenticate_user(session, email, password) # type: ignore
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
@@ -14,9 +15,9 @@ def main():
|
||||
r = requests.get(url)
|
||||
|
||||
if r.status_code == 200:
|
||||
exit(0)
|
||||
sys.exit(0)
|
||||
else:
|
||||
exit(1)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -2,10 +2,11 @@ import datetime
|
||||
import uuid
|
||||
from os import path
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import ForeignKeyConstraint, MetaData, create_engine, insert, text
|
||||
from sqlalchemy import ForeignKey, ForeignKeyConstraint, MetaData, Table, create_engine, insert, text
|
||||
from sqlalchemy.engine import base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
@@ -41,13 +42,27 @@ class AlchemyExporter(BaseService):
|
||||
self.session_maker = sessionmaker(bind=self.engine)
|
||||
|
||||
@staticmethod
|
||||
def is_uuid(value: str) -> bool:
|
||||
def is_uuid(value: Any) -> bool:
|
||||
try:
|
||||
uuid.UUID(value)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def is_valid_foreign_key(db_dump: dict[str, list[dict]], fk: ForeignKey, fk_value: Any) -> bool:
|
||||
if not fk_value:
|
||||
return True
|
||||
|
||||
foreign_table_name = fk.column.table.name
|
||||
foreign_field_name = fk.column.name
|
||||
|
||||
for row in db_dump.get(foreign_table_name, []):
|
||||
if row[foreign_field_name] == fk_value:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def convert_types(self, data: dict) -> dict:
|
||||
"""
|
||||
walks the dictionary to restore all things that look like string representations of their complex types
|
||||
@@ -70,6 +85,33 @@ class AlchemyExporter(BaseService):
|
||||
data[key] = self.DateTimeParser(time=value).time
|
||||
return data
|
||||
|
||||
def clean_rows(self, db_dump: dict[str, list[dict]], table: Table, rows: list[dict]) -> list[dict]:
|
||||
"""
|
||||
Checks rows against foreign key restraints and removes any rows that would violate them
|
||||
"""
|
||||
|
||||
fks = table.foreign_keys
|
||||
|
||||
valid_rows = []
|
||||
for row in rows:
|
||||
is_valid_row = True
|
||||
for fk in fks:
|
||||
fk_value = row.get(fk.parent.name)
|
||||
if self.is_valid_foreign_key(db_dump, fk, row.get(fk.parent.name)):
|
||||
continue
|
||||
|
||||
is_valid_row = False
|
||||
self.logger.warning(
|
||||
f"Removing row from table {table.name} because of invalid foreign key {fk.parent.name}: {fk_value}"
|
||||
)
|
||||
self.logger.warning(f"Row: {row}")
|
||||
break
|
||||
|
||||
if is_valid_row:
|
||||
valid_rows.append(row)
|
||||
|
||||
return valid_rows
|
||||
|
||||
def dump_schema(self) -> dict:
|
||||
"""
|
||||
Returns the schema of the SQLAlchemy database as a python dictionary. This dictionary is wrapped by
|
||||
@@ -125,6 +167,7 @@ class AlchemyExporter(BaseService):
|
||||
if not rows:
|
||||
continue
|
||||
table = self.meta.tables[table_name]
|
||||
rows = self.clean_rows(db_dump, table, rows)
|
||||
|
||||
connection.execute(table.delete())
|
||||
connection.execute(insert(table), rows)
|
||||
|
||||
@@ -9,8 +9,7 @@ from mealie.services.backups_v2.alchemy_exporter import AlchemyExporter
|
||||
from mealie.services.backups_v2.backup_file import BackupFile
|
||||
|
||||
|
||||
class BackupSchemaMismatch(Exception):
|
||||
...
|
||||
class BackupSchemaMismatch(Exception): ...
|
||||
|
||||
|
||||
class BackupV2(BaseService):
|
||||
@@ -69,7 +68,7 @@ class BackupV2(BaseService):
|
||||
shutil.copytree(f, self.directories.DATA_DIR / f.name)
|
||||
|
||||
def restore(self, backup_path: Path) -> None:
|
||||
self.logger.info("initially backup restore")
|
||||
self.logger.info("initializing backup restore")
|
||||
|
||||
backup = BackupFile(backup_path)
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@ import typing
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from email import message
|
||||
from email.utils import formatdate
|
||||
from uuid import uuid4
|
||||
|
||||
from html2text import html2text
|
||||
|
||||
from mealie.services._base_service import BaseService
|
||||
|
||||
@@ -36,8 +40,20 @@ class Message:
|
||||
msg["Subject"] = self.subject
|
||||
msg["From"] = f"{self.mail_from_name} <{self.mail_from_address}>"
|
||||
msg["To"] = to
|
||||
msg["Date"] = formatdate(localtime=True)
|
||||
msg.add_alternative(html2text(self.html), subtype="plain")
|
||||
msg.add_alternative(self.html, subtype="html")
|
||||
|
||||
try:
|
||||
message_id = f"<{uuid4()}@{self.mail_from_address.split('@')[1]}>"
|
||||
except IndexError:
|
||||
# this should never happen with a valid email address,
|
||||
# but we let the SMTP server handle it instead of raising it here
|
||||
message_id = f"<{uuid4()}@{self.mail_from_address}>"
|
||||
|
||||
msg["Message-ID"] = message_id
|
||||
msg["MIME-Version"] = "1.0"
|
||||
|
||||
if smtp.ssl:
|
||||
with smtplib.SMTP_SSL(smtp.host, smtp.port) as server:
|
||||
if smtp.username and smtp.password:
|
||||
@@ -57,8 +73,7 @@ class Message:
|
||||
|
||||
class ABCEmailSender(ABC):
|
||||
@abstractmethod
|
||||
def send(self, email_to: str, subject: str, html: str) -> bool:
|
||||
...
|
||||
def send(self, email_to: str, subject: str, html: str) -> bool: ...
|
||||
|
||||
|
||||
class DefaultEmailSender(ABCEmailSender, BaseService):
|
||||
|
||||
@@ -100,9 +100,12 @@ class AppriseEventListener(EventListenerBase):
|
||||
|
||||
return [
|
||||
# We use query params to add custom key: value pairs to the Apprise payload by prepending the key with ":".
|
||||
AppriseEventListener.merge_query_parameters(url, {f":{k}": v for k, v in params.items()})
|
||||
# only certain endpoints support the custom key: value pairs, so we only apply them to those endpoints
|
||||
if AppriseEventListener.is_custom_url(url) else url
|
||||
(
|
||||
AppriseEventListener.merge_query_parameters(url, {f":{k}": v for k, v in params.items()})
|
||||
# only certain endpoints support the custom key: value pairs, so we only apply them to those endpoints
|
||||
if AppriseEventListener.is_custom_url(url)
|
||||
else url
|
||||
)
|
||||
for url in urls
|
||||
]
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@ from mealie.services.event_bus_service.event_types import Event
|
||||
|
||||
|
||||
class PublisherLike(Protocol):
|
||||
def publish(self, event: Event, notification_urls: list[str]):
|
||||
...
|
||||
def publish(self, event: Event, notification_urls: list[str]): ...
|
||||
|
||||
|
||||
class ApprisePublisher:
|
||||
|
||||
@@ -37,12 +37,10 @@ class ABCExporter(BaseService):
|
||||
super().__init__()
|
||||
|
||||
@abstractproperty
|
||||
def destination_dir(self) -> str:
|
||||
...
|
||||
def destination_dir(self) -> str: ...
|
||||
|
||||
@abstractmethod
|
||||
def items(self) -> Iterator[ExportedItem]:
|
||||
...
|
||||
def items(self) -> Iterator[ExportedItem]: ...
|
||||
|
||||
def _post_export_hook(self, _: BaseModel) -> None:
|
||||
pass
|
||||
|
||||
@@ -39,7 +39,7 @@ class MealieAlphaMigrator(BaseMigrator):
|
||||
with contextlib.suppress(KeyError):
|
||||
if "" in recipe["categories"]:
|
||||
recipe["categories"] = [cat for cat in recipe["categories"] if cat != ""]
|
||||
if type(recipe["extras"]) == list:
|
||||
if isinstance(recipe["extras"], list):
|
||||
recipe["extras"] = {}
|
||||
|
||||
recipe["comments"] = []
|
||||
|
||||
@@ -23,8 +23,7 @@ def plantoeat_recipes(file: Path):
|
||||
for name in Path(tmpdir).glob("**/[!.]*.csv"):
|
||||
with open(name, newline="") as csvfile:
|
||||
reader = csv.DictReader(csvfile)
|
||||
for row in reader:
|
||||
yield row
|
||||
yield from reader
|
||||
|
||||
|
||||
def get_value_as_string_or_none(dictionary: dict, key: str):
|
||||
|
||||
@@ -187,7 +187,7 @@ def import_data(lines):
|
||||
token = unclump(token)
|
||||
|
||||
# turn B-NAME/123 back into "name"
|
||||
tag, confidence = re.split(r"/", columns[-1], 1)
|
||||
tag, confidence = re.split(r"/", columns[-1], maxsplit=1)
|
||||
tag = re.sub(r"^[BI]\-", "", tag).lower() # noqa: W605 - invalid dscape sequence
|
||||
|
||||
# ====================
|
||||
|
||||
@@ -106,12 +106,10 @@ class ABCIngredientParser(ABC):
|
||||
return 70
|
||||
|
||||
@abstractmethod
|
||||
def parse_one(self, ingredient_string: str) -> ParsedIngredient:
|
||||
...
|
||||
def parse_one(self, ingredient_string: str) -> ParsedIngredient: ...
|
||||
|
||||
@abstractmethod
|
||||
def parse(self, ingredients: list[str]) -> list[ParsedIngredient]:
|
||||
...
|
||||
def parse(self, ingredients: list[str]) -> list[ParsedIngredient]: ...
|
||||
|
||||
@classmethod
|
||||
def find_match(cls, match_value: str, *, store_map: dict[str, T], fuzzy_match_threshold: int = 0) -> T | None:
|
||||
|
||||
@@ -98,7 +98,7 @@ class RecipeBulkScraperService(BaseService):
|
||||
tasks = [_do(b.url) for b in urls.imports]
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
for b, recipe in zip(urls.imports, results, strict=True):
|
||||
if not recipe or isinstance(recipe, Exception):
|
||||
if not recipe or isinstance(recipe, BaseException):
|
||||
continue
|
||||
|
||||
if b.tags:
|
||||
|
||||
@@ -84,8 +84,7 @@ class ABCScraperStrategy(ABC):
|
||||
self.translator = translator
|
||||
|
||||
@abstractmethod
|
||||
async def get_html(self, url: str) -> str:
|
||||
...
|
||||
async def get_html(self, url: str) -> str: ...
|
||||
|
||||
@abstractmethod
|
||||
async def parse(self) -> tuple[Recipe, ScrapedExtras] | tuple[None, None]:
|
||||
|
||||
Reference in New Issue
Block a user