mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-03-04 04:33:12 -05:00
chores: updates-and-linters (#1868)
* switch to ruff * add ruff * run ruff --fix * update ruff * resolve ruff errors * drop isort from CI * fix decorator order
This commit is contained in:
@@ -15,15 +15,18 @@ settings = get_app_settings()
|
||||
description = f"""
|
||||
Mealie is a web application for managing your recipes, meal plans, and shopping lists. This is the Restful
|
||||
API interactive documentation that can be used to explore the API. If you're justing getting started with
|
||||
the API and want to get started quickly, you can use the [API Usage | Mealie Docs](https://hay-kot.github.io/mealie/documentation/getting-started/api-usage/)
|
||||
the API and want to get started quickly, you can use the
|
||||
[API Usage | Mealie Docs](https://hay-kot.github.io/mealie/documentation/getting-started/api-usage/)
|
||||
as a reference for how to get started.
|
||||
|
||||
|
||||
As of this release <b>{APP_VERSION}</b>, Mealie is still in rapid development and therefore some of these APIs may change from version to version.
|
||||
As of this release <b>{APP_VERSION}</b>, Mealie is still in rapid development and therefore some of these APIs may
|
||||
change from version to version.
|
||||
|
||||
|
||||
If you have any questions or comments about mealie, please use the discord server to talk to the developers or other community members.
|
||||
If you'd like to file an issue, please use the [GitHub Issue Tracker | Mealie](https://github.com/hay-kot/mealie/issues/new/choose)
|
||||
If you have any questions or comments about mealie, please use the discord server to talk to the developers or other
|
||||
community members. If you'd like to file an issue, please use the
|
||||
[GitHub Issue Tracker | Mealie](https://github.com/hay-kot/mealie/issues/new/choose)
|
||||
|
||||
|
||||
## Helpful Links
|
||||
@@ -32,8 +35,6 @@ If you'd like to file an issue, please use the [GitHub Issue Tracker | Mealie](h
|
||||
- [Discord](https://discord.gg/QuStdQGSGK)
|
||||
- [Demo](https://demo.mealie.io)
|
||||
- [Beta](https://demo.mealie.io)
|
||||
|
||||
|
||||
"""
|
||||
|
||||
app = FastAPI(
|
||||
|
||||
@@ -2,7 +2,6 @@ import shutil
|
||||
import tempfile
|
||||
from collections.abc import AsyncGenerator, Callable, Generator
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import Depends, HTTPException, status
|
||||
@@ -116,7 +115,7 @@ def validate_long_live_token(session: Session, client_token: str, user_id: str)
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED) from e
|
||||
|
||||
|
||||
def validate_file_token(token: Optional[str] = None) -> Path:
|
||||
def validate_file_token(token: str | None = None) -> Path:
|
||||
"""
|
||||
Args:
|
||||
token (Optional[str], optional): _description_. Defaults to None.
|
||||
@@ -143,7 +142,7 @@ def validate_file_token(token: Optional[str] = None) -> Path:
|
||||
return file_path
|
||||
|
||||
|
||||
def validate_recipe_token(token: Optional[str] = None) -> str:
|
||||
def validate_recipe_token(token: str | None = None) -> str:
|
||||
"""
|
||||
Args:
|
||||
token (Optional[str], optional): _description_. Defaults to None.
|
||||
|
||||
@@ -7,7 +7,7 @@ from mealie.core.config import determine_data_dir
|
||||
|
||||
DATA_DIR = determine_data_dir()
|
||||
|
||||
from .config import get_app_settings
|
||||
from .config import get_app_settings # noqa E402
|
||||
|
||||
LOGGER_FILE = DATA_DIR.joinpath("mealie.log")
|
||||
DATE_FORMAT = "%d-%b-%y %H:%M:%S"
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
from abc import ABC, abstractproperty
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
|
||||
from pydantic import BaseModel, BaseSettings, PostgresDsn
|
||||
|
||||
|
||||
class AbstractDBProvider(ABC):
|
||||
@abstractproperty
|
||||
@property
|
||||
@abstractmethod
|
||||
def db_url(self) -> str:
|
||||
pass
|
||||
...
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def db_url_public(self) -> str:
|
||||
pass
|
||||
...
|
||||
|
||||
|
||||
class SQLiteProvider(AbstractDBProvider, BaseModel):
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import secrets
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseSettings, NoneStr
|
||||
|
||||
@@ -54,7 +53,7 @@ class AppSettings(BaseSettings):
|
||||
# Database Configuration
|
||||
|
||||
DB_ENGINE: str = "sqlite" # Options: 'sqlite', 'postgres'
|
||||
DB_PROVIDER: Optional[AbstractDBProvider] = None
|
||||
DB_PROVIDER: AbstractDBProvider | None = None
|
||||
|
||||
@property
|
||||
def DB_URL(self) -> str | None:
|
||||
@@ -71,13 +70,13 @@ class AppSettings(BaseSettings):
|
||||
# ===============================================
|
||||
# Email Configuration
|
||||
|
||||
SMTP_HOST: Optional[str]
|
||||
SMTP_PORT: Optional[str] = "587"
|
||||
SMTP_FROM_NAME: Optional[str] = "Mealie"
|
||||
SMTP_FROM_EMAIL: Optional[str]
|
||||
SMTP_USER: Optional[str]
|
||||
SMTP_PASSWORD: Optional[str]
|
||||
SMTP_AUTH_STRATEGY: Optional[str] = "TLS" # Options: 'TLS', 'SSL', 'NONE'
|
||||
SMTP_HOST: str | None
|
||||
SMTP_PORT: str | None = "587"
|
||||
SMTP_FROM_NAME: str | None = "Mealie"
|
||||
SMTP_FROM_EMAIL: str | None
|
||||
SMTP_USER: str | None
|
||||
SMTP_PASSWORD: str | None
|
||||
SMTP_AUTH_STRATEGY: str | None = "TLS" # Options: 'TLS', 'SSL', 'NONE'
|
||||
|
||||
@property
|
||||
def SMTP_ENABLE(self) -> bool:
|
||||
|
||||
@@ -1,2 +1,8 @@
|
||||
from .auto_init import auto_init
|
||||
from .guid import GUID
|
||||
|
||||
|
||||
__all__ = [
|
||||
"auto_init",
|
||||
"GUID",
|
||||
]
|
||||
|
||||
@@ -17,7 +17,7 @@ class Translator(Protocol):
|
||||
pass
|
||||
|
||||
|
||||
@lru_cache()
|
||||
@lru_cache
|
||||
def _load_factory() -> i18n.ProviderFactory:
|
||||
return i18n.ProviderFactory(
|
||||
directory=TRANSLATIONS,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
"""
|
||||
This package containers helpful development tools to be used for development and testing. It shouldn't be used for or imported
|
||||
in production
|
||||
"""
|
||||
|
||||
from .lifespan_tracker import *
|
||||
from .timer import *
|
||||
@@ -1,26 +0,0 @@
|
||||
import time
|
||||
|
||||
|
||||
# log_lifetime is a class decorator that logs the creation and destruction of a class
|
||||
# It is used to track the lifespan of a class during development or testing.
|
||||
# It SHOULD NOT be used in production code.
|
||||
def log_lifetime(cls):
|
||||
class LifeTimeClass(cls):
|
||||
def __init__(self, *args, **kwargs):
|
||||
print(f"Creating an instance of {cls.__name__}") # noqa: T001
|
||||
self.__lifespan_timer_start = time.perf_counter()
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __del__(self):
|
||||
toc = time.perf_counter()
|
||||
print(f"Downloaded the tutorial in {toc - self.__lifespan_timer_start:0.4f} seconds") # noqa: T001
|
||||
|
||||
print(f"Deleting an instance of {cls.__name__}") # noqa: T001
|
||||
|
||||
try:
|
||||
super().__del__()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return LifeTimeClass
|
||||
@@ -1,12 +0,0 @@
|
||||
import time
|
||||
|
||||
|
||||
def timer(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
start = time.time()
|
||||
result = func(*args, **kwargs)
|
||||
end = time.time()
|
||||
print(f"{func.__name__} took {end - start} seconds") # noqa: T001
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
@@ -283,7 +283,7 @@ class RepositoryGeneric(Generic[Schema, Model]):
|
||||
|
||||
except ValueError as e:
|
||||
self.logger.error(e)
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
|
||||
count = query.count()
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import Union
|
||||
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.db.models.group import Group
|
||||
@@ -15,7 +13,7 @@ from .repository_generic import RepositoryGeneric
|
||||
|
||||
|
||||
class RepositoryGroup(RepositoryGeneric[GroupInDB, Group]):
|
||||
def get_by_name(self, name: str, limit=1) -> Union[GroupInDB, Group, None]:
|
||||
def get_by_name(self, name: str, limit=1) -> GroupInDB | Group | None:
|
||||
dbgroup = self.session.query(self.model).filter_by(**{"name": name}).one_or_none()
|
||||
if dbgroup is None:
|
||||
return None
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from random import randint
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import UUID4
|
||||
@@ -135,10 +135,10 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]):
|
||||
pagination: PaginationQuery,
|
||||
override=None,
|
||||
load_food=False,
|
||||
cookbook: Optional[ReadCookBook] = None,
|
||||
categories: Optional[list[UUID4 | str]] = None,
|
||||
tags: Optional[list[UUID4 | str]] = None,
|
||||
tools: Optional[list[UUID4 | str]] = None,
|
||||
cookbook: ReadCookBook | None = None,
|
||||
categories: list[UUID4 | str] | None = None,
|
||||
tags: list[UUID4 | str] | None = None,
|
||||
tools: list[UUID4 | str] | None = None,
|
||||
) -> RecipePagination:
|
||||
q = self.session.query(self.model)
|
||||
|
||||
@@ -307,7 +307,7 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]):
|
||||
.limit(limit)
|
||||
]
|
||||
|
||||
def get_by_slug(self, group_id: UUID4, slug: str, limit=1) -> Optional[Recipe]:
|
||||
def get_by_slug(self, group_id: UUID4, slug: str, limit=1) -> Recipe | None:
|
||||
dbrecipe = (
|
||||
self.session.query(RecipeModel)
|
||||
.filter(RecipeModel.group_id == group_id, RecipeModel.slug == slug)
|
||||
|
||||
@@ -5,7 +5,7 @@ See their repository for details -> https://github.com/dmontagu/fastapi-utils
|
||||
"""
|
||||
import inspect
|
||||
from collections.abc import Callable
|
||||
from typing import Any, TypeVar, Union, cast, get_type_hints
|
||||
from typing import Any, TypeVar, cast, get_type_hints
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi.routing import APIRoute
|
||||
@@ -92,8 +92,8 @@ def _init_cbv(cls: type[Any], instance: Any = None) -> None:
|
||||
else:
|
||||
old_init(self, *args, **kwargs)
|
||||
|
||||
setattr(cls, "__signature__", new_signature)
|
||||
setattr(cls, "__init__", new_init)
|
||||
cls.__signature__ = new_signature
|
||||
cls.__init__ = new_init
|
||||
setattr(cls, CBV_CLASS_KEY, True)
|
||||
|
||||
for name in private_attributes:
|
||||
@@ -122,7 +122,7 @@ def _register_endpoints(router: APIRouter, cls: type[Any], *urls: str) -> None:
|
||||
}
|
||||
|
||||
prefix_length = len(router.prefix)
|
||||
routes_to_append: list[tuple[int, Union[Route, WebSocketRoute]]] = []
|
||||
routes_to_append: list[tuple[int, Route | WebSocketRoute]] = []
|
||||
for _, func in function_members:
|
||||
index_route = numbered_routes_by_endpoint.get(func)
|
||||
|
||||
@@ -177,7 +177,7 @@ def _allocate_routes_by_method_name(router: APIRouter, url: str, function_member
|
||||
api_resource(func)
|
||||
|
||||
|
||||
def _update_cbv_route_endpoint_signature(cls: type[Any], route: Union[Route, WebSocketRoute]) -> None:
|
||||
def _update_cbv_route_endpoint_signature(cls: type[Any], route: Route | WebSocketRoute) -> None:
|
||||
"""
|
||||
Fixes the endpoint signature for a cbv route to ensure FastAPI performs dependency injection properly.
|
||||
"""
|
||||
@@ -191,4 +191,4 @@ def _update_cbv_route_endpoint_signature(cls: type[Any], route: Union[Route, Web
|
||||
]
|
||||
|
||||
new_signature = old_signature.replace(parameters=new_parameters)
|
||||
setattr(route.endpoint, "__signature__", new_signature)
|
||||
route.endpoint.__signature__ = new_signature
|
||||
|
||||
@@ -3,7 +3,6 @@ import json
|
||||
from collections.abc import Callable
|
||||
from enum import Enum
|
||||
from json.decoder import JSONDecodeError
|
||||
from typing import Optional, Union
|
||||
|
||||
from fastapi import APIRouter, Depends, Request, Response
|
||||
from fastapi.routing import APIRoute
|
||||
@@ -14,14 +13,14 @@ from mealie.core.dependencies import get_admin_user, get_current_user
|
||||
class AdminAPIRouter(APIRouter):
|
||||
"""Router for functions to be protected behind admin authentication"""
|
||||
|
||||
def __init__(self, tags: Optional[list[Union[str, Enum]]] = None, prefix: str = "", **kwargs):
|
||||
def __init__(self, tags: list[str | Enum] | None = None, prefix: str = "", **kwargs):
|
||||
super().__init__(tags=tags, prefix=prefix, dependencies=[Depends(get_admin_user)], **kwargs)
|
||||
|
||||
|
||||
class UserAPIRouter(APIRouter):
|
||||
"""Router for functions to be protected behind user authentication"""
|
||||
|
||||
def __init__(self, tags: Optional[list[Union[str, Enum]]] = None, prefix: str = "", **kwargs):
|
||||
def __init__(self, tags: list[str | Enum] | None = None, prefix: str = "", **kwargs):
|
||||
super().__init__(tags=tags, prefix=prefix, dependencies=[Depends(get_current_user)], **kwargs)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from datetime import timedelta
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Form, status
|
||||
from fastapi.exceptions import HTTPException
|
||||
@@ -27,8 +26,8 @@ class CustomOAuth2Form(OAuth2PasswordRequestForm):
|
||||
password: str = Form(...),
|
||||
remember_me: bool = Form(False),
|
||||
scope: str = Form(""),
|
||||
client_id: Optional[str] = Form(None),
|
||||
client_secret: Optional[str] = Form(None),
|
||||
client_id: str | None = Form(None),
|
||||
client_secret: str | None = Form(None),
|
||||
):
|
||||
self.grant_type = grant_type
|
||||
self.username = username
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
from functools import cached_property
|
||||
from typing import Type
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.core.exceptions import mealie_registered_exceptions
|
||||
from mealie.routes._base.base_controllers import BaseUserController
|
||||
from mealie.routes._base.controller import controller
|
||||
from mealie.routes._base.mixins import HttpRepo
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from datetime import date
|
||||
from functools import cached_property
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
@@ -84,15 +83,17 @@ class GroupMealplanController(BaseUserController):
|
||||
return self.mixins.create_one(
|
||||
SavePlanEntry(date=data.date, entry_type=data.entry_type, recipe_id=recipe.id, group_id=self.group_id)
|
||||
)
|
||||
except IndexError:
|
||||
raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No recipes match your rules"))
|
||||
except IndexError as e:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=ErrorResponse.respond(message="No recipes match your rules")
|
||||
) from e
|
||||
|
||||
@router.get("", response_model=PlanEntryPagination)
|
||||
def get_all(
|
||||
self,
|
||||
q: PaginationQuery = Depends(PaginationQuery),
|
||||
start_date: Optional[date] = None,
|
||||
end_date: Optional[date] = None,
|
||||
start_date: date | None = None,
|
||||
end_date: date | None = None,
|
||||
):
|
||||
# merge start and end dates into pagination query only if either is provided
|
||||
if start_date or end_date:
|
||||
|
||||
@@ -45,7 +45,7 @@ class GroupMigrationController(BaseUserController):
|
||||
|
||||
migrator: BaseMigrator
|
||||
|
||||
match migration_type:
|
||||
match migration_type: # noqa match not supported by ruff
|
||||
case SupportedMigrations.chowdown:
|
||||
migrator = ChowdownMigrator(**args)
|
||||
case SupportedMigrations.mealie_alpha:
|
||||
|
||||
@@ -41,5 +41,5 @@ async def get_recipe_asset(recipe_id: UUID4, file_name: str):
|
||||
|
||||
try:
|
||||
return FileResponse(file)
|
||||
except Exception:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
||||
except Exception as e:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND) from e
|
||||
|
||||
@@ -140,7 +140,7 @@ router = UserAPIRouter(prefix="/recipes", tags=["Recipe: CRUD"], route_class=Mea
|
||||
@controller(router)
|
||||
class RecipeController(BaseRecipeController):
|
||||
def handle_exceptions(self, ex: Exception) -> None:
|
||||
match type(ex):
|
||||
match type(ex): # noqa match statement not supported
|
||||
case exceptions.PermissionDenied:
|
||||
self.logger.error("Permission Denied on recipe controller action")
|
||||
raise HTTPException(status_code=403, detail=ErrorResponse.respond(message="Permission Denied"))
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
from typing import Optional
|
||||
|
||||
NoneFloat = Optional[float]
|
||||
NoneFloat = float | None
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -20,9 +19,9 @@ class ImportJob(BackupOptions):
|
||||
|
||||
|
||||
class CreateBackup(BaseModel):
|
||||
tag: Optional[str]
|
||||
tag: str | None
|
||||
options: BackupOptions
|
||||
templates: Optional[list[str]]
|
||||
templates: list[str] | None
|
||||
|
||||
|
||||
class BackupFile(BaseModel):
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic.main import BaseModel
|
||||
|
||||
|
||||
class ImportBase(BaseModel):
|
||||
name: str
|
||||
status: bool
|
||||
exception: Optional[str]
|
||||
exception: str | None
|
||||
|
||||
|
||||
class RecipeImport(ImportBase):
|
||||
slug: Optional[str]
|
||||
slug: str | None
|
||||
|
||||
|
||||
class CommentImport(ImportBase):
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import validator
|
||||
from slugify import slugify
|
||||
|
||||
@@ -10,7 +8,7 @@ from ..recipe.recipe_category import RecipeCategoryResponse
|
||||
|
||||
class CustomPageBase(MealieModel):
|
||||
name: str
|
||||
slug: Optional[str]
|
||||
slug: str | None
|
||||
position: int
|
||||
categories: list[RecipeCategoryResponse] = []
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
@@ -10,4 +8,4 @@ from .group_preferences import UpdateGroupPreferences
|
||||
class GroupAdminUpdate(MealieModel):
|
||||
id: UUID4
|
||||
name: str
|
||||
preferences: Optional[UpdateGroupPreferences] = None
|
||||
preferences: UpdateGroupPreferences | None = None
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import validator
|
||||
|
||||
@@ -7,16 +6,16 @@ from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class MealIn(MealieModel):
|
||||
slug: Optional[str]
|
||||
name: Optional[str]
|
||||
description: Optional[str]
|
||||
slug: str | None
|
||||
name: str | None
|
||||
description: str | None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class MealDayIn(MealieModel):
|
||||
date: Optional[date]
|
||||
date: date | None
|
||||
meals: list[MealIn]
|
||||
|
||||
class Config:
|
||||
@@ -48,7 +47,7 @@ class MealPlanIn(MealieModel):
|
||||
|
||||
class MealPlanOut(MealPlanIn):
|
||||
id: int
|
||||
shopping_list: Optional[int]
|
||||
shopping_list: int | None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from datetime import date
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import validator
|
||||
@@ -27,7 +26,7 @@ class CreatePlanEntry(MealieModel):
|
||||
entry_type: PlanEntryType = PlanEntryType.breakfast
|
||||
title: str = ""
|
||||
text: str = ""
|
||||
recipe_id: Optional[UUID]
|
||||
recipe_id: UUID | None
|
||||
|
||||
@validator("recipe_id", always=True)
|
||||
@classmethod
|
||||
@@ -51,7 +50,7 @@ class SavePlanEntry(CreatePlanEntry):
|
||||
|
||||
|
||||
class ReadPlanEntry(UpdatePlanEntry):
|
||||
recipe: Optional[RecipeSummary]
|
||||
recipe: RecipeSummary | None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic.utils import GetterDict
|
||||
|
||||
from mealie.db.models.group.shopping_list import ShoppingList
|
||||
@@ -7,7 +5,7 @@ from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class ListItem(MealieModel):
|
||||
title: Optional[str]
|
||||
title: str | None
|
||||
text: str = ""
|
||||
quantity: int = 1
|
||||
checked: bool = False
|
||||
@@ -18,7 +16,7 @@ class ListItem(MealieModel):
|
||||
|
||||
class ShoppingListIn(MealieModel):
|
||||
name: str
|
||||
group: Optional[str]
|
||||
group: str | None
|
||||
items: list[ListItem]
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class RecipeAsset(MealieModel):
|
||||
name: str
|
||||
icon: str
|
||||
file_name: Optional[str]
|
||||
file_name: str | None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@@ -65,7 +65,7 @@ class RecipeTagResponse(RecipeCategoryResponse):
|
||||
pass
|
||||
|
||||
|
||||
from mealie.schema.recipe.recipe import RecipeSummary
|
||||
from mealie.schema.recipe.recipe import RecipeSummary # noqa: E402
|
||||
|
||||
RecipeCategoryResponse.update_forward_refs()
|
||||
RecipeTagResponse.update_forward_refs()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import UUID4
|
||||
|
||||
@@ -9,7 +8,7 @@ from mealie.schema.response.pagination import PaginationBase
|
||||
|
||||
class UserBase(MealieModel):
|
||||
id: UUID4
|
||||
username: Optional[str]
|
||||
username: str | None
|
||||
admin: bool
|
||||
|
||||
class Config:
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
from typing import Optional
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class Nutrition(MealieModel):
|
||||
calories: Optional[str]
|
||||
fat_content: Optional[str]
|
||||
protein_content: Optional[str]
|
||||
carbohydrate_content: Optional[str]
|
||||
fiber_content: Optional[str]
|
||||
sodium_content: Optional[str]
|
||||
sugar_content: Optional[str]
|
||||
calories: str | None
|
||||
fat_content: str | None
|
||||
protein_content: str | None
|
||||
carbohydrate_content: str | None
|
||||
fiber_content: str | None
|
||||
sodium_content: str | None
|
||||
sugar_content: str | None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from typing import Optional
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from pydantic import UUID4, Field
|
||||
@@ -11,15 +10,15 @@ class IngredientReferences(MealieModel):
|
||||
A list of ingredient references.
|
||||
"""
|
||||
|
||||
reference_id: Optional[UUID4]
|
||||
reference_id: UUID4 | None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class RecipeStep(MealieModel):
|
||||
id: Optional[UUID] = Field(default_factory=uuid4)
|
||||
title: Optional[str] = ""
|
||||
id: UUID | None = Field(default_factory=uuid4)
|
||||
title: str | None = ""
|
||||
text: str
|
||||
ingredient_references: list[IngredientReferences] = []
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import typing
|
||||
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
@@ -23,7 +21,7 @@ class RecipeToolOut(RecipeToolCreate):
|
||||
|
||||
|
||||
class RecipeToolResponse(RecipeToolOut):
|
||||
recipes: typing.List["Recipe"] = []
|
||||
recipes: list["Recipe"] = []
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@@ -62,7 +62,14 @@ class QueryFilter:
|
||||
self.filter_components = QueryFilter._parse_base_components_into_filter_components(base_components)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'<<{" ".join([str(component.value if isinstance(component, LogicalOperator) else component) for component in self.filter_components])}>>'
|
||||
joined = " ".join(
|
||||
[
|
||||
str(component.value if isinstance(component, LogicalOperator) else component)
|
||||
for component in self.filter_components
|
||||
],
|
||||
)
|
||||
|
||||
return f"<<{joined}>>"
|
||||
|
||||
def filter_query(self, query: Query, model: type[Model]) -> Query:
|
||||
segments: list[str] = []
|
||||
@@ -76,8 +83,9 @@ class QueryFilter:
|
||||
segments.append(component.value)
|
||||
continue
|
||||
|
||||
# for some reason typing doesn't like the lsep and rsep literals, so we explicitly mark this as a filter component instead
|
||||
# cast doesn't actually do anything at runtime
|
||||
# for some reason typing doesn't like the lsep and rsep literals, so
|
||||
# we explicitly mark this as a filter component instead cast doesn't
|
||||
# actually do anything at runtime
|
||||
component = cast(QueryFilterComponent, component)
|
||||
|
||||
if not hasattr(model, component.attribute_name):
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
@@ -8,10 +6,10 @@ from mealie.schema._mealie import MealieModel
|
||||
class ErrorResponse(BaseModel):
|
||||
message: str
|
||||
error: bool = True
|
||||
exception: Optional[str] = None
|
||||
exception: str | None = None
|
||||
|
||||
@classmethod
|
||||
def respond(cls, message: str, exception: Optional[str] = None) -> dict:
|
||||
def respond(cls, message: str, exception: str | None = None) -> dict:
|
||||
"""
|
||||
This method is an helper to create an object and convert to a dictionary
|
||||
in the same call, for use while providing details to a HTTPException
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import UUID4, BaseModel
|
||||
from pydantic.types import constr
|
||||
|
||||
@@ -12,8 +10,8 @@ class Token(BaseModel):
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
user_id: Optional[UUID4]
|
||||
username: Optional[constr(to_lower=True, strip_whitespace=True)] = None # type: ignore
|
||||
user_id: UUID4 | None
|
||||
username: constr(to_lower=True, strip_whitespace=True) | None = None # type: ignore
|
||||
|
||||
|
||||
class UnlockResults(MealieModel):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import UUID4, Field, validator
|
||||
@@ -62,13 +62,13 @@ class GroupBase(MealieModel):
|
||||
|
||||
|
||||
class UserBase(MealieModel):
|
||||
username: Optional[str]
|
||||
full_name: Optional[str] = None
|
||||
username: str | None
|
||||
full_name: str | None = None
|
||||
email: constr(to_lower=True, strip_whitespace=True) # type: ignore
|
||||
admin: bool = False
|
||||
group: Optional[str]
|
||||
group: str | None
|
||||
advanced: bool = False
|
||||
favorite_recipes: Optional[list[str]] = []
|
||||
favorite_recipes: list[str] | None = []
|
||||
|
||||
can_invite: bool = False
|
||||
can_manage: bool = False
|
||||
@@ -103,9 +103,9 @@ class UserOut(UserBase):
|
||||
id: UUID4
|
||||
group: str
|
||||
group_id: UUID4
|
||||
tokens: Optional[list[LongLiveTokenOut]]
|
||||
tokens: list[LongLiveTokenOut] | None
|
||||
cache_key: str
|
||||
favorite_recipes: Optional[list[str]] = []
|
||||
favorite_recipes: list[str] | None = []
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
@@ -171,14 +171,14 @@ class PrivateUser(UserOut):
|
||||
class UpdateGroup(GroupBase):
|
||||
id: UUID4
|
||||
name: str
|
||||
categories: Optional[list[CategoryBase]] = []
|
||||
categories: list[CategoryBase] | None = []
|
||||
|
||||
webhooks: list[Any] = []
|
||||
|
||||
|
||||
class GroupInDB(UpdateGroup):
|
||||
users: Optional[list[UserOut]]
|
||||
preferences: Optional[ReadGroupPreferences] = None
|
||||
users: list[UserOut] | None
|
||||
preferences: ReadGroupPreferences | None = None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
from .email_service import EmailService, EmailTemplate
|
||||
|
||||
__all__ = [
|
||||
"EmailService",
|
||||
"EmailTemplate",
|
||||
]
|
||||
|
||||
@@ -130,7 +130,7 @@ class WebhookEventListener(EventListenerBase):
|
||||
return scheduled_webhooks
|
||||
|
||||
def publish_to_subscribers(self, event: Event, subscribers: list[ReadWebhook]) -> None:
|
||||
match event.document_data.document_type:
|
||||
match event.document_data.document_type: # noqa - match statement not supported by ruff
|
||||
case EventDocumentType.mealplan:
|
||||
# TODO: limit mealplan data to a date range instead of returning all mealplans
|
||||
meal_repo = self.repos.meals.by_group(self.group_id)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import BackgroundTasks, Depends
|
||||
from pydantic import UUID4
|
||||
from sqlalchemy.orm.session import Session
|
||||
@@ -45,7 +43,7 @@ class EventBusService:
|
||||
group_id: UUID4 | None
|
||||
|
||||
def __init__(
|
||||
self, bg: Optional[BackgroundTasks] = None, session: Optional[Session] = None, group_id: UUID4 | None = None
|
||||
self, bg: BackgroundTasks | None = None, session: Session | None = None, group_id: UUID4 | None = None
|
||||
) -> None:
|
||||
self.bg = bg
|
||||
self.session = session
|
||||
@@ -61,7 +59,7 @@ class EventBusService:
|
||||
integration_id: str,
|
||||
group_id: UUID4,
|
||||
event_type: EventTypes,
|
||||
document_data: Optional[EventDocumentDataBase],
|
||||
document_data: EventDocumentDataBase | None,
|
||||
message: str = "",
|
||||
) -> None:
|
||||
self.group_id = group_id
|
||||
|
||||
@@ -3,7 +3,7 @@ from abc import abstractmethod, abstractproperty
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Callable, Optional
|
||||
from typing import Callable
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel
|
||||
@@ -28,7 +28,7 @@ class ExportedItem:
|
||||
|
||||
|
||||
class ABCExporter(BaseService):
|
||||
write_dir_to_zip: Callable[[Path, str, Optional[set[str]]], None] | None
|
||||
write_dir_to_zip: Callable[[Path, str, set[str] | None], None] | None
|
||||
|
||||
def __init__(self, db: AllRepositories, group_id: UUID) -> None:
|
||||
self.logger = get_logger()
|
||||
|
||||
@@ -169,7 +169,9 @@ class ShoppingListService:
|
||||
if not updated_shopping_list:
|
||||
raise UnexpectedNone("Shopping List not found")
|
||||
|
||||
updated_shopping_list_items, deleted_shopping_list_items = self.consolidate_and_save(updated_shopping_list.list_items) # type: ignore
|
||||
updated_shopping_list_items, deleted_shopping_list_items = self.consolidate_and_save(
|
||||
updated_shopping_list.list_items, # type: ignore
|
||||
)
|
||||
updated_shopping_list.list_items = updated_shopping_list_items
|
||||
|
||||
not_found = True
|
||||
@@ -268,5 +270,8 @@ class ShoppingListService:
|
||||
self.list_refs.update(recipe_ref.id, ref)
|
||||
break
|
||||
|
||||
# Save Changes
|
||||
return self.shopping_lists.get_one(shopping_list.id), updated_shopping_list_items, deleted_shopping_list_items # type: ignore
|
||||
return (
|
||||
self.shopping_lists.get_one(shopping_list.id),
|
||||
updated_shopping_list_items,
|
||||
deleted_shopping_list_items,
|
||||
) # type: ignore
|
||||
|
||||
@@ -2,7 +2,6 @@ import tempfile
|
||||
import zipfile
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from slugify import slugify
|
||||
|
||||
@@ -15,7 +14,7 @@ from .utils.migration_helpers import MigrationReaders, glob_walker, import_image
|
||||
class NextcloudDir:
|
||||
name: str
|
||||
recipe: dict
|
||||
image: Optional[Path]
|
||||
image: Path | None
|
||||
|
||||
@property
|
||||
def slug(self):
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from collections.abc import Callable
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -12,4 +11,4 @@ class MigrationAlias(BaseModel):
|
||||
|
||||
key: str
|
||||
alias: str
|
||||
func: Optional[Callable] = None
|
||||
func: Callable | None = None
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
from .process import parse
|
||||
|
||||
__all__ = [
|
||||
"parse",
|
||||
]
|
||||
|
||||
@@ -26,8 +26,8 @@ def parse_fraction(x):
|
||||
raise ValueError
|
||||
try:
|
||||
return int(frac_split[0]) / int(frac_split[1])
|
||||
except ZeroDivisionError:
|
||||
raise ValueError
|
||||
except ZeroDivisionError as e:
|
||||
raise ValueError from e
|
||||
|
||||
|
||||
def parse_amount(ing_str) -> tuple[float, str, str]:
|
||||
|
||||
@@ -52,7 +52,8 @@ def wrap_or_clause(string: str):
|
||||
Attempts to wrap or clauses in ()
|
||||
|
||||
Examples:
|
||||
'1 tsp. Diamond Crystal or ½ tsp. Morton kosher salt, plus more' -> '1 teaspoon diamond crystal (or 1/2 teaspoon morton kosher salt), plus more'
|
||||
'1 tsp. Diamond Crystal or ½ tsp. Morton kosher salt, plus more'
|
||||
-> '1 teaspoon diamond crystal (or 1/2 teaspoon morton kosher salt), plus more'
|
||||
|
||||
"""
|
||||
# TODO: Needs more adequite testing to be sure this doesn't have side effects.
|
||||
|
||||
@@ -17,14 +17,17 @@ async def largest_content_len(urls: list[str]) -> tuple[str, int]:
|
||||
largest_url = ""
|
||||
largest_len = 0
|
||||
|
||||
def do(session: requests.Session, url: str):
|
||||
def _do() -> requests.Response:
|
||||
return session.head(url, headers={"User-Agent": _FIREFOX_UA})
|
||||
|
||||
return _do
|
||||
|
||||
with ThreadPoolExecutor(max_workers=10) as executor:
|
||||
with requests.Session() as session:
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
tasks = [
|
||||
loop.run_in_executor(executor, lambda: session.head(url, headers={"User-Agent": _FIREFOX_UA}))
|
||||
for url in urls
|
||||
]
|
||||
tasks = [loop.run_in_executor(executor, do(session, url)) for url in urls]
|
||||
|
||||
response: requests.Response # required for type hinting within the loop
|
||||
for response in await asyncio.gather(*tasks):
|
||||
|
||||
@@ -3,7 +3,6 @@ import shutil
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from shutil import copytree, rmtree
|
||||
from typing import Union
|
||||
from zipfile import ZipFile
|
||||
|
||||
from fastapi import UploadFile
|
||||
@@ -27,12 +26,6 @@ step_text = """Recipe steps as well as other fields in the recipe page support m
|
||||
|
||||
[My Link](https://demo.mealie.io)
|
||||
|
||||
**Embed an image**
|
||||
|
||||
Use the `height="100"` or `width="100"` attributes to set the size of the image.
|
||||
|
||||
<img height="100" src="https://images.unsplash.com/photo-1567620905732-2d1ec7ab7445?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=960&q=80"></img>
|
||||
|
||||
"""
|
||||
|
||||
ingredient_note = "1 Cup Flour"
|
||||
@@ -110,7 +103,7 @@ class RecipeService(BaseService):
|
||||
|
||||
return Recipe(**additional_attrs)
|
||||
|
||||
def create_one(self, create_data: Union[Recipe, CreateRecipe]) -> Recipe:
|
||||
def create_one(self, create_data: Recipe | CreateRecipe) -> Recipe:
|
||||
|
||||
if create_data.name is None:
|
||||
create_data.name = "New Recipe"
|
||||
|
||||
@@ -6,22 +6,22 @@ import logging
|
||||
from asyncio import ensure_future
|
||||
from functools import wraps
|
||||
from traceback import format_exception
|
||||
from typing import Any, Callable, Coroutine, Optional, Union
|
||||
from typing import Any, Callable, Coroutine
|
||||
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
|
||||
NoArgsNoReturnFuncT = Callable[[], None]
|
||||
NoArgsNoReturnAsyncFuncT = Callable[[], Coroutine[Any, Any, None]]
|
||||
NoArgsNoReturnDecorator = Callable[[Union[NoArgsNoReturnFuncT, NoArgsNoReturnAsyncFuncT]], NoArgsNoReturnAsyncFuncT]
|
||||
NoArgsNoReturnDecorator = Callable[[NoArgsNoReturnFuncT | NoArgsNoReturnAsyncFuncT], NoArgsNoReturnAsyncFuncT]
|
||||
|
||||
|
||||
def repeat_every(
|
||||
*,
|
||||
minutes: float,
|
||||
wait_first: bool = False,
|
||||
logger: Optional[logging.Logger] = None,
|
||||
logger: logging.Logger | None = None,
|
||||
raise_exceptions: bool = False,
|
||||
max_repetitions: Optional[int] = None,
|
||||
max_repetitions: int | None = None,
|
||||
) -> NoArgsNoReturnDecorator:
|
||||
"""
|
||||
This function returns a decorator that modifies a function so it is periodically re-executed after its first call.
|
||||
@@ -46,7 +46,7 @@ def repeat_every(
|
||||
The maximum number of times to call the repeated function. If `None`, the function is repeated forever.
|
||||
"""
|
||||
|
||||
def decorator(func: Union[NoArgsNoReturnAsyncFuncT, NoArgsNoReturnFuncT]) -> NoArgsNoReturnAsyncFuncT:
|
||||
def decorator(func: NoArgsNoReturnAsyncFuncT | NoArgsNoReturnFuncT) -> NoArgsNoReturnAsyncFuncT:
|
||||
"""
|
||||
Converts the decorated function into a repeated, periodically-called version of itself.
|
||||
"""
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import UUID4
|
||||
|
||||
@@ -18,7 +17,7 @@ from mealie.services.event_bus_service.event_types import (
|
||||
last_ran = datetime.now(timezone.utc)
|
||||
|
||||
|
||||
def post_group_webhooks(start_dt: Optional[datetime] = None, group_id: Optional[UUID4] = None) -> None:
|
||||
def post_group_webhooks(start_dt: datetime | None = None, group_id: UUID4 | None = None) -> None:
|
||||
"""Post webhook events to specified group, or all groups"""
|
||||
|
||||
global last_ran
|
||||
|
||||
@@ -95,7 +95,7 @@ def clean_image(image: str | list | dict | None = None, default="no image") -> s
|
||||
if not image:
|
||||
return default
|
||||
|
||||
match image:
|
||||
match image: # noqa - match statement not supported
|
||||
case str(image):
|
||||
return image
|
||||
case list(image):
|
||||
@@ -189,7 +189,13 @@ def clean_instructions(steps_object: list | dict | str, default: list | None = N
|
||||
# }
|
||||
#
|
||||
steps_object = typing.cast(list[dict[str, str]], steps_object)
|
||||
return clean_instructions(functools.reduce(operator.concat, [x["itemListElement"] for x in steps_object], [])) # type: ignore
|
||||
return clean_instructions(
|
||||
functools.reduce(
|
||||
operator.concat, # type: ignore
|
||||
[x["itemListElement"] for x in steps_object],
|
||||
[],
|
||||
)
|
||||
)
|
||||
case _:
|
||||
raise TypeError(f"Unexpected type for instructions: {type(steps_object)}, {steps_object}")
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class PasswordResetService(BaseService):
|
||||
email_servive.send_forgot_password(email, reset_url)
|
||||
except Exception as e:
|
||||
self.logger.error(f"failed to send reset email: {e}")
|
||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to send reset email")
|
||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to send reset email") from e
|
||||
|
||||
def reset_password(self, token: str, new_password: str):
|
||||
# Validate Token
|
||||
|
||||
Reference in New Issue
Block a user