refactor(backend): ♻️ cleanup HTTP service classes and remove database singleton (#687)
* refactor(backend): ♻️ cleanup duplicate code in http services * refactor(backend): ♻️ refactor database away from singleton design removed the database single and instead injected the session into a new Database class that is created during each request life-cycle. Now sessions no longer need to be passed into each method on the database All tests pass, but there are likely some hidden breaking changes that were not discovered. * fix venv * disable venv cache * fix install script * bump poetry version * postgres fixes * revert install * fix db initialization for postgres * add postgres to docker * refactor(backend): ♻️ cleanup unused and duplicate code in http services * refactor(backend): remove sessions from arguments * refactor(backend): ♻️ convert units and ingredients to use http service class * test(backend): ✅ add unit and food tests * lint * update tags * re-enable cache * fix missing fraction in db * fix lint Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
parent
c0e3f04c23
commit
476aefeeb0
68 changed files with 1131 additions and 1084 deletions
4
.github/workflows/backend-tests.yml
vendored
4
.github/workflows/backend-tests.yml
vendored
|
@ -37,7 +37,7 @@ jobs:
|
|||
# ----- install & configure poetry -----
|
||||
#----------------------------------------------
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1.1.1
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
virtualenvs-create: true
|
||||
virtualenvs-in-project: true
|
||||
|
@ -57,7 +57,7 @@ jobs:
|
|||
run: |
|
||||
poetry install
|
||||
poetry add "psycopg2-binary==2.8.6"
|
||||
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
|
||||
# if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
|
||||
#----------------------------------------------
|
||||
# run test suite
|
||||
#----------------------------------------------
|
||||
|
|
|
@ -24,12 +24,19 @@ services:
|
|||
ports:
|
||||
- 9092:80
|
||||
environment:
|
||||
# DB_ENGINE: postgres # Optional: 'sqlite', 'postgres'
|
||||
# POSTGRES_USER: mealie
|
||||
# POSTGRES_PASSWORD: mealie
|
||||
# POSTGRES_SERVER: postgres
|
||||
# POSTGRES_PORT: 5432
|
||||
# POSTGRES_DB: mealie
|
||||
# WORKERS_PER_CORE: 0.5
|
||||
DB_ENGINE: postgres # Optional: 'sqlite', 'postgres'
|
||||
POSTGRES_USER: mealie
|
||||
POSTGRES_PASSWORD: mealie
|
||||
POSTGRES_SERVER: postgres
|
||||
POSTGRES_PORT: 5432
|
||||
POSTGRES_DB: mealie
|
||||
WORKERS_PER_CORE: 0.5
|
||||
MAX_WORKERS: 1
|
||||
WEB_CONCURRENCY: 1
|
||||
postgres:
|
||||
container_name: postgres
|
||||
image: postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: mealie
|
||||
POSTGRES_USER: mealie
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, toRefs } from "@nuxtjs/composition-api";
|
||||
import { isSameDay, addDays, subDays, parseISO, format } from "date-fns";
|
||||
import { SortableEvent } from "sortablejs";
|
||||
import { SortableEvent } from "sortablejs"; // eslint-disable-line
|
||||
import draggable from "vuedraggable";
|
||||
import { useMealplans } from "~/composables/use-group-mealplan";
|
||||
import { useRecipes, allRecipes } from "~/composables/use-recipes";
|
||||
|
|
|
@ -6,7 +6,6 @@ from mealie.core.config import APP_VERSION, settings
|
|||
from mealie.core.root_logger import get_logger
|
||||
from mealie.routes import backup_routes, migration_routes, router, utility_routes
|
||||
from mealie.routes.about import about_router
|
||||
from mealie.routes.mealplans import meal_plan_router
|
||||
from mealie.routes.media import media_router
|
||||
from mealie.routes.site_settings import settings_router
|
||||
from mealie.services.events import create_general_event
|
||||
|
@ -36,7 +35,6 @@ def api_routers():
|
|||
app.include_router(media_router)
|
||||
app.include_router(about_router)
|
||||
# Meal Routes
|
||||
app.include_router(meal_plan_router)
|
||||
# Settings Routes
|
||||
app.include_router(settings_router)
|
||||
# Backups/Imports Routes
|
||||
|
|
|
@ -7,7 +7,7 @@ from jose import JWTError, jwt
|
|||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.config import app_dirs, settings
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.schema.user import LongLiveTokenInDB, PrivateUser, TokenData
|
||||
|
||||
|
@ -69,7 +69,9 @@ async def get_current_user(token: str = Depends(oauth2_scheme), session=Depends(
|
|||
except JWTError:
|
||||
raise credentials_exception
|
||||
|
||||
user = db.users.get(session, token_data.username, "email", any_case=True)
|
||||
db = get_database(session)
|
||||
|
||||
user = db.users.get(token_data.username, "email", any_case=True)
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
return user
|
||||
|
@ -82,8 +84,9 @@ async def get_admin_user(current_user=Depends(get_current_user)) -> PrivateUser:
|
|||
|
||||
|
||||
def validate_long_live_token(session: Session, client_token: str, id: int) -> PrivateUser:
|
||||
db = get_database(session)
|
||||
|
||||
tokens: list[LongLiveTokenInDB] = db.api_tokens.get(session, id, "parent_id", limit=9999)
|
||||
tokens: list[LongLiveTokenInDB] = db.api_tokens.get(id, "parent_id", limit=9999)
|
||||
|
||||
for token in tokens:
|
||||
token: LongLiveTokenInDB
|
||||
|
|
|
@ -5,7 +5,7 @@ from jose import jwt
|
|||
from passlib.context import CryptContext
|
||||
|
||||
from mealie.core.config import settings
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.schema.user import PrivateUser
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
@ -28,10 +28,12 @@ def create_file_token(file_path: Path) -> bool:
|
|||
|
||||
|
||||
def authenticate_user(session, email: str, password: str) -> PrivateUser:
|
||||
user: PrivateUser = db.users.get(session, email, "email", any_case=True)
|
||||
db = get_database(session)
|
||||
|
||||
user: PrivateUser = db.users.get(email, "email", any_case=True)
|
||||
|
||||
if not user:
|
||||
user = db.users.get(session, email, "username", any_case=True)
|
||||
user = db.users.get(email, "username", any_case=True)
|
||||
if not user:
|
||||
return False
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
from .db_access import DatabaseAccessLayer
|
||||
from .access_model_factory import Database
|
||||
|
|
|
@ -14,7 +14,7 @@ T = TypeVar("T")
|
|||
D = TypeVar("D")
|
||||
|
||||
|
||||
class BaseAccessModel(Generic[T, D]):
|
||||
class AccessModel(Generic[T, D]):
|
||||
"""A Generic BaseAccess Model method to perform common operations on the database
|
||||
|
||||
Args:
|
||||
|
@ -22,7 +22,8 @@ class BaseAccessModel(Generic[T, D]):
|
|||
Generic ([D]): Represents the SqlAlchemyModel Model
|
||||
"""
|
||||
|
||||
def __init__(self, primary_key: Union[str, int], sql_model: D, schema: T) -> None:
|
||||
def __init__(self, session: Session, primary_key: Union[str, int], sql_model: D, schema: T) -> None:
|
||||
self.session = session
|
||||
self.primary_key = primary_key
|
||||
self.sql_model = sql_model
|
||||
self.schema = schema
|
||||
|
@ -37,9 +38,7 @@ class BaseAccessModel(Generic[T, D]):
|
|||
for observer in self.observers:
|
||||
observer()
|
||||
|
||||
def get_all(
|
||||
self, session: Session, limit: int = None, order_by: str = None, start=0, override_schema=None
|
||||
) -> list[T]:
|
||||
def get_all(self, limit: int = None, order_by: str = None, start=0, override_schema=None) -> list[T]:
|
||||
eff_schema = override_schema or self.schema
|
||||
|
||||
if order_by:
|
||||
|
@ -47,27 +46,20 @@ class BaseAccessModel(Generic[T, D]):
|
|||
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in session.query(self.sql_model).order_by(order_attr.desc()).offset(start).limit(limit).all()
|
||||
for x in self.session.query(self.sql_model).order_by(order_attr.desc()).offset(start).limit(limit).all()
|
||||
]
|
||||
|
||||
return [eff_schema.from_orm(x) for x in session.query(self.sql_model).offset(start).limit(limit).all()]
|
||||
return [eff_schema.from_orm(x) for x in self.session.query(self.sql_model).offset(start).limit(limit).all()]
|
||||
|
||||
def multi_query(
|
||||
self,
|
||||
session: Session,
|
||||
query_by: dict[str, str],
|
||||
start=0,
|
||||
limit: int = None,
|
||||
override_schema=None,
|
||||
) -> list[T]:
|
||||
def multi_query(self, query_by: dict[str, str], start=0, limit: int = None, override_schema=None) -> list[T]:
|
||||
eff_schema = override_schema or self.schema
|
||||
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in session.query(self.sql_model).filter_by(**query_by).offset(start).limit(limit).all()
|
||||
for x in self.session.query(self.sql_model).filter_by(**query_by).offset(start).limit(limit).all()
|
||||
]
|
||||
|
||||
def get_all_limit_columns(self, session: Session, fields: list[str], limit: int = None) -> list[D]:
|
||||
def get_all_limit_columns(self, fields: list[str], limit: int = None) -> list[D]:
|
||||
"""Queries the database for the selected model. Restricts return responses to the
|
||||
keys specified under "fields"
|
||||
|
||||
|
@ -79,9 +71,9 @@ class BaseAccessModel(Generic[T, D]):
|
|||
Returns:
|
||||
list[SqlAlchemyBase]: Returns a list of ORM objects
|
||||
"""
|
||||
return session.query(self.sql_model).options(load_only(*fields)).limit(limit).all()
|
||||
return self.session.query(self.sql_model).options(load_only(*fields)).limit(limit).all()
|
||||
|
||||
def get_all_primary_keys(self, session: Session) -> list[str]:
|
||||
def get_all_primary_keys(self) -> list[str]:
|
||||
"""Queries the database of the selected model and returns a list
|
||||
of all primary_key values
|
||||
|
||||
|
@ -91,11 +83,11 @@ class BaseAccessModel(Generic[T, D]):
|
|||
Returns:
|
||||
list[str]:
|
||||
"""
|
||||
results = session.query(self.sql_model).options(load_only(str(self.primary_key)))
|
||||
results = self.session.query(self.sql_model).options(load_only(str(self.primary_key)))
|
||||
results_as_dict = [x.dict() for x in results]
|
||||
return [x.get(self.primary_key) for x in results_as_dict]
|
||||
|
||||
def _query_one(self, session: Session, match_value: str, match_key: str = None) -> D:
|
||||
def _query_one(self, match_value: str, match_key: str = None) -> D:
|
||||
"""
|
||||
Query the sql database for one item an return the sql alchemy model
|
||||
object. If no match key is provided the primary_key attribute will be used.
|
||||
|
@ -103,16 +95,16 @@ class BaseAccessModel(Generic[T, D]):
|
|||
if match_key is None:
|
||||
match_key = self.primary_key
|
||||
|
||||
return session.query(self.sql_model).filter_by(**{match_key: match_value}).one()
|
||||
return self.session.query(self.sql_model).filter_by(**{match_key: match_value}).one()
|
||||
|
||||
def get_one(self, session: Session, value: str | int, key: str = None, any_case=False, override_schema=None) -> T:
|
||||
def get_one(self, value: str | int, key: str = None, any_case=False, override_schema=None) -> T:
|
||||
key = key or self.primary_key
|
||||
|
||||
if any_case:
|
||||
search_attr = getattr(self.sql_model, key)
|
||||
result = session.query(self.sql_model).filter(func.lower(search_attr) == key.lower()).one_or_none()
|
||||
result = self.session.query(self.sql_model).filter(func.lower(search_attr) == key.lower()).one_or_none()
|
||||
else:
|
||||
result = session.query(self.sql_model).filter_by(**{key: value}).one_or_none()
|
||||
result = self.session.query(self.sql_model).filter_by(**{key: value}).one_or_none()
|
||||
|
||||
if not result:
|
||||
return
|
||||
|
@ -121,7 +113,7 @@ class BaseAccessModel(Generic[T, D]):
|
|||
return eff_schema.from_orm(result)
|
||||
|
||||
def get(
|
||||
self, session: Session, match_value: str, match_key: str = None, limit=1, any_case=False, override_schema=None
|
||||
self, match_value: str, match_key: str = None, limit=1, any_case=False, override_schema=None
|
||||
) -> T | list[T]:
|
||||
"""Retrieves an entry from the database by matching a key/value pair. If no
|
||||
key is provided the class objects primary key will be used to match against.
|
||||
|
@ -141,10 +133,13 @@ class BaseAccessModel(Generic[T, D]):
|
|||
if any_case:
|
||||
search_attr = getattr(self.sql_model, match_key)
|
||||
result = (
|
||||
session.query(self.sql_model).filter(func.lower(search_attr) == match_value.lower()).limit(limit).all()
|
||||
self.session.query(self.sql_model)
|
||||
.filter(func.lower(search_attr) == match_value.lower())
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
else:
|
||||
result = session.query(self.sql_model).filter_by(**{match_key: match_value}).limit(limit).all()
|
||||
result = self.session.query(self.sql_model).filter_by(**{match_key: match_value}).limit(limit).all()
|
||||
|
||||
eff_schema = override_schema or self.schema
|
||||
|
||||
|
@ -156,7 +151,7 @@ class BaseAccessModel(Generic[T, D]):
|
|||
|
||||
return [eff_schema.from_orm(x) for x in result]
|
||||
|
||||
def create(self, session: Session, document: T) -> T:
|
||||
def create(self, document: T) -> T:
|
||||
"""Creates a new database entry for the given SQL Alchemy Model.
|
||||
|
||||
Args:
|
||||
|
@ -167,17 +162,17 @@ class BaseAccessModel(Generic[T, D]):
|
|||
dict: A dictionary representation of the database entry
|
||||
"""
|
||||
document = document if isinstance(document, dict) else document.dict()
|
||||
new_document = self.sql_model(session=session, **document)
|
||||
session.add(new_document)
|
||||
session.commit()
|
||||
session.refresh(new_document)
|
||||
new_document = self.sql_model(session=self.session, **document)
|
||||
self.session.add(new_document)
|
||||
self.session.commit()
|
||||
self.session.refresh(new_document)
|
||||
|
||||
if self.observers:
|
||||
self.update_observers()
|
||||
|
||||
return self.schema.from_orm(new_document)
|
||||
|
||||
def update(self, session: Session, match_value: str, new_data: dict) -> T:
|
||||
def update(self, match_value: str, new_data: dict) -> T:
|
||||
"""Update a database entry.
|
||||
Args:
|
||||
session (Session): Database Session
|
||||
|
@ -189,19 +184,19 @@ class BaseAccessModel(Generic[T, D]):
|
|||
"""
|
||||
new_data = new_data if isinstance(new_data, dict) else new_data.dict()
|
||||
|
||||
entry = self._query_one(session=session, match_value=match_value)
|
||||
entry.update(session=session, **new_data)
|
||||
entry = self._query_one(match_value=match_value)
|
||||
entry.update(session=self.session, **new_data)
|
||||
|
||||
if self.observers:
|
||||
self.update_observers()
|
||||
|
||||
session.commit()
|
||||
self.session.commit()
|
||||
return self.schema.from_orm(entry)
|
||||
|
||||
def patch(self, session: Session, match_value: str, new_data: dict) -> T:
|
||||
def patch(self, match_value: str, new_data: dict) -> T:
|
||||
new_data = new_data if isinstance(new_data, dict) else new_data.dict()
|
||||
|
||||
entry = self._query_one(session=session, match_value=match_value)
|
||||
entry = self._query_one(match_value=match_value)
|
||||
|
||||
if not entry:
|
||||
return
|
||||
|
@ -209,43 +204,43 @@ class BaseAccessModel(Generic[T, D]):
|
|||
entry_as_dict = self.schema.from_orm(entry).dict()
|
||||
entry_as_dict.update(new_data)
|
||||
|
||||
return self.update(session, match_value, entry_as_dict)
|
||||
return self.update(match_value, entry_as_dict)
|
||||
|
||||
def delete(self, session: Session, primary_key_value) -> D:
|
||||
result = session.query(self.sql_model).filter_by(**{self.primary_key: primary_key_value}).one()
|
||||
def delete(self, primary_key_value) -> D:
|
||||
result = self.session.query(self.sql_model).filter_by(**{self.primary_key: primary_key_value}).one()
|
||||
results_as_model = self.schema.from_orm(result)
|
||||
|
||||
session.delete(result)
|
||||
session.commit()
|
||||
self.session.delete(result)
|
||||
self.session.commit()
|
||||
|
||||
if self.observers:
|
||||
self.update_observers()
|
||||
|
||||
return results_as_model
|
||||
|
||||
def delete_all(self, session: Session) -> None:
|
||||
session.query(self.sql_model).delete()
|
||||
session.commit()
|
||||
def delete_all(self) -> None:
|
||||
self.session.query(self.sql_model).delete()
|
||||
self.session.commit()
|
||||
|
||||
if self.observers:
|
||||
self.update_observers()
|
||||
|
||||
def count_all(self, session: Session, match_key=None, match_value=None) -> int:
|
||||
def count_all(self, match_key=None, match_value=None) -> int:
|
||||
if None in [match_key, match_value]:
|
||||
return session.query(self.sql_model).count()
|
||||
return self.session.query(self.sql_model).count()
|
||||
else:
|
||||
return session.query(self.sql_model).filter_by(**{match_key: match_value}).count()
|
||||
return self.session.query(self.sql_model).filter_by(**{match_key: match_value}).count()
|
||||
|
||||
def _count_attribute(
|
||||
self, session: Session, attribute_name: str, attr_match: str = None, count=True, override_schema=None
|
||||
self, attribute_name: str, attr_match: str = None, count=True, override_schema=None
|
||||
) -> Union[int, T]:
|
||||
eff_schema = override_schema or self.schema
|
||||
# attr_filter = getattr(self.sql_model, attribute_name)
|
||||
|
||||
if count:
|
||||
return session.query(self.sql_model).filter(attribute_name == attr_match).count() # noqa: 711
|
||||
return self.session.query(self.sql_model).filter(attribute_name == attr_match).count() # noqa: 711
|
||||
else:
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in session.query(self.sql_model).filter(attribute_name == attr_match).all() # noqa: 711
|
||||
for x in self.session.query(self.sql_model).filter(attribute_name == attr_match).all() # noqa: 711
|
||||
]
|
145
mealie/db/data_access_layer/access_model_factory.py
Normal file
145
mealie/db/data_access_layer/access_model_factory.py
Normal file
|
@ -0,0 +1,145 @@
|
|||
from functools import cached_property
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from mealie.db.models.event import Event, EventNotification
|
||||
from mealie.db.models.group import Group, GroupMealPlan
|
||||
from mealie.db.models.group.cookbook import CookBook
|
||||
from mealie.db.models.group.invite_tokens import GroupInviteToken
|
||||
from mealie.db.models.group.preferences import GroupPreferencesModel
|
||||
from mealie.db.models.group.webhooks import GroupWebhooksModel
|
||||
from mealie.db.models.recipe.category import Category
|
||||
from mealie.db.models.recipe.comment import RecipeComment
|
||||
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
|
||||
from mealie.db.models.recipe.recipe import RecipeModel
|
||||
from mealie.db.models.recipe.tag import Tag
|
||||
from mealie.db.models.settings import SiteSettings
|
||||
from mealie.db.models.sign_up import SignUp
|
||||
from mealie.db.models.users import LongLiveToken, User
|
||||
from mealie.schema.admin import SiteSettings as SiteSettingsSchema
|
||||
from mealie.schema.cookbook.cookbook import ReadCookBook
|
||||
from mealie.schema.events import Event as EventSchema
|
||||
from mealie.schema.events import EventNotificationIn
|
||||
from mealie.schema.group.group_preferences import ReadGroupPreferences
|
||||
from mealie.schema.group.invite_token import ReadInviteToken
|
||||
from mealie.schema.group.webhook import ReadWebhook
|
||||
from mealie.schema.meal_plan.new_meal import ReadPlanEntry
|
||||
from mealie.schema.recipe import CommentOut, Recipe, RecipeCategoryResponse, RecipeTagResponse
|
||||
from mealie.schema.recipe.recipe_ingredient import IngredientFood, IngredientUnit
|
||||
from mealie.schema.user import GroupInDB, LongLiveTokenInDB, PrivateUser, SignUpOut
|
||||
|
||||
from ._access_model import AccessModel
|
||||
from .group_access_model import GroupDataAccessModel
|
||||
from .meal_access_model import MealDataAccessModel
|
||||
from .recipe_access_model import RecipeDataAccessModel
|
||||
from .user_access_model import UserDataAccessModel
|
||||
|
||||
pk_id = "id"
|
||||
pk_slug = "slug"
|
||||
pk_token = "token"
|
||||
|
||||
|
||||
class CategoryDataAccessModel(AccessModel):
|
||||
def get_empty(self):
|
||||
return self.session.query(Category).filter(~Category.recipes.any()).all()
|
||||
|
||||
|
||||
class TagsDataAccessModel(AccessModel):
|
||||
def get_empty(self):
|
||||
return self.session.query(Tag).filter(~Tag.recipes.any()).all()
|
||||
|
||||
|
||||
class Database:
|
||||
def __init__(self, session: Session) -> None:
|
||||
"""
|
||||
`DatabaseAccessLayer` class is the data access layer for all database actions within
|
||||
Mealie. Database uses composition from classes derived from AccessModel. These
|
||||
can be substantiated from the AccessModel class or through inheritance when
|
||||
additional methods are required.
|
||||
"""
|
||||
|
||||
self.session = session
|
||||
|
||||
# ================================================================
|
||||
# Recipe Items
|
||||
|
||||
@cached_property
|
||||
def recipes(self) -> RecipeDataAccessModel:
|
||||
return RecipeDataAccessModel(self.session, pk_slug, RecipeModel, Recipe)
|
||||
|
||||
@cached_property
|
||||
def ingredient_foods(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, IngredientFoodModel, IngredientFood)
|
||||
|
||||
@cached_property
|
||||
def ingredient_units(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, IngredientUnitModel, IngredientUnit)
|
||||
|
||||
@cached_property
|
||||
def comments(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, RecipeComment, CommentOut)
|
||||
|
||||
@cached_property
|
||||
def categories(self) -> CategoryDataAccessModel:
|
||||
return CategoryDataAccessModel(self.session, pk_id, Category, RecipeCategoryResponse)
|
||||
|
||||
@cached_property
|
||||
def tags(self) -> TagsDataAccessModel:
|
||||
return TagsDataAccessModel(self.session, pk_id, Tag, RecipeTagResponse)
|
||||
|
||||
# ================================================================
|
||||
# Site Items
|
||||
|
||||
@cached_property
|
||||
def settings(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, SiteSettings, SiteSettingsSchema)
|
||||
|
||||
@cached_property
|
||||
def sign_up(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, SignUp, SignUpOut)
|
||||
|
||||
@cached_property
|
||||
def event_notifications(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, EventNotification, EventNotificationIn)
|
||||
|
||||
@cached_property
|
||||
def events(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, Event, EventSchema)
|
||||
|
||||
# ================================================================
|
||||
# User Items
|
||||
|
||||
@cached_property
|
||||
def users(self) -> UserDataAccessModel:
|
||||
return UserDataAccessModel(self.session, pk_id, User, PrivateUser)
|
||||
|
||||
@cached_property
|
||||
def api_tokens(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, LongLiveToken, LongLiveTokenInDB)
|
||||
|
||||
# ================================================================
|
||||
# Group Items
|
||||
|
||||
@cached_property
|
||||
def groups(self) -> GroupDataAccessModel:
|
||||
return GroupDataAccessModel(self.session, pk_id, Group, GroupInDB)
|
||||
|
||||
@cached_property
|
||||
def group_invite_tokens(self) -> AccessModel:
|
||||
return AccessModel(self.session, "token", GroupInviteToken, ReadInviteToken)
|
||||
|
||||
@cached_property
|
||||
def group_preferences(self) -> AccessModel:
|
||||
return AccessModel(self.session, "group_id", GroupPreferencesModel, ReadGroupPreferences)
|
||||
|
||||
@cached_property
|
||||
def meals(self) -> MealDataAccessModel:
|
||||
return MealDataAccessModel(self.session, pk_id, GroupMealPlan, ReadPlanEntry)
|
||||
|
||||
@cached_property
|
||||
def cookbooks(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, CookBook, ReadCookBook)
|
||||
|
||||
@cached_property
|
||||
def webhooks(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, GroupWebhooksModel, ReadWebhook)
|
|
@ -1,98 +0,0 @@
|
|||
from logging import getLogger
|
||||
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.data_access_layer.group_access_model import GroupDataAccessModel
|
||||
from mealie.db.data_access_layer.meal_access_model import MealDataAccessModel
|
||||
from mealie.db.models.event import Event, EventNotification
|
||||
from mealie.db.models.group import Group, GroupMealPlan
|
||||
from mealie.db.models.group.cookbook import CookBook
|
||||
from mealie.db.models.group.invite_tokens import GroupInviteToken
|
||||
from mealie.db.models.group.preferences import GroupPreferencesModel
|
||||
from mealie.db.models.group.shopping_list import ShoppingList
|
||||
from mealie.db.models.group.webhooks import GroupWebhooksModel
|
||||
from mealie.db.models.recipe.category import Category
|
||||
from mealie.db.models.recipe.comment import RecipeComment
|
||||
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
|
||||
from mealie.db.models.recipe.recipe import RecipeModel, Tag
|
||||
from mealie.db.models.settings import SiteSettings
|
||||
from mealie.db.models.sign_up import SignUp
|
||||
from mealie.db.models.users import LongLiveToken, User
|
||||
from mealie.schema.admin import SiteSettings as SiteSettingsSchema
|
||||
from mealie.schema.cookbook import ReadCookBook
|
||||
from mealie.schema.events import Event as EventSchema
|
||||
from mealie.schema.events import EventNotificationIn
|
||||
from mealie.schema.group.group_preferences import ReadGroupPreferences
|
||||
from mealie.schema.group.invite_token import ReadInviteToken
|
||||
from mealie.schema.group.webhook import ReadWebhook
|
||||
from mealie.schema.meal_plan import ShoppingListOut
|
||||
from mealie.schema.meal_plan.new_meal import ReadPlanEntry
|
||||
from mealie.schema.recipe import (
|
||||
CommentOut,
|
||||
IngredientFood,
|
||||
IngredientUnit,
|
||||
Recipe,
|
||||
RecipeCategoryResponse,
|
||||
RecipeTagResponse,
|
||||
)
|
||||
from mealie.schema.user import GroupInDB, LongLiveTokenInDB, PrivateUser, SignUpOut
|
||||
|
||||
from ._base_access_model import BaseAccessModel
|
||||
from .recipe_access_model import RecipeDataAccessModel
|
||||
from .user_access_model import UserDataAccessModel
|
||||
|
||||
logger = getLogger()
|
||||
|
||||
|
||||
pk_id = "id"
|
||||
pk_slug = "slug"
|
||||
pk_token = "token"
|
||||
|
||||
|
||||
class CategoryDataAccessModel(BaseAccessModel):
|
||||
def get_empty(self, session: Session):
|
||||
return session.query(Category).filter(~Category.recipes.any()).all()
|
||||
|
||||
|
||||
class TagsDataAccessModel(BaseAccessModel):
|
||||
def get_empty(self, session: Session):
|
||||
return session.query(Tag).filter(~Tag.recipes.any()).all()
|
||||
|
||||
|
||||
class DatabaseAccessLayer:
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
`DatabaseAccessLayer` class is the data access layer for all database actions within
|
||||
Mealie. Database uses composition from classes derived from BaseAccessModel. These
|
||||
can be substantiated from the BaseAccessModel class or through inheritance when
|
||||
additional methods are required.
|
||||
"""
|
||||
|
||||
# Recipes
|
||||
self.recipes = RecipeDataAccessModel(pk_slug, RecipeModel, Recipe)
|
||||
self.ingredient_foods = BaseAccessModel(pk_id, IngredientFoodModel, IngredientFood)
|
||||
self.ingredient_units = BaseAccessModel(pk_id, IngredientUnitModel, IngredientUnit)
|
||||
self.comments = BaseAccessModel(pk_id, RecipeComment, CommentOut)
|
||||
|
||||
# Tags and Categories
|
||||
self.categories = CategoryDataAccessModel(pk_slug, Category, RecipeCategoryResponse)
|
||||
self.tags = TagsDataAccessModel(pk_slug, Tag, RecipeTagResponse)
|
||||
|
||||
# Site
|
||||
self.settings = BaseAccessModel(pk_id, SiteSettings, SiteSettingsSchema)
|
||||
self.sign_ups = BaseAccessModel(pk_token, SignUp, SignUpOut)
|
||||
self.event_notifications = BaseAccessModel(pk_id, EventNotification, EventNotificationIn)
|
||||
self.events = BaseAccessModel(pk_id, Event, EventSchema)
|
||||
|
||||
# Users
|
||||
self.users = UserDataAccessModel(pk_id, User, PrivateUser)
|
||||
self.api_tokens = BaseAccessModel(pk_id, LongLiveToken, LongLiveTokenInDB)
|
||||
|
||||
# Group Data
|
||||
self.groups = GroupDataAccessModel(pk_id, Group, GroupInDB)
|
||||
self.group_tokens = BaseAccessModel("token", GroupInviteToken, ReadInviteToken)
|
||||
self.meals = MealDataAccessModel(pk_id, GroupMealPlan, ReadPlanEntry)
|
||||
self.webhooks = BaseAccessModel(pk_id, GroupWebhooksModel, ReadWebhook)
|
||||
self.shopping_lists = BaseAccessModel(pk_id, ShoppingList, ShoppingListOut)
|
||||
self.cookbooks = BaseAccessModel(pk_id, CookBook, ReadCookBook)
|
||||
self.group_preferences = BaseAccessModel("group_id", GroupPreferencesModel, ReadGroupPreferences)
|
|
@ -4,10 +4,10 @@ from mealie.db.models.group import Group
|
|||
from mealie.schema.meal_plan.meal import MealPlanOut
|
||||
from mealie.schema.user.user import GroupInDB
|
||||
|
||||
from ._base_access_model import BaseAccessModel
|
||||
from ._access_model import AccessModel
|
||||
|
||||
|
||||
class GroupDataAccessModel(BaseAccessModel[GroupInDB, Group]):
|
||||
class GroupDataAccessModel(AccessModel[GroupInDB, Group]):
|
||||
def get_meals(self, session: Session, match_value: str, match_key: str = "name") -> list[MealPlanOut]:
|
||||
"""A Helper function to get the group from the database and return a sorted list of
|
||||
|
||||
|
|
|
@ -1,26 +1,24 @@
|
|||
from datetime import date
|
||||
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.models.group import GroupMealPlan
|
||||
from mealie.schema.meal_plan.new_meal import ReadPlanEntry
|
||||
|
||||
from ._base_access_model import BaseAccessModel
|
||||
from ._access_model import AccessModel
|
||||
|
||||
|
||||
class MealDataAccessModel(BaseAccessModel[ReadPlanEntry, GroupMealPlan]):
|
||||
def get_slice(self, session: Session, start: date, end: date, group_id: int) -> list[ReadPlanEntry]:
|
||||
class MealDataAccessModel(AccessModel[ReadPlanEntry, GroupMealPlan]):
|
||||
def get_slice(self, start: date, end: date, group_id: int) -> list[ReadPlanEntry]:
|
||||
start = start.strftime("%Y-%m-%d")
|
||||
end = end.strftime("%Y-%m-%d")
|
||||
qry = session.query(GroupMealPlan).filter(
|
||||
qry = self.session.query(GroupMealPlan).filter(
|
||||
GroupMealPlan.date.between(start, end),
|
||||
GroupMealPlan.group_id == group_id,
|
||||
)
|
||||
|
||||
return [self.schema.from_orm(x) for x in qry.all()]
|
||||
|
||||
def get_today(self, session: Session, group_id: int) -> list[ReadPlanEntry]:
|
||||
def get_today(self, group_id: int) -> list[ReadPlanEntry]:
|
||||
today = date.today()
|
||||
qry = session.query(GroupMealPlan).filter(GroupMealPlan.date == today, GroupMealPlan.group_id == group_id)
|
||||
qry = self.session.query(GroupMealPlan).filter(GroupMealPlan.date == today, GroupMealPlan.group_id == group_id)
|
||||
|
||||
return [self.schema.from_orm(x) for x in qry.all()]
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
from random import randint
|
||||
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.models.recipe.recipe import RecipeModel
|
||||
from mealie.db.models.recipe.settings import RecipeSettings
|
||||
from mealie.schema.recipe import Recipe
|
||||
|
||||
from ._base_access_model import BaseAccessModel
|
||||
from ._access_model import AccessModel
|
||||
|
||||
|
||||
class RecipeDataAccessModel(BaseAccessModel[Recipe, RecipeModel]):
|
||||
def get_all_public(self, session: Session, limit: int = None, order_by: str = None, start=0, override_schema=None):
|
||||
class RecipeDataAccessModel(AccessModel[Recipe, RecipeModel]):
|
||||
def get_all_public(self, limit: int = None, order_by: str = None, start=0, override_schema=None):
|
||||
eff_schema = override_schema or self.schema
|
||||
|
||||
if order_by:
|
||||
|
@ -18,7 +16,7 @@ class RecipeDataAccessModel(BaseAccessModel[Recipe, RecipeModel]):
|
|||
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in session.query(self.sql_model)
|
||||
for x in self.session.query(self.sql_model)
|
||||
.join(RecipeSettings)
|
||||
.filter(RecipeSettings.public == True) # noqa: 711
|
||||
.order_by(order_attr.desc())
|
||||
|
@ -29,7 +27,7 @@ class RecipeDataAccessModel(BaseAccessModel[Recipe, RecipeModel]):
|
|||
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in session.query(self.sql_model)
|
||||
for x in self.session.query(self.sql_model)
|
||||
.join(RecipeSettings)
|
||||
.filter(RecipeSettings.public == True) # noqa: 711
|
||||
.offset(start)
|
||||
|
@ -37,23 +35,25 @@ class RecipeDataAccessModel(BaseAccessModel[Recipe, RecipeModel]):
|
|||
.all()
|
||||
]
|
||||
|
||||
def update_image(self, session: Session, slug: str, _: str = None) -> str:
|
||||
entry: RecipeModel = self._query_one(session, match_value=slug)
|
||||
def update_image(self, slug: str, _: str = None) -> str:
|
||||
entry: RecipeModel = self._query_one(match_value=slug)
|
||||
entry.image = randint(0, 255)
|
||||
session.commit()
|
||||
self.session.commit()
|
||||
|
||||
return entry.image
|
||||
|
||||
def count_uncategorized(self, session: Session, count=True, override_schema=None) -> int:
|
||||
def count_uncategorized(self, count=True, override_schema=None) -> int:
|
||||
return self._count_attribute(
|
||||
session,
|
||||
attribute_name=RecipeModel.recipe_category,
|
||||
attr_match=None,
|
||||
count=count,
|
||||
override_schema=override_schema,
|
||||
)
|
||||
|
||||
def count_untagged(self, session: Session, count=True, override_schema=None) -> int:
|
||||
def count_untagged(self, count=True, override_schema=None) -> int:
|
||||
return self._count_attribute(
|
||||
session, attribute_name=RecipeModel.tags, attr_match=None, count=count, override_schema=override_schema
|
||||
attribute_name=RecipeModel.tags,
|
||||
attr_match=None,
|
||||
count=count,
|
||||
override_schema=override_schema,
|
||||
)
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
from mealie.db.models.users import User
|
||||
from mealie.schema.user.user import PrivateUser
|
||||
|
||||
from ._base_access_model import BaseAccessModel
|
||||
from ._access_model import AccessModel
|
||||
|
||||
|
||||
class UserDataAccessModel(BaseAccessModel[PrivateUser, User]):
|
||||
class UserDataAccessModel(AccessModel[PrivateUser, User]):
|
||||
def update_password(self, session, id, password: str):
|
||||
entry = self._query_one(session=session, match_value=id)
|
||||
entry = self._query_one(match_value=id)
|
||||
entry.update_password(password)
|
||||
session.commit()
|
||||
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from ..data_access_layer import DatabaseAccessLayer
|
||||
from mealie.db.data_access_layer.access_model_factory import Database
|
||||
|
||||
CWD = Path(__file__).parent
|
||||
|
||||
|
@ -20,15 +18,15 @@ def get_default_units():
|
|||
return units
|
||||
|
||||
|
||||
def default_recipe_unit_init(db: DatabaseAccessLayer, session: Session) -> None:
|
||||
def default_recipe_unit_init(db: Database) -> None:
|
||||
for unit in get_default_units():
|
||||
try:
|
||||
db.ingredient_units.create(session, unit)
|
||||
db.ingredient_units.create(unit)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
for food in get_default_foods():
|
||||
try:
|
||||
db.ingredient_foods.create(session, food)
|
||||
db.ingredient_foods.create(food)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
from functools import lru_cache
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .data_access_layer import DatabaseAccessLayer
|
||||
|
||||
db = DatabaseAccessLayer()
|
||||
from .data_access_layer.access_model_factory import Database
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_database():
|
||||
return db
|
||||
def get_database(session: Session):
|
||||
return Database(session)
|
||||
|
|
|
@ -10,7 +10,7 @@ def sql_global_init(db_url: str):
|
|||
if "sqlite" in db_url:
|
||||
connect_args["check_same_thread"] = False
|
||||
|
||||
engine = sa.create_engine(db_url, echo=False, connect_args=connect_args)
|
||||
engine = sa.create_engine(db_url, echo=False, connect_args=connect_args, pool_pre_ping=True)
|
||||
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
from sqlalchemy.orm import Session
|
||||
|
||||
from mealie.core import root_logger
|
||||
from mealie.core.config import settings
|
||||
from mealie.core.security import hash_password
|
||||
from mealie.db.data_access_layer.access_model_factory import Database
|
||||
from mealie.db.data_initialization.init_units_foods import default_recipe_unit_init
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import create_session, engine
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
from mealie.schema.admin import SiteSettings
|
||||
|
@ -21,30 +20,24 @@ def create_all_models():
|
|||
SqlAlchemyBase.metadata.create_all(engine)
|
||||
|
||||
|
||||
def init_db(session: Session = None) -> None:
|
||||
create_all_models()
|
||||
|
||||
if not session:
|
||||
session = create_session()
|
||||
|
||||
with session:
|
||||
default_group_init(session)
|
||||
default_settings_init(session)
|
||||
default_user_init(session)
|
||||
default_recipe_unit_init(db, session)
|
||||
def init_db(db: Database) -> None:
|
||||
default_group_init(db)
|
||||
default_settings_init(db)
|
||||
default_user_init(db)
|
||||
default_recipe_unit_init(db)
|
||||
|
||||
|
||||
def default_settings_init(session: Session):
|
||||
document = db.settings.create(session, SiteSettings().dict())
|
||||
def default_settings_init(db: Database):
|
||||
document = db.settings.create(SiteSettings().dict())
|
||||
logger.info(f"Created Site Settings: \n {document}")
|
||||
|
||||
|
||||
def default_group_init(session: Session):
|
||||
def default_group_init(db: Database):
|
||||
logger.info("Generating Default Group")
|
||||
create_new_group(session, GroupBase(name=settings.DEFAULT_GROUP))
|
||||
create_new_group(db, GroupBase(name=settings.DEFAULT_GROUP))
|
||||
|
||||
|
||||
def default_user_init(session: Session):
|
||||
def default_user_init(db: Database):
|
||||
default_user = {
|
||||
"full_name": "Change Me",
|
||||
"username": "admin",
|
||||
|
@ -55,21 +48,26 @@ def default_user_init(session: Session):
|
|||
}
|
||||
|
||||
logger.info("Generating Default User")
|
||||
db.users.create(session, default_user)
|
||||
db.users.create(default_user)
|
||||
|
||||
|
||||
def main():
|
||||
create_all_models()
|
||||
|
||||
session = create_session()
|
||||
db = get_database(session)
|
||||
|
||||
try:
|
||||
session = create_session()
|
||||
init_user = db.users.get(session, "1", "id")
|
||||
init_user = db.users.get("1", "id")
|
||||
except Exception:
|
||||
init_db()
|
||||
init_db(db)
|
||||
return
|
||||
|
||||
if init_user:
|
||||
logger.info("Database Exists")
|
||||
else:
|
||||
logger.info("Database Doesn't Exists, Initializing...")
|
||||
init_db()
|
||||
init_db(db)
|
||||
create_general_event("Initialize Database", "Initialize database with default values", session)
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from sqlalchemy import Column, ForeignKey, Integer, String, orm
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, orm
|
||||
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
|
||||
|
@ -11,6 +11,7 @@ class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
|
|||
name = Column(String)
|
||||
description = Column(String)
|
||||
abbreviation = Column(String)
|
||||
fraction = Column(Boolean)
|
||||
ingredients = orm.relationship("RecipeIngredient", back_populates="unit")
|
||||
|
||||
@auto_init()
|
||||
|
|
|
@ -2,7 +2,7 @@ from fastapi import Depends
|
|||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import AdminAPIRouter
|
||||
from mealie.schema.events import EventsOut
|
||||
|
@ -15,18 +15,20 @@ logger = get_logger()
|
|||
@router.get("", response_model=EventsOut)
|
||||
async def get_events(session: Session = Depends(generate_session)):
|
||||
""" Get event from the Database """
|
||||
# Get Item
|
||||
return EventsOut(total=db.events.count_all(session), events=db.events.get_all(session, order_by="time_stamp"))
|
||||
db = get_database(session)
|
||||
|
||||
return EventsOut(total=db.events.count_all(), events=db.events.get_all(order_by="time_stamp"))
|
||||
|
||||
|
||||
@router.delete("")
|
||||
async def delete_events(session: Session = Depends(generate_session)):
|
||||
""" Get event from the Database """
|
||||
# Get Item
|
||||
db = get_database(session)
|
||||
return db.events.delete_all(session)
|
||||
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def delete_event(id: int, session: Session = Depends(generate_session)):
|
||||
""" Delete event from the Database """
|
||||
db = get_database(session)
|
||||
return db.events.delete(session, id)
|
||||
|
|
|
@ -4,7 +4,7 @@ from fastapi import Depends, status
|
|||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import AdminAPIRouter
|
||||
from mealie.schema.events import EventNotificationIn, EventNotificationOut, TestEvent
|
||||
|
@ -21,8 +21,9 @@ async def create_event_notification(
|
|||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Create event_notification in the Database """
|
||||
db = get_database(session)
|
||||
|
||||
return db.event_notifications.create(session, event_data)
|
||||
return db.event_notifications.create(event_data)
|
||||
|
||||
|
||||
@router.post("/notifications/test")
|
||||
|
@ -31,9 +32,10 @@ async def test_notification_route(
|
|||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Create event_notification in the Database """
|
||||
db = get_database(session)
|
||||
|
||||
if test_data.id:
|
||||
event_obj: EventNotificationIn = db.event_notifications.get(session, test_data.id)
|
||||
event_obj: EventNotificationIn = db.event_notifications.get(test_data.id)
|
||||
test_data.test_url = event_obj.notification_url
|
||||
|
||||
try:
|
||||
|
@ -46,8 +48,8 @@ async def test_notification_route(
|
|||
@router.get("/notifications", response_model=list[EventNotificationOut])
|
||||
async def get_all_event_notification(session: Session = Depends(generate_session)):
|
||||
""" Get all event_notification from the Database """
|
||||
# Get Item
|
||||
return db.event_notifications.get_all(session, override_schema=EventNotificationOut)
|
||||
db = get_database(session)
|
||||
return db.event_notifications.get_all(override_schema=EventNotificationOut)
|
||||
|
||||
|
||||
@router.put("/notifications/{id}")
|
||||
|
@ -61,4 +63,5 @@ async def update_event_notification(id: int, session: Session = Depends(generate
|
|||
async def delete_event_notification(id: int, session: Session = Depends(generate_session)):
|
||||
""" Delete event_notification from the Database """
|
||||
# Delete Item
|
||||
return db.event_notifications.delete(session, id)
|
||||
db = get_database(session)
|
||||
return db.event_notifications.delete(id)
|
||||
|
|
|
@ -28,11 +28,11 @@ async def get_app_info():
|
|||
|
||||
@router.get("/statistics", response_model=AppStatistics)
|
||||
async def get_app_statistics(session: Session = Depends(generate_session)):
|
||||
db = get_database()
|
||||
db = get_database(session)
|
||||
return AppStatistics(
|
||||
total_recipes=db.recipes.count_all(session),
|
||||
uncategorized_recipes=db.recipes.count_uncategorized(session),
|
||||
untagged_recipes=db.recipes.count_untagged(session),
|
||||
total_users=db.users.count_all(session),
|
||||
total_groups=db.groups.count_all(session),
|
||||
total_recipes=db.recipes.count_all(),
|
||||
uncategorized_recipes=db.recipes.count_uncategorized(),
|
||||
untagged_recipes=db.recipes.count_untagged(),
|
||||
total_users=db.users.count_all(),
|
||||
total_groups=db.groups.count_all(),
|
||||
)
|
||||
|
|
|
@ -2,7 +2,7 @@ from fastapi import BackgroundTasks, Depends, HTTPException, status
|
|||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.dependencies import get_current_user
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import AdminAPIRouter
|
||||
from mealie.schema.user import GroupBase, GroupInDB, PrivateUser, UpdateGroup
|
||||
|
@ -14,8 +14,9 @@ router = AdminAPIRouter(prefix="/groups")
|
|||
@router.get("", response_model=list[GroupInDB])
|
||||
async def get_all_groups(session: Session = Depends(generate_session)):
|
||||
""" Returns a list of all groups in the database """
|
||||
db = get_database(session)
|
||||
|
||||
return db.groups.get_all(session)
|
||||
return db.groups.get_all()
|
||||
|
||||
|
||||
@router.post("", status_code=status.HTTP_201_CREATED, response_model=GroupInDB)
|
||||
|
@ -25,9 +26,10 @@ async def create_group(
|
|||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Creates a Group in the Database """
|
||||
db = get_database(session)
|
||||
|
||||
try:
|
||||
new_group = db.groups.create(session, group_data.dict())
|
||||
new_group = db.groups.create(group_data.dict())
|
||||
background_tasks.add_task(create_group_event, "Group Created", f"'{group_data.name}' created", session)
|
||||
return new_group
|
||||
except Exception:
|
||||
|
@ -37,7 +39,8 @@ async def create_group(
|
|||
@router.put("/{id}")
|
||||
async def update_group_data(id: int, group_data: UpdateGroup, session: Session = Depends(generate_session)):
|
||||
""" Updates a User Group """
|
||||
db.groups.update(session, id, group_data.dict())
|
||||
db = get_database(session)
|
||||
db.groups.update(id, group_data.dict())
|
||||
|
||||
|
||||
@router.delete("/{id}")
|
||||
|
@ -48,11 +51,12 @@ async def delete_user_group(
|
|||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Removes a user group from the database """
|
||||
db = get_database(session)
|
||||
|
||||
if id == 1:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="DEFAULT_GROUP")
|
||||
|
||||
group: GroupInDB = db.groups.get(session, id)
|
||||
group: GroupInDB = db.groups.get(id)
|
||||
|
||||
if not group:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="GROUP_NOT_FOUND")
|
||||
|
@ -64,4 +68,4 @@ async def delete_user_group(
|
|||
create_group_event, "Group Deleted", f"'{group.name}' deleted by {current_user.full_name}", session
|
||||
)
|
||||
|
||||
db.groups.delete(session, id)
|
||||
db.groups.delete(id)
|
||||
|
|
|
@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
|||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.dependencies import is_logged_in
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import AdminAPIRouter, UserAPIRouter
|
||||
from mealie.schema.recipe import CategoryIn, RecipeCategoryResponse
|
||||
|
@ -15,13 +15,15 @@ admin_router = AdminAPIRouter()
|
|||
@public_router.get("")
|
||||
async def get_all_recipe_categories(session: Session = Depends(generate_session)):
|
||||
""" Returns a list of available categories in the database """
|
||||
return db.categories.get_all_limit_columns(session, ["slug", "name"])
|
||||
db = get_database(session)
|
||||
return db.categories.get_all_limit_columns(fields=["slug", "name"])
|
||||
|
||||
|
||||
@public_router.get("/empty")
|
||||
def get_empty_categories(session: Session = Depends(generate_session)):
|
||||
""" Returns a list of categories that do not contain any recipes"""
|
||||
return db.categories.get_empty(session)
|
||||
db = get_database(session)
|
||||
return db.categories.get_empty()
|
||||
|
||||
|
||||
@public_router.get("/{category}", response_model=RecipeCategoryResponse)
|
||||
|
@ -29,8 +31,9 @@ def get_all_recipes_by_category(
|
|||
category: str, session: Session = Depends(generate_session), is_user: bool = Depends(is_logged_in)
|
||||
):
|
||||
""" Returns a list of recipes associated with the provided category. """
|
||||
db = get_database(session)
|
||||
|
||||
category_obj = db.categories.get(session, category)
|
||||
category_obj = db.categories.get(category)
|
||||
category_obj = RecipeCategoryResponse.from_orm(category_obj)
|
||||
|
||||
if not is_user:
|
||||
|
@ -42,9 +45,10 @@ def get_all_recipes_by_category(
|
|||
@user_router.post("")
|
||||
async def create_recipe_category(category: CategoryIn, session: Session = Depends(generate_session)):
|
||||
""" Creates a Category in the database """
|
||||
db = get_database(session)
|
||||
|
||||
try:
|
||||
return db.categories.create(session, category.dict())
|
||||
return db.categories.create(category.dict())
|
||||
except Exception:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
@ -52,9 +56,10 @@ async def create_recipe_category(category: CategoryIn, session: Session = Depend
|
|||
@admin_router.put("/{category}", response_model=RecipeCategoryResponse)
|
||||
async def update_recipe_category(category: str, new_category: CategoryIn, session: Session = Depends(generate_session)):
|
||||
""" Updates an existing Tag in the database """
|
||||
db = get_database(session)
|
||||
|
||||
try:
|
||||
return db.categories.update(session, category, new_category.dict())
|
||||
return db.categories.update(category, new_category.dict())
|
||||
except Exception:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
@ -66,8 +71,9 @@ async def delete_recipe_category(category: str, session: Session = Depends(gener
|
|||
category does not impact a recipe. The category will be removed
|
||||
from any recipes that contain it
|
||||
"""
|
||||
db = get_database(session)
|
||||
|
||||
try:
|
||||
db.categories.delete(session, category)
|
||||
db.categories.delete(category)
|
||||
except Exception:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
|
|
|
@ -7,12 +7,10 @@ router = APIRouter()
|
|||
|
||||
|
||||
@router.get("", response_model=list[ReadInviteToken])
|
||||
def get_invite_tokens(g_service: GroupSelfService = Depends(GroupSelfService.write_existing)):
|
||||
def get_invite_tokens(g_service: GroupSelfService = Depends(GroupSelfService.private)):
|
||||
return g_service.get_invite_tokens()
|
||||
|
||||
|
||||
@router.post("", response_model=ReadInviteToken, status_code=status.HTTP_201_CREATED)
|
||||
def create_invite_token(
|
||||
uses: CreateInviteToken, g_service: GroupSelfService = Depends(GroupSelfService.write_existing)
|
||||
):
|
||||
def create_invite_token(uses: CreateInviteToken, g_service: GroupSelfService = Depends(GroupSelfService.private)):
|
||||
return g_service.create_invite_token(uses.uses)
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
from fastapi import APIRouter
|
||||
|
||||
from . import crud, helpers, mealplans
|
||||
|
||||
meal_plan_router = APIRouter()
|
||||
|
||||
meal_plan_router.include_router(crud.router)
|
||||
meal_plan_router.include_router(crud.public_router)
|
||||
meal_plan_router.include_router(helpers.router)
|
||||
meal_plan_router.include_router(mealplans.router)
|
|
@ -1,126 +0,0 @@
|
|||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status
|
||||
from sqlalchemy.orm.session import Session
|
||||
from starlette.responses import FileResponse
|
||||
|
||||
from mealie.core.dependencies import get_current_user
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import UserAPIRouter
|
||||
from mealie.schema.meal_plan import MealPlanIn, MealPlanOut
|
||||
from mealie.schema.user import GroupInDB, PrivateUser
|
||||
from mealie.services.events import create_group_event
|
||||
from mealie.services.image import image
|
||||
from mealie.services.meal_services import get_todays_meal, set_mealplan_dates
|
||||
|
||||
router = UserAPIRouter(prefix="/api/meal-plans", tags=["Meal Plan"])
|
||||
public_router = APIRouter(prefix="/api/meal-plans", tags=["Meal Plan"])
|
||||
|
||||
|
||||
@router.get("/all", response_model=list[MealPlanOut])
|
||||
def get_all_meals(
|
||||
current_user: PrivateUser = Depends(get_current_user),
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Returns a list of all available Meal Plan """
|
||||
|
||||
return db.groups.get_meals(session, current_user.group)
|
||||
|
||||
|
||||
@router.get("/this-week", response_model=MealPlanOut)
|
||||
def get_this_week(session: Session = Depends(generate_session), current_user: PrivateUser = Depends(get_current_user)):
|
||||
""" Returns the meal plan data for this week """
|
||||
plans = db.groups.get_meals(session, current_user.group)
|
||||
if plans:
|
||||
return plans[0]
|
||||
|
||||
|
||||
@router.get("/today", tags=["Meal Plan"])
|
||||
def get_today(session: Session = Depends(generate_session), current_user: PrivateUser = Depends(get_current_user)):
|
||||
"""
|
||||
Returns the recipe slug for the meal scheduled for today.
|
||||
If no meal is scheduled nothing is returned
|
||||
"""
|
||||
|
||||
group_in_db: GroupInDB = db.groups.get(session, current_user.group, "name")
|
||||
recipe = get_todays_meal(session, group_in_db)
|
||||
if recipe:
|
||||
return recipe
|
||||
|
||||
|
||||
@public_router.get("/today/image", tags=["Meal Plan"])
|
||||
def get_todays_image(session: Session = Depends(generate_session), group_name: str = "Home"):
|
||||
"""
|
||||
Returns the image for todays meal-plan.
|
||||
"""
|
||||
|
||||
group_in_db: GroupInDB = db.groups.get(session, group_name, "name")
|
||||
recipe = get_todays_meal(session, group_in_db)
|
||||
recipe_image = recipe.image_dir.joinpath(image.ImageOptions.ORIGINAL_IMAGE)
|
||||
|
||||
if not recipe and not recipe_image.exists():
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
||||
|
||||
return FileResponse(recipe_image)
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=MealPlanOut)
|
||||
def get_meal_plan(
|
||||
id,
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Returns a single Meal Plan from the Database """
|
||||
|
||||
return db.meals.get(session, id)
|
||||
|
||||
|
||||
@router.post("/create", status_code=status.HTTP_201_CREATED)
|
||||
def create_meal_plan(
|
||||
background_tasks: BackgroundTasks,
|
||||
data: MealPlanIn,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user: PrivateUser = Depends(get_current_user),
|
||||
):
|
||||
""" Creates a meal plan database entry """
|
||||
set_mealplan_dates(data)
|
||||
background_tasks.add_task(
|
||||
create_group_event, "Meal Plan Created", f"Mealplan Created for '{current_user.group}'", session=session
|
||||
)
|
||||
return db.meals.create(session, data.dict())
|
||||
|
||||
|
||||
@router.put("/{plan_id}")
|
||||
def update_meal_plan(
|
||||
background_tasks: BackgroundTasks,
|
||||
plan_id: str,
|
||||
meal_plan: MealPlanIn,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user: PrivateUser = Depends(get_current_user),
|
||||
):
|
||||
""" Updates a meal plan based off ID """
|
||||
set_mealplan_dates(meal_plan)
|
||||
processed_plan = MealPlanOut(id=plan_id, **meal_plan.dict())
|
||||
try:
|
||||
db.meals.update(session, plan_id, processed_plan.dict())
|
||||
background_tasks.add_task(
|
||||
create_group_event, "Meal Plan Updated", f"Mealplan Updated for '{current_user.group}'", session=session
|
||||
)
|
||||
except Exception:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
@router.delete("/{plan_id}")
|
||||
def delete_meal_plan(
|
||||
background_tasks: BackgroundTasks,
|
||||
plan_id,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user: PrivateUser = Depends(get_current_user),
|
||||
):
|
||||
""" Removes a meal plan from the database """
|
||||
|
||||
try:
|
||||
db.meals.delete(session, plan_id)
|
||||
background_tasks.add_task(
|
||||
create_group_event, "Meal Plan Deleted", f"Mealplan Deleted for '{current_user.group}'", session=session
|
||||
)
|
||||
except Exception:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
|
@ -1,50 +0,0 @@
|
|||
from fastapi import Depends
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.dependencies import get_current_user
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import UserAPIRouter
|
||||
from mealie.schema.meal_plan import ListItem, MealPlanOut, ShoppingListIn, ShoppingListOut
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.schema.user import PrivateUser
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
router = UserAPIRouter(prefix="/api/meal-plans", tags=["Meal Plan"])
|
||||
|
||||
|
||||
@router.get("/{id}/shopping-list")
|
||||
def get_shopping_list(
|
||||
id: str,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user: PrivateUser = Depends(get_current_user),
|
||||
):
|
||||
|
||||
mealplan: MealPlanOut = db.meals.get(session, id)
|
||||
|
||||
all_ingredients = []
|
||||
|
||||
for plan_day in mealplan.plan_days:
|
||||
for meal in plan_day.meals:
|
||||
if not meal.slug:
|
||||
continue
|
||||
|
||||
try:
|
||||
recipe: Recipe = db.recipes.get(session, meal.slug)
|
||||
all_ingredients += recipe.recipe_ingredient
|
||||
except Exception:
|
||||
logger.error("Recipe Not Found")
|
||||
|
||||
new_list = ShoppingListIn(
|
||||
name="MealPlan Shopping List", group=current_user.group, items=[ListItem(text=t.note) for t in all_ingredients]
|
||||
)
|
||||
|
||||
created_list: ShoppingListOut = db.shopping_lists.create(session, new_list)
|
||||
|
||||
mealplan.shopping_list = created_list.id
|
||||
|
||||
db.meals.update(session, mealplan.id, mealplan)
|
||||
|
||||
return created_list
|
|
@ -1,8 +0,0 @@
|
|||
from fastapi import APIRouter
|
||||
|
||||
from mealie.routes.mealplans import crud, helpers
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
router.include_router(crud.router)
|
||||
router.include_router(helpers.router)
|
|
@ -1,7 +1,7 @@
|
|||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.schema.recipe import RecipeSummary
|
||||
|
||||
|
@ -10,9 +10,11 @@ router = APIRouter()
|
|||
|
||||
@router.get("/summary/untagged", response_model=list[RecipeSummary])
|
||||
async def get_untagged_recipes(count: bool = False, session: Session = Depends(generate_session)):
|
||||
return db.recipes.count_untagged(session, count=count, override_schema=RecipeSummary)
|
||||
db = get_database(session)
|
||||
return db.recipes.count_untagged(count=count, override_schema=RecipeSummary)
|
||||
|
||||
|
||||
@router.get("/summary/uncategorized", response_model=list[RecipeSummary])
|
||||
async def get_uncategorized_recipes(count: bool = False, session: Session = Depends(generate_session)):
|
||||
return db.recipes.count_uncategorized(session, count=count, override_schema=RecipeSummary)
|
||||
db = get_database(session)
|
||||
return db.recipes.count_uncategorized(count=count, override_schema=RecipeSummary)
|
||||
|
|
|
@ -4,7 +4,7 @@ from fastapi import Depends, status
|
|||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.dependencies import get_current_user
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import UserAPIRouter
|
||||
from mealie.schema.recipe import CommentOut, CreateComment, SaveComment
|
||||
|
@ -21,9 +21,10 @@ async def create_comment(
|
|||
current_user: PrivateUser = Depends(get_current_user),
|
||||
):
|
||||
""" Create comment in the Database """
|
||||
db = get_database(session)
|
||||
|
||||
new_comment = SaveComment(user=current_user.id, text=new_comment.text, recipe_slug=slug)
|
||||
return db.comments.create(session, new_comment)
|
||||
return db.comments.create(new_comment)
|
||||
|
||||
|
||||
@router.put("/{slug}/comments/{id}")
|
||||
|
@ -34,12 +35,13 @@ async def update_comment(
|
|||
current_user: PrivateUser = Depends(get_current_user),
|
||||
):
|
||||
""" Update comment in the Database """
|
||||
old_comment: CommentOut = db.comments.get(session, id)
|
||||
db = get_database(session)
|
||||
old_comment: CommentOut = db.comments.get(id)
|
||||
|
||||
if current_user.id != old_comment.user.id:
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||
|
||||
return db.comments.update(session, id, new_comment)
|
||||
return db.comments.update(id, new_comment)
|
||||
|
||||
|
||||
@router.delete("/{slug}/comments/{id}")
|
||||
|
@ -47,9 +49,10 @@ async def delete_comment(
|
|||
id: int, session: Session = Depends(generate_session), current_user: PrivateUser = Depends(get_current_user)
|
||||
):
|
||||
""" Delete comment from the Database """
|
||||
comment: CommentOut = db.comments.get(session, id)
|
||||
db = get_database(session)
|
||||
comment: CommentOut = db.comments.get(id)
|
||||
if current_user.id == comment.user.id or current_user.admin:
|
||||
db.comments.delete(session, id)
|
||||
db.comments.delete(id)
|
||||
return
|
||||
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||
|
|
|
@ -5,7 +5,7 @@ from fastapi.datastructures import UploadFile
|
|||
from slugify import slugify
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import UserAPIRouter
|
||||
from mealie.schema.recipe import CreateRecipeByURL, Recipe, RecipeAsset
|
||||
|
@ -32,8 +32,9 @@ def update_recipe_image(
|
|||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Removes an existing image and replaces it with the incoming file. """
|
||||
db = get_database(session)
|
||||
write_image(slug, image, extension)
|
||||
new_version = db.recipes.update_image(session, slug, extension)
|
||||
new_version = db.recipes.update_image(slug, extension)
|
||||
|
||||
return {"image": new_version}
|
||||
|
||||
|
@ -58,7 +59,9 @@ def upload_recipe_asset(
|
|||
if not dest.is_file():
|
||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
recipe: Recipe = db.recipes.get(session, slug)
|
||||
db = get_database(session)
|
||||
|
||||
recipe: Recipe = db.recipes.get(slug)
|
||||
recipe.assets.append(asset_in)
|
||||
db.recipes.update(session, slug, recipe.dict())
|
||||
db.recipes.update(slug, recipe.dict())
|
||||
return asset_in
|
||||
|
|
|
@ -10,7 +10,7 @@ from starlette.responses import FileResponse
|
|||
|
||||
from mealie.core.dependencies import temporary_zip_path
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import UserAPIRouter
|
||||
from mealie.schema.recipe import CreateRecipeByURL, Recipe, RecipeImageTypes
|
||||
|
@ -71,7 +71,9 @@ async def create_recipe_from_zip(
|
|||
with myzip.open(file) as myfile:
|
||||
recipe_image = myfile.read()
|
||||
|
||||
recipe: Recipe = db.recipes.create(session, Recipe(**recipe_dict))
|
||||
db = get_database(session)
|
||||
|
||||
recipe: Recipe = db.recipes.create(Recipe(**recipe_dict))
|
||||
|
||||
write_image(recipe.slug, recipe_image, "webp")
|
||||
|
||||
|
@ -89,7 +91,9 @@ async def get_recipe_as_zip(
|
|||
slug: str, session: Session = Depends(generate_session), temp_path=Depends(temporary_zip_path)
|
||||
):
|
||||
""" Get a Recipe and It's Original Image as a Zip File """
|
||||
recipe: Recipe = db.recipes.get(session, slug)
|
||||
db = get_database(session)
|
||||
|
||||
recipe: Recipe = db.recipes.get(slug)
|
||||
|
||||
image_asset = recipe.image_dir.joinpath(RecipeImageTypes.original.value)
|
||||
|
||||
|
@ -105,14 +109,12 @@ async def get_recipe_as_zip(
|
|||
@user_router.put("/{slug}")
|
||||
def update_recipe(data: Recipe, recipe_service: RecipeService = Depends(RecipeService.write_existing)):
|
||||
""" Updates a recipe by existing slug and data. """
|
||||
|
||||
return recipe_service.update_one(data)
|
||||
|
||||
|
||||
@user_router.patch("/{slug}")
|
||||
def patch_recipe(data: Recipe, recipe_service: RecipeService = Depends(RecipeService.write_existing)):
|
||||
""" Updates a recipe by existing slug and data. """
|
||||
|
||||
return recipe_service.patch_one(data)
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ from fastapi import Depends
|
|||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.dependencies import get_current_user
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import UserAPIRouter
|
||||
from mealie.schema.meal_plan import ShoppingListIn, ShoppingListOut
|
||||
|
@ -18,25 +18,28 @@ async def create_shopping_list(
|
|||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Create Shopping List in the Database """
|
||||
|
||||
db = get_database(session)
|
||||
list_in.group = current_user.group
|
||||
|
||||
return db.shopping_lists.create(session, list_in)
|
||||
return db.shopping_lists.create(list_in)
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=ShoppingListOut)
|
||||
async def get_shopping_list(id: int, session: Session = Depends(generate_session)):
|
||||
""" Get Shopping List from the Database """
|
||||
return db.shopping_lists.get(session, id)
|
||||
db = get_database(session)
|
||||
return db.shopping_lists.get(id)
|
||||
|
||||
|
||||
@router.put("/{id}", response_model=ShoppingListOut)
|
||||
async def update_shopping_list(id: int, new_data: ShoppingListIn, session: Session = Depends(generate_session)):
|
||||
""" Update Shopping List in the Database """
|
||||
return db.shopping_lists.update(session, id, new_data)
|
||||
db = get_database(session)
|
||||
return db.shopping_lists.update(id, new_data)
|
||||
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def delete_shopping_list(id: int, session: Session = Depends(generate_session)):
|
||||
""" Delete Shopping List from the Database """
|
||||
return db.shopping_lists.delete(session, id)
|
||||
db = get_database(session)
|
||||
return db.shopping_lists.delete(id)
|
||||
|
|
|
@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
|||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.dependencies import get_current_user
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import AdminAPIRouter
|
||||
from mealie.schema.admin import SiteSettings
|
||||
|
@ -16,8 +16,9 @@ admin_router = AdminAPIRouter(prefix="/api/site-settings", tags=["Settings"])
|
|||
@public_router.get("")
|
||||
def get_main_settings(session: Session = Depends(generate_session)):
|
||||
""" Returns basic site settings """
|
||||
db = get_database(session)
|
||||
|
||||
return db.settings.get(session, 1)
|
||||
return db.settings.get(1)
|
||||
|
||||
|
||||
@admin_router.put("")
|
||||
|
@ -26,7 +27,8 @@ def update_settings(
|
|||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Returns Site Settings """
|
||||
db.settings.update(session, 1, data.dict())
|
||||
db = get_database(session)
|
||||
db.settings.update(1, data.dict())
|
||||
|
||||
|
||||
@admin_router.post("/webhooks/test")
|
||||
|
@ -35,7 +37,8 @@ def test_webhooks(
|
|||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Run the function to test your webhooks """
|
||||
group_entry: GroupInDB = db.groups.get(session, current_user.group, "name")
|
||||
db = get_database(session)
|
||||
group_entry: GroupInDB = db.groups.get(current_user.group, "name")
|
||||
|
||||
try:
|
||||
post_webhooks(group_entry.id, session)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm.session import Session
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from mealie.core.dependencies import is_logged_in
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import AdminAPIRouter, UserAPIRouter
|
||||
from mealie.schema.recipe import RecipeTagResponse, TagIn
|
||||
|
@ -15,13 +15,15 @@ admin_router = AdminAPIRouter()
|
|||
@public_router.get("")
|
||||
async def get_all_recipe_tags(session: Session = Depends(generate_session)):
|
||||
""" Returns a list of available tags in the database """
|
||||
return db.tags.get_all_limit_columns(session, ["slug", "name"])
|
||||
db = get_database(session)
|
||||
return db.tags.get_all_limit_columns(["slug", "name"])
|
||||
|
||||
|
||||
@public_router.get("/empty")
|
||||
def get_empty_tags(session: Session = Depends(generate_session)):
|
||||
""" Returns a list of tags that do not contain any recipes"""
|
||||
return db.tags.get_empty(session)
|
||||
db = get_database(session)
|
||||
return db.tags.get_empty()
|
||||
|
||||
|
||||
@public_router.get("/{tag}", response_model=RecipeTagResponse)
|
||||
|
@ -29,7 +31,8 @@ def get_all_recipes_by_tag(
|
|||
tag: str, session: Session = Depends(generate_session), is_user: bool = Depends(is_logged_in)
|
||||
):
|
||||
""" Returns a list of recipes associated with the provided tag. """
|
||||
tag_obj = db.tags.get(session, tag)
|
||||
db = get_database(session)
|
||||
tag_obj = db.tags.get(tag)
|
||||
tag_obj = RecipeTagResponse.from_orm(tag_obj)
|
||||
|
||||
if not is_user:
|
||||
|
@ -41,15 +44,15 @@ def get_all_recipes_by_tag(
|
|||
@user_router.post("")
|
||||
async def create_recipe_tag(tag: TagIn, session: Session = Depends(generate_session)):
|
||||
""" Creates a Tag in the database """
|
||||
|
||||
return db.tags.create(session, tag.dict())
|
||||
db = get_database(session)
|
||||
return db.tags.create(tag.dict())
|
||||
|
||||
|
||||
@admin_router.put("/{tag}", response_model=RecipeTagResponse)
|
||||
async def update_recipe_tag(tag: str, new_tag: TagIn, session: Session = Depends(generate_session)):
|
||||
""" Updates an existing Tag in the database """
|
||||
|
||||
return db.tags.update(session, tag, new_tag.dict())
|
||||
db = get_database(session)
|
||||
return db.tags.update(tag, new_tag.dict())
|
||||
|
||||
|
||||
@admin_router.delete("/{tag}")
|
||||
|
@ -59,6 +62,7 @@ async def delete_recipe_tag(tag: str, session: Session = Depends(generate_sessio
|
|||
from any recipes that contain it"""
|
||||
|
||||
try:
|
||||
db.tags.delete(session, tag)
|
||||
db = get_database(session)
|
||||
db.tags.delete(tag)
|
||||
except Exception:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
from fastapi import APIRouter
|
||||
|
||||
from . import food_routes, unit_routes
|
||||
from mealie.services._base_http_service.router_factory import RouterFactory
|
||||
from mealie.services.recipe.recipe_food_service import RecipeFoodService
|
||||
from mealie.services.recipe.recipe_unit_service import RecipeUnitService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
router.include_router(food_routes.router, prefix="/foods", tags=["Recipes: Foods"])
|
||||
router.include_router(unit_routes.router, prefix="/units", tags=["Recipes: Units"])
|
||||
router.include_router(RouterFactory(RecipeFoodService, prefix="/foods", tags=["Recipes: Foods"]))
|
||||
router.include_router(RouterFactory(RecipeUnitService, prefix="/units", tags=["Recipes: Units"]))
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
from fastapi import Depends, status
|
||||
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import Session, generate_session
|
||||
from mealie.routes.routers import UserAPIRouter
|
||||
from mealie.schema.recipe import CreateIngredientFood, IngredientFood
|
||||
|
||||
router = UserAPIRouter()
|
||||
|
||||
|
||||
@router.get("", response_model=list[IngredientFood])
|
||||
async def get_all(
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Get unit from the Database """
|
||||
# Get unit
|
||||
return db.ingredient_foods.get_all(session)
|
||||
|
||||
|
||||
@router.post("", response_model=IngredientFood, status_code=status.HTTP_201_CREATED)
|
||||
async def create_food(unit: CreateIngredientFood, session: Session = Depends(generate_session)):
|
||||
""" Create unit in the Database """
|
||||
|
||||
return db.ingredient_foods.create(session, unit)
|
||||
|
||||
|
||||
@router.get("/{id}")
|
||||
async def get_food(id: str, session: Session = Depends(generate_session)):
|
||||
""" Get unit from the Database """
|
||||
|
||||
return db.ingredient_foods.get(session, id)
|
||||
|
||||
|
||||
@router.put("/{id}")
|
||||
async def update_food(id: str, unit: CreateIngredientFood, session: Session = Depends(generate_session)):
|
||||
""" Update unit in the Database """
|
||||
|
||||
return db.ingredient_foods.update(session, id, unit)
|
||||
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def delete_food(id: str, session: Session = Depends(generate_session)):
|
||||
""" Delete unit from the Database """
|
||||
return db.ingredient_foods.delete(session, id)
|
|
@ -1,44 +0,0 @@
|
|||
from fastapi import Depends, status
|
||||
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import Session, generate_session
|
||||
from mealie.routes.routers import UserAPIRouter
|
||||
from mealie.schema.recipe import CreateIngredientUnit, IngredientUnit
|
||||
|
||||
router = UserAPIRouter()
|
||||
|
||||
|
||||
@router.get("", response_model=list[IngredientUnit])
|
||||
async def get_all(
|
||||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Get unit from the Database """
|
||||
# Get unit
|
||||
return db.ingredient_units.get_all(session)
|
||||
|
||||
|
||||
@router.post("", response_model=IngredientUnit, status_code=status.HTTP_201_CREATED)
|
||||
async def create_unit(unit: CreateIngredientUnit, session: Session = Depends(generate_session)):
|
||||
""" Create unit in the Database """
|
||||
|
||||
return db.ingredient_units.create(session, unit)
|
||||
|
||||
|
||||
@router.get("/{id}")
|
||||
async def get_unit(id: str, session: Session = Depends(generate_session)):
|
||||
""" Get unit from the Database """
|
||||
|
||||
return db.ingredient_units.get(session, id)
|
||||
|
||||
|
||||
@router.put("/{id}")
|
||||
async def update_unit(id: str, unit: CreateIngredientUnit, session: Session = Depends(generate_session)):
|
||||
""" Update unit in the Database """
|
||||
|
||||
return db.ingredient_units.update(session, id, unit)
|
||||
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def delete_unit(id: str, session: Session = Depends(generate_session)):
|
||||
""" Delete unit from the Database """
|
||||
return db.ingredient_units.delete(session, id)
|
|
@ -6,7 +6,7 @@ from sqlalchemy.orm.session import Session
|
|||
|
||||
from mealie.core.dependencies import get_current_user
|
||||
from mealie.core.security import create_access_token
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import UserAPIRouter
|
||||
from mealie.schema.user import CreateToken, LoingLiveTokenIn, LongLiveTokenInDB, PrivateUser
|
||||
|
@ -33,7 +33,9 @@ async def create_api_token(
|
|||
parent_id=current_user.id,
|
||||
)
|
||||
|
||||
new_token_in_db = db.api_tokens.create(session, token_model)
|
||||
db = get_database(session)
|
||||
|
||||
new_token_in_db = db.api_tokens.create(token_model)
|
||||
|
||||
if new_token_in_db:
|
||||
return {"token": token}
|
||||
|
@ -46,13 +48,14 @@ async def delete_api_token(
|
|||
session: Session = Depends(generate_session),
|
||||
):
|
||||
""" Delete api_token from the Database """
|
||||
token: LongLiveTokenInDB = db.api_tokens.get(session, token_id)
|
||||
db = get_database(session)
|
||||
token: LongLiveTokenInDB = db.api_tokens.get(token_id)
|
||||
|
||||
if not token:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, f"Could not locate token with id '{token_id}' in database")
|
||||
|
||||
if token.user.email == current_user.email:
|
||||
deleted_token = db.api_tokens.delete(session, token_id)
|
||||
deleted_token = db.api_tokens.delete(token_id)
|
||||
return {"token_delete": deleted_token.name}
|
||||
else:
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||
|
|
|
@ -4,7 +4,7 @@ from sqlalchemy.orm.session import Session
|
|||
from mealie.core import security
|
||||
from mealie.core.dependencies import get_current_user
|
||||
from mealie.core.security import hash_password
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import AdminAPIRouter, UserAPIRouter
|
||||
from mealie.routes.users._helpers import assert_user_change_allowed
|
||||
|
@ -17,7 +17,8 @@ admin_router = AdminAPIRouter(prefix="")
|
|||
|
||||
@admin_router.get("", response_model=list[UserOut])
|
||||
async def get_all_users(session: Session = Depends(generate_session)):
|
||||
return db.users.get_all(session)
|
||||
db = get_database(session)
|
||||
return db.users.get_all()
|
||||
|
||||
|
||||
@admin_router.post("", response_model=UserOut, status_code=201)
|
||||
|
@ -33,12 +34,14 @@ async def create_user(
|
|||
create_user_event, "User Created", f"Created by {current_user.full_name}", session=session
|
||||
)
|
||||
|
||||
return db.users.create(session, new_user.dict())
|
||||
db = get_database(session)
|
||||
return db.users.create(new_user.dict())
|
||||
|
||||
|
||||
@admin_router.get("/{id}", response_model=UserOut)
|
||||
async def get_user(id: int, session: Session = Depends(generate_session)):
|
||||
return db.users.get(session, id)
|
||||
db = get_database(session)
|
||||
return db.users.get(id)
|
||||
|
||||
|
||||
@admin_router.delete("/{id}")
|
||||
|
@ -56,7 +59,8 @@ def delete_user(
|
|||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="SUPER_USER")
|
||||
|
||||
try:
|
||||
db.users.delete(session, id)
|
||||
db = get_database(session)
|
||||
db.users.delete(id)
|
||||
background_tasks.add_task(create_user_event, "User Deleted", f"User ID: {id}", session=session)
|
||||
except Exception:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
|
@ -87,7 +91,9 @@ async def update_user(
|
|||
# prevent an admin from demoting themself
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||
|
||||
db.users.update(session, id, new_data.dict())
|
||||
db = get_database(session)
|
||||
db.users.update(id, new_data.dict())
|
||||
|
||||
if current_user.id == id:
|
||||
access_token = security.create_access_token(data=dict(sub=new_data.email))
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
|
|
|
@ -2,7 +2,7 @@ from fastapi import Depends
|
|||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.dependencies import get_current_user
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import UserAPIRouter
|
||||
from mealie.routes.users._helpers import assert_user_change_allowed
|
||||
|
@ -14,8 +14,8 @@ user_router = UserAPIRouter()
|
|||
@user_router.get("/{id}/favorites", response_model=UserFavorites)
|
||||
async def get_favorites(id: str, session: Session = Depends(generate_session)):
|
||||
""" Get user's favorite recipes """
|
||||
|
||||
return db.users.get(session, id, override_schema=UserFavorites)
|
||||
db = get_database(session)
|
||||
return db.users.get(id, override_schema=UserFavorites)
|
||||
|
||||
|
||||
@user_router.post("/{id}/favorites/{slug}")
|
||||
|
@ -29,7 +29,8 @@ def add_favorite(
|
|||
assert_user_change_allowed(id, current_user)
|
||||
current_user.favorite_recipes.append(slug)
|
||||
|
||||
db.users.update(session, current_user.id, current_user)
|
||||
db = get_database(session)
|
||||
db.users.update(current_user.id, current_user)
|
||||
|
||||
|
||||
@user_router.delete("/{id}/favorites/{slug}")
|
||||
|
@ -43,6 +44,7 @@ def remove_favorite(
|
|||
assert_user_change_allowed(id, current_user)
|
||||
current_user.favorite_recipes = [x for x in current_user.favorite_recipes if x != slug]
|
||||
|
||||
db.users.update(session, current_user.id, current_user)
|
||||
db = get_database(session)
|
||||
db.users.update(current_user.id, current_user)
|
||||
|
||||
return
|
||||
|
|
|
@ -3,7 +3,7 @@ from sqlalchemy.orm.session import Session
|
|||
|
||||
from mealie.core.config import settings
|
||||
from mealie.core.security import hash_password
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.routers import UserAPIRouter
|
||||
from mealie.schema.user import ChangePassword
|
||||
|
@ -15,7 +15,9 @@ user_router = UserAPIRouter(prefix="")
|
|||
@user_router.put("/{id}/reset-password")
|
||||
async def reset_user_password(id: int, session: Session = Depends(generate_session)):
|
||||
new_password = hash_password(settings.DEFAULT_PASSWORD)
|
||||
db.users.update_password(session, id, new_password)
|
||||
|
||||
db = get_database(session)
|
||||
db.users.update_password(id, new_password)
|
||||
|
||||
|
||||
@user_router.put("/{id}/password")
|
||||
|
|
|
@ -7,7 +7,6 @@ from sqlalchemy.orm.session import Session
|
|||
|
||||
from mealie.core.config import get_app_dirs, get_settings
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.data_access_layer.db_access import DatabaseAccessLayer
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import SessionLocal
|
||||
from mealie.schema.user.user import PrivateUser
|
||||
|
@ -45,8 +44,6 @@ class BaseHttpService(Generic[T, D], ABC):
|
|||
delete_one: Callable = None
|
||||
delete_all: Callable = None
|
||||
|
||||
db_access: DatabaseAccessLayer = None
|
||||
|
||||
# Type Definitions
|
||||
_schema = None
|
||||
|
||||
|
@ -64,7 +61,7 @@ class BaseHttpService(Generic[T, D], ABC):
|
|||
self.background_tasks = background_tasks
|
||||
|
||||
# Static Globals Dependency Injection
|
||||
self.db = get_database()
|
||||
self.db = get_database(session)
|
||||
self.app_dirs = get_app_dirs()
|
||||
self.settings = get_settings()
|
||||
|
||||
|
@ -110,7 +107,7 @@ class BaseHttpService(Generic[T, D], ABC):
|
|||
def group_id(self):
|
||||
# TODO: Populate Group in Private User Call WARNING: May require significant refactoring
|
||||
if not self._group_id_cache:
|
||||
group = self.db.groups.get(self.session, self.user.group, "name")
|
||||
group = self.db.groups.get(self.user.group, "name")
|
||||
self._group_id_cache = group.id
|
||||
return self._group_id_cache
|
||||
|
||||
|
|
|
@ -1,27 +1,37 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.data_access_layer.db_access import DatabaseAccessLayer
|
||||
from mealie.db.data_access_layer._access_model import AccessModel
|
||||
|
||||
C = TypeVar("C", bound=BaseModel)
|
||||
R = TypeVar("R", bound=BaseModel)
|
||||
U = TypeVar("U", bound=BaseModel)
|
||||
DAL = TypeVar("DAL", bound=DatabaseAccessLayer)
|
||||
DAL = TypeVar("DAL", bound=AccessModel)
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
class CrudHttpMixins(Generic[C, R, U]):
|
||||
item: C
|
||||
class CrudHttpMixins(Generic[C, R, U], ABC):
|
||||
item: R
|
||||
session: Session
|
||||
dal: DAL
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def dal(self) -> DAL:
|
||||
...
|
||||
|
||||
def populate_item(self, id: int) -> R:
|
||||
self.item = self.dal.get_one(id)
|
||||
return self.item
|
||||
|
||||
def _create_one(self, data: C, exception_msg="generic-create-error") -> R:
|
||||
try:
|
||||
self.item = self.dal.create(self.session, data)
|
||||
self.item = self.dal.create(data)
|
||||
except Exception as ex:
|
||||
logger.exception(ex)
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail={"message": exception_msg, "exception": str(ex)})
|
||||
|
@ -33,17 +43,26 @@ class CrudHttpMixins(Generic[C, R, U]):
|
|||
return
|
||||
|
||||
target_id = item_id or self.item.id
|
||||
self.item = self.dal.update(self.session, target_id, data)
|
||||
self.item = self.dal.update(target_id, data)
|
||||
|
||||
return self.item
|
||||
|
||||
def _patch_one(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def _delete_one(self, item_id: int = None) -> None:
|
||||
if not self.item:
|
||||
return
|
||||
def _patch_one(self, data: U, item_id: int) -> None:
|
||||
try:
|
||||
self.item = self.dal.patch(item_id, data.dict(exclude_unset=True, exclude_defaults=True))
|
||||
except IntegrityError:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail={"message": "generic-patch-error"})
|
||||
|
||||
def _delete_one(self, item_id: int = None) -> R:
|
||||
target_id = item_id or self.item.id
|
||||
self.item = self.dal.delete(self.session, target_id)
|
||||
logger.info(f"Deleting item with id {target_id}")
|
||||
|
||||
try:
|
||||
self.item = self.dal.delete(target_id)
|
||||
except Exception as ex:
|
||||
logger.exception(ex)
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST, detail={"message": "generic-delete-error", "exception": str(ex)}
|
||||
)
|
||||
|
||||
return self.item
|
||||
|
|
|
@ -10,7 +10,7 @@ from pydantic.main import BaseModel
|
|||
|
||||
from mealie.core import root_logger
|
||||
from mealie.core.config import app_dirs
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import create_session
|
||||
from mealie.services.events import create_backup_event
|
||||
|
||||
|
@ -114,29 +114,31 @@ def backup_all(
|
|||
):
|
||||
db_export = ExportDatabase(tag=tag, templates=templates)
|
||||
|
||||
db = get_database(session)
|
||||
|
||||
if export_users:
|
||||
all_users = db.users.get_all(session)
|
||||
all_users = db.users.get_all()
|
||||
db_export.export_items(all_users, "users")
|
||||
|
||||
if export_groups:
|
||||
all_groups = db.groups.get_all(session)
|
||||
all_groups = db.groups.get_all()
|
||||
db_export.export_items(all_groups, "groups")
|
||||
|
||||
if export_recipes:
|
||||
all_recipes = db.recipes.get_all(session)
|
||||
all_recipes = db.recipes.get_all()
|
||||
db_export.export_recipe_dirs()
|
||||
db_export.export_items(all_recipes, "recipes", export_list=False, slug_folder=True)
|
||||
db_export.export_templates(all_recipes)
|
||||
|
||||
all_comments = db.comments.get_all(session)
|
||||
all_comments = db.comments.get_all()
|
||||
db_export.export_items(all_comments, "comments")
|
||||
|
||||
if export_settings:
|
||||
all_settings = db.settings.get_all(session)
|
||||
all_settings = db.settings.get_all()
|
||||
db_export.export_items(all_settings, "settings")
|
||||
|
||||
if export_notifications:
|
||||
all_notifications = db.event_notifications.get_all(session)
|
||||
all_notifications = db.event_notifications.get_all()
|
||||
db_export.export_items(all_notifications, "notifications")
|
||||
|
||||
return db_export.finish_export()
|
||||
|
|
|
@ -8,7 +8,7 @@ from pydantic.main import BaseModel
|
|||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.config import app_dirs
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.schema.admin import (
|
||||
CommentImport,
|
||||
GroupImport,
|
||||
|
@ -44,6 +44,7 @@ class ImportDatabase:
|
|||
"""
|
||||
self.user = user
|
||||
self.session = session
|
||||
self.db = get_database(session)
|
||||
self.archive = app_dirs.BACKUP_DIR.joinpath(zip_archive)
|
||||
self.force_imports = force_import
|
||||
|
||||
|
@ -72,7 +73,7 @@ class ImportDatabase:
|
|||
recipe.user_id = self.user.id
|
||||
|
||||
import_status = self.import_model(
|
||||
db_table=db.recipes,
|
||||
db_table=self.db.recipes,
|
||||
model=recipe,
|
||||
return_model=RecipeImport,
|
||||
name_attr="name",
|
||||
|
@ -101,7 +102,7 @@ class ImportDatabase:
|
|||
comment: CommentOut
|
||||
|
||||
self.import_model(
|
||||
db_table=db.comments,
|
||||
db_table=self.db.comments,
|
||||
model=comment,
|
||||
return_model=CommentImport,
|
||||
name_attr="uuid",
|
||||
|
@ -166,7 +167,7 @@ class ImportDatabase:
|
|||
|
||||
for notify in notifications:
|
||||
import_status = self.import_model(
|
||||
db_table=db.event_notifications,
|
||||
db_table=self.db.event_notifications,
|
||||
model=notify,
|
||||
return_model=NotificationImport,
|
||||
name_attr="name",
|
||||
|
@ -183,7 +184,7 @@ class ImportDatabase:
|
|||
settings = settings[0]
|
||||
|
||||
try:
|
||||
db.settings.update(self.session, 1, settings.dict())
|
||||
self.db.settings.update(1, settings.dict())
|
||||
import_status = SettingsImport(name="Site Settings", status=True)
|
||||
|
||||
except Exception as inst:
|
||||
|
@ -198,7 +199,7 @@ class ImportDatabase:
|
|||
group_imports = []
|
||||
|
||||
for group in groups:
|
||||
import_status = self.import_model(db.groups, group, GroupImport, search_key="name")
|
||||
import_status = self.import_model(self.db.groups, group, GroupImport, search_key="name")
|
||||
group_imports.append(import_status)
|
||||
|
||||
return group_imports
|
||||
|
@ -209,13 +210,13 @@ class ImportDatabase:
|
|||
user_imports = []
|
||||
for user in users:
|
||||
if user.id == 1: # Update Default User
|
||||
db.users.update(self.session, 1, user.dict())
|
||||
self.db.users.update(1, user.dict())
|
||||
import_status = UserImport(name=user.full_name, status=True)
|
||||
user_imports.append(import_status)
|
||||
continue
|
||||
|
||||
import_status = self.import_model(
|
||||
db_table=db.users,
|
||||
db_table=self.db.users,
|
||||
model=user,
|
||||
return_model=UserImport,
|
||||
name_attr="full_name",
|
||||
|
@ -283,7 +284,7 @@ class ImportDatabase:
|
|||
model_name = getattr(model, name_attr)
|
||||
search_value = getattr(model, search_key)
|
||||
|
||||
item = db_table.get(self.session, search_value, search_key)
|
||||
item = db_table.get(search_value, search_key)
|
||||
if item:
|
||||
if not self.force_imports:
|
||||
return return_model(
|
||||
|
@ -293,9 +294,9 @@ class ImportDatabase:
|
|||
)
|
||||
|
||||
primary_key = getattr(item, db_table.primary_key)
|
||||
db_table.delete(self.session, primary_key)
|
||||
db_table.delete(primary_key)
|
||||
try:
|
||||
db_table.create(self.session, model.dict())
|
||||
db_table.create(model.dict())
|
||||
import_status = return_model(name=model_name, status=True)
|
||||
|
||||
except Exception as inst:
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import apprise
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import create_session
|
||||
from mealie.schema.events import Event, EventCategory
|
||||
|
||||
|
||||
def test_notification(notification_url, event=None) -> bool:
|
||||
|
||||
if event is None:
|
||||
event = Event(
|
||||
title="Test Notification",
|
||||
|
@ -38,9 +37,10 @@ def post_notifications(event: Event, notification_urls=list[str], hard_fail=Fals
|
|||
def save_event(title, text, category, session: Session, attachment=None):
|
||||
event = Event(title=title, text=text, category=category)
|
||||
session = session or create_session()
|
||||
db.events.create(session, event.dict())
|
||||
db = get_database(session)
|
||||
db.events.create(event.dict())
|
||||
|
||||
notification_objects = db.event_notifications.get(session=session, match_value=True, match_key=category, limit=9999)
|
||||
notification_objects = db.event_notifications.get(match_value=True, match_key=category, limit=9999)
|
||||
notification_urls = [x.notification_url for x in notification_objects]
|
||||
post_notifications(event, notification_urls, attachment=attachment)
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.database import get_database
|
||||
from mealie.schema.cookbook.cookbook import CreateCookBook, ReadCookBook, RecipeCookBook, SaveCookBook, UpdateCookBook
|
||||
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
|
@ -12,17 +13,16 @@ logger = get_logger(module=__name__)
|
|||
|
||||
|
||||
class CookbookService(
|
||||
UserHttpService[int, ReadCookBook],
|
||||
CrudHttpMixins[CreateCookBook, ReadCookBook, UpdateCookBook],
|
||||
UserHttpService[int, ReadCookBook],
|
||||
):
|
||||
event_func = create_group_event
|
||||
_restrict_by_group = True
|
||||
|
||||
_schema = ReadCookBook
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.dal = get_database().cookbooks
|
||||
@cached_property
|
||||
def dal(self):
|
||||
return self.db.cookbooks
|
||||
|
||||
def populate_item(self, item_id: int) -> RecipeCookBook:
|
||||
try:
|
||||
|
@ -31,13 +31,13 @@ class CookbookService(
|
|||
pass
|
||||
|
||||
if isinstance(item_id, int):
|
||||
self.item = self.db.cookbooks.get_one(self.session, item_id, override_schema=RecipeCookBook)
|
||||
self.item = self.dal.get_one(item_id, override_schema=RecipeCookBook)
|
||||
|
||||
else:
|
||||
self.item = self.db.cookbooks.get_one(self.session, item_id, key="slug", override_schema=RecipeCookBook)
|
||||
self.item = self.dal.get_one(item_id, key="slug", override_schema=RecipeCookBook)
|
||||
|
||||
def get_all(self) -> list[ReadCookBook]:
|
||||
items = self.db.cookbooks.get(self.session, self.group_id, "group_id", limit=999)
|
||||
items = self.dal.get(self.group_id, "group_id", limit=999)
|
||||
items.sort(key=lambda x: x.position)
|
||||
return items
|
||||
|
||||
|
@ -52,7 +52,7 @@ class CookbookService(
|
|||
updated = []
|
||||
|
||||
for cookbook in data:
|
||||
cb = self.db.cookbooks.update(self.session, cookbook.id, cookbook)
|
||||
cb = self.dal.update(cookbook.id, cookbook)
|
||||
updated.append(cb)
|
||||
|
||||
return updated
|
||||
|
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi import Depends
|
||||
|
||||
from mealie.core.dependencies.grouped import UserDeps
|
||||
from mealie.core.root_logger import get_logger
|
||||
|
@ -17,7 +17,7 @@ logger = get_logger(module=__name__)
|
|||
|
||||
|
||||
class GroupSelfService(UserHttpService[int, str]):
|
||||
_restrict_by_group = True
|
||||
_restrict_by_group = False
|
||||
event_func = create_group_event
|
||||
item: GroupInDB
|
||||
|
||||
|
@ -31,31 +31,21 @@ class GroupSelfService(UserHttpService[int, str]):
|
|||
"""Override parent method to remove `item_id` from arguments"""
|
||||
return super().write_existing(item_id=0, deps=deps)
|
||||
|
||||
def assert_existing(self, _: str = None):
|
||||
self.populate_item()
|
||||
|
||||
if not self.item:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
||||
|
||||
if self.item.id != self.group_id:
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def populate_item(self, _: str = None) -> GroupInDB:
|
||||
self.item = self.db.groups.get(self.session, self.group_id)
|
||||
self.item = self.db.groups.get(self.group_id)
|
||||
return self.item
|
||||
|
||||
def update_categories(self, new_categories: list[CategoryBase]):
|
||||
self.item.categories = new_categories
|
||||
return self.db.groups.update(self.session, self.group_id, self.item)
|
||||
return self.db.groups.update(self.group_id, self.item)
|
||||
|
||||
def update_preferences(self, new_preferences: UpdateGroupPreferences):
|
||||
self.db.group_preferences.update(self.session, self.group_id, new_preferences)
|
||||
|
||||
self.db.group_preferences.update(self.group_id, new_preferences)
|
||||
return self.populate_item()
|
||||
|
||||
def create_invite_token(self, uses: int = 1) -> None:
|
||||
token = SaveInviteToken(uses_left=uses, group_id=self.group_id, token=uuid4().hex)
|
||||
return self.db.group_tokens.create(self.session, token)
|
||||
return self.db.group_invite_tokens.create(token)
|
||||
|
||||
def get_invite_tokens(self) -> list[ReadInviteToken]:
|
||||
return self.db.group_tokens.multi_query(self.session, {"group_id": self.group_id})
|
||||
return self.db.group_invite_tokens.multi_query({"group_id": self.group_id})
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
from mealie.db.database import get_database
|
||||
from mealie.db.data_access_layer.access_model_factory import Database
|
||||
from mealie.schema.group.group_preferences import CreateGroupPreferences
|
||||
from mealie.schema.user.user import GroupBase, GroupInDB
|
||||
|
||||
|
||||
def create_new_group(session, g_base: GroupBase, g_preferences: CreateGroupPreferences = None) -> GroupInDB:
|
||||
db = get_database()
|
||||
created_group = db.groups.create(session, g_base)
|
||||
def create_new_group(db: Database, g_base: GroupBase, g_preferences: CreateGroupPreferences = None) -> GroupInDB:
|
||||
created_group = db.groups.create(g_base)
|
||||
|
||||
g_preferences = g_preferences or CreateGroupPreferences(group_id=0) # Assign Temporary ID before group is created
|
||||
|
||||
g_preferences.group_id = created_group.id
|
||||
|
||||
db.group_preferences.create(session, g_preferences)
|
||||
db.group_preferences.create(g_preferences)
|
||||
|
||||
return created_group
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
from functools import cached_property
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.database import get_database
|
||||
from mealie.schema.meal_plan import CreatePlanEntry, ReadPlanEntry, SavePlanEntry, UpdatePlanEntry
|
||||
|
||||
from .._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
|
@ -13,26 +13,27 @@ from ..events import create_group_event
|
|||
logger = get_logger(module=__name__)
|
||||
|
||||
|
||||
class MealService(UserHttpService[int, ReadPlanEntry], CrudHttpMixins[CreatePlanEntry, ReadPlanEntry, UpdatePlanEntry]):
|
||||
class MealService(CrudHttpMixins[CreatePlanEntry, ReadPlanEntry, UpdatePlanEntry], UserHttpService[int, ReadPlanEntry]):
|
||||
event_func = create_group_event
|
||||
_restrict_by_group = True
|
||||
|
||||
_schema = ReadPlanEntry
|
||||
item: ReadPlanEntry
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.dal = get_database().meals
|
||||
@cached_property
|
||||
def dal(self):
|
||||
return self.db.meals
|
||||
|
||||
def populate_item(self, id: int) -> ReadPlanEntry:
|
||||
self.item = self.db.meals.get_one(self.session, id)
|
||||
self.item = self.dal.get_one(id)
|
||||
return self.item
|
||||
|
||||
def get_slice(self, start: date = None, end: date = None) -> list[ReadPlanEntry]:
|
||||
# 2 days ago
|
||||
return self.db.meals.get_slice(self.session, start, end, group_id=self.group_id)
|
||||
return self.dal.get_slice(start, end, group_id=self.group_id)
|
||||
|
||||
def get_today(self) -> list[ReadPlanEntry]:
|
||||
return self.db.meals.get_today(self.session, group_id=self.group_id)
|
||||
return self.dal.get_today(group_id=self.group_id)
|
||||
|
||||
def create_one(self, data: CreatePlanEntry) -> ReadPlanEntry:
|
||||
data = self.cast(data, SavePlanEntry)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.database import get_database
|
||||
from mealie.schema.group import ReadWebhook
|
||||
from mealie.schema.group.webhook import CreateWebhook, SaveWebhook
|
||||
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
|
@ -11,22 +12,21 @@ from mealie.services.events import create_group_event
|
|||
logger = get_logger(module=__name__)
|
||||
|
||||
|
||||
class WebhookService(UserHttpService[int, ReadWebhook], CrudHttpMixins[ReadWebhook, CreateWebhook, CreateWebhook]):
|
||||
class WebhookService(CrudHttpMixins[ReadWebhook, CreateWebhook, CreateWebhook], UserHttpService[int, ReadWebhook]):
|
||||
event_func = create_group_event
|
||||
_restrict_by_group = True
|
||||
|
||||
_schema = ReadWebhook
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.dal = get_database().webhooks
|
||||
@cached_property
|
||||
def dal(self):
|
||||
return self.db.webhooks
|
||||
|
||||
def populate_item(self, id: int) -> ReadWebhook:
|
||||
self.item = self.db.webhooks.get_one(self.session, id)
|
||||
self.item = self.dal.get_one(id)
|
||||
return self.item
|
||||
|
||||
def get_all(self) -> list[ReadWebhook]:
|
||||
return self.db.webhooks.get(self.session, self.group_id, match_key="group_id", limit=9999)
|
||||
return self.dal.get(self.group_id, match_key="group_id", limit=9999)
|
||||
|
||||
def create_one(self, data: CreateWebhook) -> ReadWebhook:
|
||||
data = self.cast(data, SaveWebhook)
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
from datetime import date, timedelta
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import create_session
|
||||
from mealie.schema.meal_plan import MealDayIn, MealPlanIn
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.schema.user import GroupInDB
|
||||
|
||||
|
||||
def set_mealplan_dates(meal_plan_base: MealPlanIn) -> MealPlanIn:
|
||||
for x, plan_days in enumerate(meal_plan_base.plan_days):
|
||||
plan_days: MealDayIn
|
||||
plan_days.date = meal_plan_base.start_date + timedelta(days=x)
|
||||
|
||||
|
||||
def get_todays_meal(session: Session, group: Union[int, GroupInDB]) -> Recipe:
|
||||
"""Returns the given mealplan for today based off the group. If the group
|
||||
Type is of type int, then a query will be made to the database to get the
|
||||
grop object."
|
||||
|
||||
Args:
|
||||
session (Session): SqlAlchemy Session
|
||||
group (Union[int, GroupInDB]): Either the id of the group or the GroupInDB Object
|
||||
|
||||
Returns:
|
||||
Recipe: Pydantic Recipe Object
|
||||
"""
|
||||
|
||||
session = session or create_session()
|
||||
|
||||
if isinstance(group, int):
|
||||
group: GroupInDB = db.groups.get(session, group)
|
||||
|
||||
today_slug = None
|
||||
|
||||
for mealplan in group.mealplans:
|
||||
for plan_day in mealplan.plan_days:
|
||||
if plan_day.date == date.today():
|
||||
if plan_day.meals[0].slug and plan_day.meals[0].slug != "":
|
||||
today_slug = plan_day.meals[0].slug
|
||||
else:
|
||||
return plan_day.meals[0]
|
||||
|
||||
if today_slug:
|
||||
return db.recipes.get(session, today_slug)
|
||||
else:
|
||||
return None
|
|
@ -7,7 +7,7 @@ import yaml
|
|||
from pydantic import BaseModel
|
||||
|
||||
from mealie.core import root_logger
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.schema.admin import MigrationImport
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.schema.user.user import PrivateUser
|
||||
|
@ -37,6 +37,10 @@ class MigrationBase(BaseModel):
|
|||
|
||||
user: PrivateUser
|
||||
|
||||
@property
|
||||
def db(self):
|
||||
return get_database(self.session)
|
||||
|
||||
@property
|
||||
def temp_dir(self) -> TemporaryDirectory:
|
||||
"""unpacks the migration_file into a temporary directory
|
||||
|
@ -66,7 +70,7 @@ class MigrationBase(BaseModel):
|
|||
with open(yaml_file, "r") as f:
|
||||
contents = f.read().split("---")
|
||||
recipe_data = {}
|
||||
for x, document in enumerate(contents):
|
||||
for _, document in enumerate(contents):
|
||||
|
||||
# Check if None or Empty String
|
||||
if document is None or document == "":
|
||||
|
@ -172,7 +176,7 @@ class MigrationBase(BaseModel):
|
|||
exception = ""
|
||||
status = False
|
||||
try:
|
||||
db.recipes.create(self.session, recipe.dict())
|
||||
self.db.recipes.create(recipe.dict())
|
||||
status = True
|
||||
|
||||
except Exception as inst:
|
||||
|
|
37
mealie/services/recipe/recipe_food_service.py
Normal file
37
mealie/services/recipe/recipe_food_service.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
|
||||
from mealie.schema.recipe.recipe_ingredient import CreateIngredientFood, IngredientFood
|
||||
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_recipe_event
|
||||
|
||||
|
||||
class RecipeFoodService(
|
||||
CrudHttpMixins[IngredientFood, CreateIngredientFood, CreateIngredientFood],
|
||||
UserHttpService[int, IngredientFood],
|
||||
):
|
||||
event_func = create_recipe_event
|
||||
_restrict_by_group = False
|
||||
_schema = IngredientFood
|
||||
|
||||
@cached_property
|
||||
def dal(self):
|
||||
return self.db.ingredient_foods
|
||||
|
||||
def populate_item(self, id: int) -> IngredientFood:
|
||||
self.item = self.dal.get_one(id)
|
||||
return self.item
|
||||
|
||||
def get_all(self) -> list[IngredientFood]:
|
||||
return self.dal.get_all()
|
||||
|
||||
def create_one(self, data: CreateIngredientFood) -> IngredientFood:
|
||||
return self._create_one(data)
|
||||
|
||||
def update_one(self, data: IngredientFood, item_id: int = None) -> IngredientFood:
|
||||
return self._update_one(data, item_id)
|
||||
|
||||
def delete_one(self, id: int = None) -> IngredientFood:
|
||||
return self._delete_one(id)
|
|
@ -1,13 +1,15 @@
|
|||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from shutil import copytree, rmtree
|
||||
from typing import Union
|
||||
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from mealie.core.dependencies.grouped import PublicDeps, UserDeps
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.data_access_layer.recipe_access_model import RecipeDataAccessModel
|
||||
from mealie.schema.recipe.recipe import CreateRecipe, Recipe, RecipeSummary
|
||||
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_recipe_event
|
||||
from mealie.services.recipe.mixins import recipe_creation_factory
|
||||
|
@ -15,7 +17,7 @@ from mealie.services.recipe.mixins import recipe_creation_factory
|
|||
logger = get_logger(module=__name__)
|
||||
|
||||
|
||||
class RecipeService(UserHttpService[str, Recipe]):
|
||||
class RecipeService(CrudHttpMixins[CreateRecipe, Recipe, Recipe], UserHttpService[str, Recipe]):
|
||||
"""
|
||||
Class Methods:
|
||||
`read_existing`: Reads an existing recipe from the database.
|
||||
|
@ -25,6 +27,10 @@ class RecipeService(UserHttpService[str, Recipe]):
|
|||
|
||||
event_func = create_recipe_event
|
||||
|
||||
@cached_property
|
||||
def dal(self) -> RecipeDataAccessModel:
|
||||
return self.db.recipes
|
||||
|
||||
@classmethod
|
||||
def write_existing(cls, slug: str, deps: UserDeps = Depends()):
|
||||
return super().write_existing(slug, deps)
|
||||
|
@ -35,75 +41,49 @@ class RecipeService(UserHttpService[str, Recipe]):
|
|||
|
||||
def assert_existing(self, slug: str):
|
||||
self.populate_item(slug)
|
||||
|
||||
if not self.item:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
||||
|
||||
if not self.item.settings.public and not self.user:
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def populate_item(self, slug: str) -> Recipe:
|
||||
self.item = self.db.recipes.get(self.session, slug)
|
||||
return self.item
|
||||
|
||||
# CRUD METHODS
|
||||
def get_all(self, start=0, limit=None):
|
||||
return self.db.recipes.multi_query(
|
||||
self.session, {"group_id": self.user.group_id}, start=start, limit=limit, override_schema=RecipeSummary
|
||||
{"group_id": self.user.group_id},
|
||||
start=start,
|
||||
limit=limit,
|
||||
override_schema=RecipeSummary,
|
||||
)
|
||||
|
||||
def create_one(self, create_data: Union[Recipe, CreateRecipe]) -> Recipe:
|
||||
create_data = recipe_creation_factory(self.user, name=create_data.name, additional_attrs=create_data.dict())
|
||||
|
||||
try:
|
||||
self.item = self.db.recipes.create(self.session, create_data)
|
||||
except IntegrityError:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail={"message": "RECIPE_ALREADY_EXISTS"})
|
||||
|
||||
self._create_one(create_data, "RECIPE_ALREAD_EXISTS")
|
||||
self._create_event(
|
||||
"Recipe Created",
|
||||
f"'{self.item.name}' by {self.user.username} \n {self.settings.BASE_URL}/recipe/{self.item.slug}",
|
||||
)
|
||||
|
||||
return self.item
|
||||
|
||||
def update_one(self, update_data: Recipe) -> Recipe:
|
||||
original_slug = self.item.slug
|
||||
|
||||
try:
|
||||
self.item = self.db.recipes.update(self.session, original_slug, update_data)
|
||||
except IntegrityError:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail={"message": "RECIPE_ALREADY_EXISTS"})
|
||||
|
||||
self._check_assets(original_slug)
|
||||
|
||||
self._update_one(update_data, original_slug)
|
||||
self.check_assets(original_slug)
|
||||
return self.item
|
||||
|
||||
def patch_one(self, patch_data: Recipe) -> Recipe:
|
||||
original_slug = self.item.slug
|
||||
|
||||
try:
|
||||
self.item = self.db.recipes.patch(
|
||||
self.session, original_slug, patch_data.dict(exclude_unset=True, exclude_defaults=True)
|
||||
)
|
||||
except IntegrityError:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail={"message": "RECIPE_ALREADY_EXISTS"})
|
||||
|
||||
self._check_assets(original_slug)
|
||||
|
||||
self._patch_one(patch_data, original_slug)
|
||||
self.check_assets(original_slug)
|
||||
return self.item
|
||||
|
||||
def delete_one(self) -> Recipe:
|
||||
try:
|
||||
recipe: Recipe = self.db.recipes.delete(self.session, self.item.slug)
|
||||
self._delete_assets()
|
||||
except Exception:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
self._delete_one(self.item.slug)
|
||||
self.delete_assets()
|
||||
self._create_event("Recipe Delete", f"'{self.item.name}' deleted by {self.user.full_name}")
|
||||
return self.item
|
||||
|
||||
self._create_event("Recipe Delete", f"'{recipe.name}' deleted by {self.user.full_name}")
|
||||
return recipe
|
||||
|
||||
def _check_assets(self, original_slug: str) -> None:
|
||||
def check_assets(self, original_slug: str) -> None:
|
||||
"""Checks if the recipe slug has changed, and if so moves the assets to a new file with the new slug."""
|
||||
if original_slug != self.item.slug:
|
||||
current_dir = self.app_dirs.RECIPE_DATA_DIR.joinpath(original_slug)
|
||||
|
@ -123,7 +103,7 @@ class RecipeService(UserHttpService[str, Recipe]):
|
|||
if file.name not in all_asset_files:
|
||||
file.unlink()
|
||||
|
||||
def _delete_assets(self) -> None:
|
||||
def delete_assets(self) -> None:
|
||||
recipe_dir = self.item.directory
|
||||
rmtree(recipe_dir, ignore_errors=True)
|
||||
logger.info(f"Recipe Directory Removed: {self.item.slug}")
|
||||
|
|
37
mealie/services/recipe/recipe_unit_service.py
Normal file
37
mealie/services/recipe/recipe_unit_service.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
|
||||
from mealie.schema.recipe.recipe_ingredient import CreateIngredientUnit, IngredientUnit
|
||||
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_recipe_event
|
||||
|
||||
|
||||
class RecipeUnitService(
|
||||
CrudHttpMixins[IngredientUnit, CreateIngredientUnit, CreateIngredientUnit],
|
||||
UserHttpService[int, IngredientUnit],
|
||||
):
|
||||
event_func = create_recipe_event
|
||||
_restrict_by_group = False
|
||||
_schema = IngredientUnit
|
||||
|
||||
@cached_property
|
||||
def dal(self):
|
||||
return self.db.ingredient_units
|
||||
|
||||
def populate_item(self, id: int) -> IngredientUnit:
|
||||
self.item = self.dal.get_one(id)
|
||||
return self.item
|
||||
|
||||
def get_all(self) -> list[IngredientUnit]:
|
||||
return self.dal.get_all()
|
||||
|
||||
def create_one(self, data: CreateIngredientUnit) -> IngredientUnit:
|
||||
return self._create_one(data)
|
||||
|
||||
def update_one(self, data: IngredientUnit, item_id: int = None) -> IngredientUnit:
|
||||
return self._update_one(data, item_id)
|
||||
|
||||
def delete_one(self, id: int = None) -> IngredientUnit:
|
||||
return self._delete_one(id)
|
|
@ -3,7 +3,7 @@ import datetime
|
|||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
|
||||
from mealie.core import root_logger
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import create_session
|
||||
from mealie.db.models.event import Event
|
||||
from mealie.schema.user import GroupInDB
|
||||
|
@ -39,7 +39,8 @@ def update_webhook_schedule():
|
|||
poll the database for changes and reschedule the webhook time
|
||||
"""
|
||||
session = create_session()
|
||||
all_groups: list[GroupInDB] = db.groups.get_all(session)
|
||||
db = get_database(session)
|
||||
all_groups: list[GroupInDB] = db.groups.get_all()
|
||||
|
||||
for group in all_groups:
|
||||
|
||||
|
@ -100,7 +101,8 @@ def add_group_to_schedule(scheduler, group: GroupInDB):
|
|||
|
||||
def init_webhook_schedule(scheduler, job_store: dict):
|
||||
session = create_session()
|
||||
all_groups: list[GroupInDB] = db.groups.get_all(session)
|
||||
db = get_database(session)
|
||||
all_groups: list[GroupInDB] = db.groups.get_all()
|
||||
|
||||
for group in all_groups:
|
||||
job_store.update(add_group_to_schedule(scheduler, group))
|
||||
|
|
|
@ -29,14 +29,14 @@ class RegistrationService(PublicHttpService[int, str]):
|
|||
|
||||
elif registration.group_token and registration.group_token != "":
|
||||
|
||||
token_entry = self.db.group_tokens.get(self.session, registration.group_token)
|
||||
token_entry = self.db.group_invite_tokens.get(registration.group_token)
|
||||
|
||||
print("Token Entry", token_entry)
|
||||
|
||||
if not token_entry:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, {"message": "Invalid group token"})
|
||||
|
||||
group = self.db.groups.get(self.session, token_entry.group_id)
|
||||
group = self.db.groups.get(token_entry.group_id)
|
||||
|
||||
else:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, {"message": "Missing group"})
|
||||
|
@ -47,10 +47,10 @@ class RegistrationService(PublicHttpService[int, str]):
|
|||
token_entry.uses_left = token_entry.uses_left - 1
|
||||
|
||||
if token_entry.uses_left == 0:
|
||||
self.db.group_tokens.delete(self.session, token_entry.token)
|
||||
self.db.group_invite_tokens.delete(token_entry.token)
|
||||
|
||||
else:
|
||||
self.db.group_tokens.update(self.session, token_entry.token, token_entry)
|
||||
self.db.group_invite_tokens.update(token_entry.token, token_entry)
|
||||
|
||||
return user
|
||||
|
||||
|
@ -64,7 +64,7 @@ class RegistrationService(PublicHttpService[int, str]):
|
|||
group=group.name,
|
||||
)
|
||||
|
||||
return self.db.users.create(self.session, new_user)
|
||||
return self.db.users.create(new_user)
|
||||
|
||||
def _register_new_group(self) -> GroupInDB:
|
||||
group_data = GroupBase(name=self.registration.group)
|
||||
|
@ -81,4 +81,4 @@ class RegistrationService(PublicHttpService[int, str]):
|
|||
recipe_disable_amount=self.registration.advanced,
|
||||
)
|
||||
|
||||
return create_new_group(self.session, group_data, group_preferences)
|
||||
return create_new_group(self.db, group_data, group_preferences)
|
||||
|
|
|
@ -25,7 +25,7 @@ class UserService(UserHttpService[int, str]):
|
|||
|
||||
def _populate_target_user(self, id: int = None):
|
||||
if id:
|
||||
self.target_user = self.db.users.get(self.session, id)
|
||||
self.target_user = self.db.users.get(id)
|
||||
if not self.target_user:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
||||
else:
|
||||
|
@ -38,4 +38,4 @@ class UserService(UserHttpService[int, str]):
|
|||
|
||||
self.target_user.password = hash_password(password_change.new_password)
|
||||
|
||||
return self.db.users.update_password(self.session, self.target_user.id, self.target_user.password)
|
||||
return self.db.users.update_password(self.target_user.id, self.target_user.password)
|
||||
|
|
|
@ -3,21 +3,22 @@ import json
|
|||
import requests
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import create_session
|
||||
from mealie.schema.user import GroupInDB
|
||||
from mealie.services.events import create_scheduled_event
|
||||
from mealie.services.meal_services import get_todays_meal
|
||||
|
||||
|
||||
def post_webhooks(group: int, session: Session = None, force=True):
|
||||
session = session or create_session()
|
||||
group_settings: GroupInDB = db.groups.get(session, group)
|
||||
db = get_database(session)
|
||||
group_settings: GroupInDB = db.groups.get(group)
|
||||
|
||||
if not group_settings.webhook_enable and not force:
|
||||
return
|
||||
|
||||
todays_recipe = get_todays_meal(session, group)
|
||||
# TODO: Fix Mealplan Webhooks
|
||||
todays_recipe = None
|
||||
|
||||
if not todays_recipe:
|
||||
return
|
||||
|
|
593
poetry.lock
generated
593
poetry.lock
generated
|
@ -67,7 +67,7 @@ zookeeper = ["kazoo"]
|
|||
|
||||
[[package]]
|
||||
name = "astroid"
|
||||
version = "2.6.5"
|
||||
version = "2.8.0"
|
||||
description = "An abstract syntax tree for Python with inference support."
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -75,6 +75,7 @@ python-versions = "~=3.6"
|
|||
|
||||
[package.dependencies]
|
||||
lazy-object-proxy = ">=1.4.0"
|
||||
typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""}
|
||||
wrapt = ">=1.11,<1.13"
|
||||
|
||||
[[package]]
|
||||
|
@ -117,14 +118,14 @@ typecheck = ["mypy"]
|
|||
|
||||
[[package]]
|
||||
name = "beautifulsoup4"
|
||||
version = "4.9.3"
|
||||
version = "4.10.0"
|
||||
description = "Screen-scraping library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
python-versions = ">3.0.0"
|
||||
|
||||
[package.dependencies]
|
||||
soupsieve = {version = ">1.2", markers = "python_version >= \"3.0\""}
|
||||
soupsieve = ">1.2"
|
||||
|
||||
[package.extras]
|
||||
html5lib = ["html5lib"]
|
||||
|
@ -173,7 +174,7 @@ pycparser = "*"
|
|||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "2.0.4"
|
||||
version = "2.0.6"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -222,7 +223,7 @@ toml = ["toml"]
|
|||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "3.4.7"
|
||||
version = "3.4.8"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -241,7 +242,7 @@ test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pret
|
|||
|
||||
[[package]]
|
||||
name = "decorator"
|
||||
version = "5.0.9"
|
||||
version = "5.1.0"
|
||||
description = "Decorators for Humans"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -343,7 +344,7 @@ dev = ["twine", "markdown", "flake8"]
|
|||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
description = "Lightweight in-process concurrent programming"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -424,7 +425,7 @@ python-versions = ">=3.5"
|
|||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "4.6.3"
|
||||
version = "4.8.1"
|
||||
description = "Read metadata from Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -588,7 +589,7 @@ i18n = ["babel (>=2.9.0)"]
|
|||
|
||||
[[package]]
|
||||
name = "mkdocs-material"
|
||||
version = "7.2.2"
|
||||
version = "7.2.7"
|
||||
description = "A Material Design theme for MkDocs"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -596,21 +597,18 @@ python-versions = "*"
|
|||
|
||||
[package.dependencies]
|
||||
markdown = ">=3.2"
|
||||
mkdocs = ">=1.1"
|
||||
mkdocs = ">=1.2.2"
|
||||
mkdocs-material-extensions = ">=1.0"
|
||||
Pygments = ">=2.4"
|
||||
pymdown-extensions = ">=7.0"
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material-extensions"
|
||||
version = "1.0.1"
|
||||
version = "1.0.3"
|
||||
description = "Extension pack for Python Markdown."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.dependencies]
|
||||
mkdocs-material = ">=5.0.0"
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
|
@ -679,22 +677,35 @@ test = ["allpairspy", "click", "faker", "pytest (>=6.0.1)", "pytest-discord (>=0
|
|||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "8.3.1"
|
||||
version = "8.3.2"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.3.0"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "0.13.1"
|
||||
version = "1.0.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg2-binary"
|
||||
|
@ -773,7 +784,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.9.0"
|
||||
version = "2.10.0"
|
||||
description = "Pygments is a syntax highlighting package written in Python."
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -789,18 +800,20 @@ python-versions = "*"
|
|||
|
||||
[[package]]
|
||||
name = "pylint"
|
||||
version = "2.9.6"
|
||||
version = "2.11.1"
|
||||
description = "python code static checker"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "~=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
astroid = ">=2.6.5,<2.7"
|
||||
astroid = ">=2.8.0,<2.9"
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
isort = ">=4.2.5,<6"
|
||||
mccabe = ">=0.6,<0.7"
|
||||
platformdirs = ">=2.2.0"
|
||||
toml = ">=0.7.1"
|
||||
typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
|
||||
|
||||
[[package]]
|
||||
name = "pymdown-extensions"
|
||||
|
@ -835,7 +848,7 @@ rdflib = "*"
|
|||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "6.2.4"
|
||||
version = "6.2.5"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -847,7 +860,7 @@ attrs = ">=19.2.0"
|
|||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<1.0.0a1"
|
||||
pluggy = ">=0.12,<2.0"
|
||||
py = ">=1.8.2"
|
||||
toml = "*"
|
||||
|
||||
|
@ -964,7 +977,7 @@ pyyaml = "*"
|
|||
|
||||
[[package]]
|
||||
name = "rdflib"
|
||||
version = "6.0.0"
|
||||
version = "6.0.1"
|
||||
description = "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information."
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -977,22 +990,22 @@ pyparsing = "*"
|
|||
[package.extras]
|
||||
docs = ["sphinx (<5)", "sphinxcontrib-apidoc"]
|
||||
html = ["html5lib"]
|
||||
tests = ["html5lib", "networkx", "nose (==1.3.7)", "nose-timer", "coverage", "black (==21.7b0)", "flake8", "doctest-ignore-unicode (==0.1.2)"]
|
||||
tests = ["html5lib", "networkx", "nose (==1.3.7)", "nose-timer", "coverage", "black (==21.6b0)", "flake8", "doctest-ignore-unicode (==0.1.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "rdflib-jsonld"
|
||||
version = "0.5.0"
|
||||
version = "0.6.2"
|
||||
description = "rdflib extension adding JSON-LD parser and serializer"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
rdflib = ">=4.2.2"
|
||||
rdflib = ">=5.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "recipe-scrapers"
|
||||
version = "13.3.5"
|
||||
version = "13.4.0"
|
||||
description = "Python package, scraping recipes from all over the internet"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -1005,7 +1018,7 @@ requests = ">=2.19.1"
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2021.7.6"
|
||||
version = "2021.8.28"
|
||||
description = "Alternative regular expression module, to replace re."
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -1046,7 +1059,7 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
|
|||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "10.7.0"
|
||||
version = "10.10.0"
|
||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -1103,14 +1116,14 @@ python-versions = ">=3.6"
|
|||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "1.4.22"
|
||||
version = "1.4.23"
|
||||
description = "Database Abstraction Library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
|
||||
|
||||
[package.dependencies]
|
||||
greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\""}
|
||||
greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and platform_machine in \"x86_64 X86_64 aarch64 AARCH64 ppc64le PPC64LE amd64 AMD64 win32 WIN32\""}
|
||||
|
||||
[package.extras]
|
||||
aiomysql = ["greenlet (!=0.4.17)", "aiomysql"]
|
||||
|
@ -1120,7 +1133,7 @@ mariadb_connector = ["mariadb (>=1.0.1)"]
|
|||
mssql = ["pyodbc"]
|
||||
mssql_pymssql = ["pymssql"]
|
||||
mssql_pyodbc = ["pyodbc"]
|
||||
mypy = ["sqlalchemy2-stubs", "mypy (>=0.800)"]
|
||||
mypy = ["sqlalchemy2-stubs", "mypy (>=0.910)"]
|
||||
mysql = ["mysqlclient (>=1.4.0,<2)", "mysqlclient (>=1.4.0)"]
|
||||
mysql_connector = ["mysqlconnector"]
|
||||
oracle = ["cx_oracle (>=7,<8)", "cx_oracle (>=7)"]
|
||||
|
@ -1169,7 +1182,7 @@ python-versions = "*"
|
|||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "3.10.0.0"
|
||||
version = "3.10.0.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.5+"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -1223,16 +1236,16 @@ standard = ["websockets (>=8.0.0,<9.0.0)", "watchgod (>=0.6)", "python-dotenv (>
|
|||
|
||||
[[package]]
|
||||
name = "uvloop"
|
||||
version = "0.15.3"
|
||||
version = "0.16.0"
|
||||
description = "Fast implementation of asyncio event loop on top of libuv"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
dev = ["Cython (>=0.29.20,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx-rtd-theme (>=0.2.4,<0.3.0)", "aiohttp", "flake8 (>=3.8.4,<3.9.0)", "psutil", "pycodestyle (>=2.6.0,<2.7.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
|
||||
docs = ["Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx-rtd-theme (>=0.2.4,<0.3.0)"]
|
||||
test = ["aiohttp", "flake8 (>=3.8.4,<3.9.0)", "psutil", "pycodestyle (>=2.6.0,<2.7.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
|
||||
dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
|
||||
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"]
|
||||
test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
|
||||
|
||||
[[package]]
|
||||
name = "validators"
|
||||
|
@ -1262,7 +1275,7 @@ six = ">=1.4.1"
|
|||
|
||||
[[package]]
|
||||
name = "watchdog"
|
||||
version = "2.1.3"
|
||||
version = "2.1.5"
|
||||
description = "Filesystem events monitoring"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -1321,7 +1334,7 @@ pgsql = ["psycopg2-binary"]
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "03d6e9fea568f4167c5cc6865d417c57575305f7ad6813dd503c6f40e85090d7"
|
||||
content-hash = "1b9a18e7114a8f157226c20e951dce0bd08ac884e0795f0f816e9f57d72ec309"
|
||||
|
||||
[metadata.files]
|
||||
aiofiles = [
|
||||
|
@ -1345,8 +1358,8 @@ apscheduler = [
|
|||
{file = "APScheduler-3.7.0.tar.gz", hash = "sha256:1cab7f2521e107d07127b042155b632b7a1cd5e02c34be5a28ff62f77c900c6a"},
|
||||
]
|
||||
astroid = [
|
||||
{file = "astroid-2.6.5-py3-none-any.whl", hash = "sha256:7b963d1c590d490f60d2973e57437115978d3a2529843f160b5003b721e1e925"},
|
||||
{file = "astroid-2.6.5.tar.gz", hash = "sha256:83e494b02d75d07d4e347b27c066fd791c0c74fc96c613d1ea3de0c82c48168f"},
|
||||
{file = "astroid-2.8.0-py3-none-any.whl", hash = "sha256:dcc06f6165f415220013801642bd6c9808a02967070919c4b746c6864c205471"},
|
||||
{file = "astroid-2.8.0.tar.gz", hash = "sha256:fe81f80c0b35264acb5653302ffbd935d394f1775c5e4487df745bf9c2442708"},
|
||||
]
|
||||
atomicwrites = [
|
||||
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
|
||||
|
@ -1366,9 +1379,8 @@ bcrypt = [
|
|||
{file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"},
|
||||
]
|
||||
beautifulsoup4 = [
|
||||
{file = "beautifulsoup4-4.9.3-py2-none-any.whl", hash = "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35"},
|
||||
{file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"},
|
||||
{file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"},
|
||||
{file = "beautifulsoup4-4.10.0-py3-none-any.whl", hash = "sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf"},
|
||||
{file = "beautifulsoup4-4.10.0.tar.gz", hash = "sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"},
|
||||
]
|
||||
black = [
|
||||
{file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
|
||||
|
@ -1425,8 +1437,8 @@ cffi = [
|
|||
{file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"},
|
||||
]
|
||||
charset-normalizer = [
|
||||
{file = "charset-normalizer-2.0.4.tar.gz", hash = "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"},
|
||||
{file = "charset_normalizer-2.0.4-py3-none-any.whl", hash = "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b"},
|
||||
{file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"},
|
||||
{file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
|
||||
|
@ -1495,22 +1507,27 @@ coverage = [
|
|||
{file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"},
|
||||
]
|
||||
cryptography = [
|
||||
{file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"},
|
||||
{file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"},
|
||||
{file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"},
|
||||
{file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"},
|
||||
{file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"},
|
||||
{file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"},
|
||||
{file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"},
|
||||
{file = "cryptography-3.4.8-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14"},
|
||||
{file = "cryptography-3.4.8-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7"},
|
||||
{file = "cryptography-3.4.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e"},
|
||||
{file = "cryptography-3.4.8-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085"},
|
||||
{file = "cryptography-3.4.8-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b"},
|
||||
{file = "cryptography-3.4.8-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb"},
|
||||
{file = "cryptography-3.4.8-cp36-abi3-win32.whl", hash = "sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7"},
|
||||
{file = "cryptography-3.4.8-cp36-abi3-win_amd64.whl", hash = "sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc"},
|
||||
{file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5"},
|
||||
{file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af"},
|
||||
{file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a"},
|
||||
{file = "cryptography-3.4.8-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06"},
|
||||
{file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498"},
|
||||
{file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7"},
|
||||
{file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9"},
|
||||
{file = "cryptography-3.4.8-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e"},
|
||||
{file = "cryptography-3.4.8.tar.gz", hash = "sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c"},
|
||||
]
|
||||
decorator = [
|
||||
{file = "decorator-5.0.9-py3-none-any.whl", hash = "sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323"},
|
||||
{file = "decorator-5.0.9.tar.gz", hash = "sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5"},
|
||||
{file = "decorator-5.1.0-py3-none-any.whl", hash = "sha256:7b12e7c3c6ab203a29e157335e9122cb03de9ab7264b137594103fd4a683b374"},
|
||||
{file = "decorator-5.1.0.tar.gz", hash = "sha256:e59913af105b9860aa2c8d3272d9de5a56a4e608db9a2f167a8480b323d529a7"},
|
||||
]
|
||||
ecdsa = [
|
||||
{file = "ecdsa-0.17.0-py2.py3-none-any.whl", hash = "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676"},
|
||||
|
@ -1535,55 +1552,56 @@ ghp-import = [
|
|||
{file = "ghp-import-2.0.1.tar.gz", hash = "sha256:753de2eace6e0f7d4edfb3cce5e3c3b98cd52aadb80163303d1d036bda7b4483"},
|
||||
]
|
||||
greenlet = [
|
||||
{file = "greenlet-1.1.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:60848099b76467ef09b62b0f4512e7e6f0a2c977357a036de602b653667f5f4c"},
|
||||
{file = "greenlet-1.1.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f42ad188466d946f1b3afc0a9e1a266ac8926461ee0786c06baac6bd71f8a6f3"},
|
||||
{file = "greenlet-1.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:76ed710b4e953fc31c663b079d317c18f40235ba2e3d55f70ff80794f7b57922"},
|
||||
{file = "greenlet-1.1.0-cp27-cp27m-win32.whl", hash = "sha256:b33b51ab057f8a20b497ffafdb1e79256db0c03ef4f5e3d52e7497200e11f821"},
|
||||
{file = "greenlet-1.1.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed1377feed808c9c1139bdb6a61bcbf030c236dd288d6fca71ac26906ab03ba6"},
|
||||
{file = "greenlet-1.1.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:da862b8f7de577bc421323714f63276acb2f759ab8c5e33335509f0b89e06b8f"},
|
||||
{file = "greenlet-1.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5f75e7f237428755d00e7460239a2482fa7e3970db56c8935bd60da3f0733e56"},
|
||||
{file = "greenlet-1.1.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:258f9612aba0d06785143ee1cbf2d7361801c95489c0bd10c69d163ec5254a16"},
|
||||
{file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d928e2e3c3906e0a29b43dc26d9b3d6e36921eee276786c4e7ad9ff5665c78a"},
|
||||
{file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cc407b68e0a874e7ece60f6639df46309376882152345508be94da608cc0b831"},
|
||||
{file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c557c809eeee215b87e8a7cbfb2d783fb5598a78342c29ade561440abae7d22"},
|
||||
{file = "greenlet-1.1.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:3d13da093d44dee7535b91049e44dd2b5540c2a0e15df168404d3dd2626e0ec5"},
|
||||
{file = "greenlet-1.1.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b3090631fecdf7e983d183d0fad7ea72cfb12fa9212461a9b708ff7907ffff47"},
|
||||
{file = "greenlet-1.1.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:06ecb43b04480e6bafc45cb1b4b67c785e183ce12c079473359e04a709333b08"},
|
||||
{file = "greenlet-1.1.0-cp35-cp35m-win32.whl", hash = "sha256:944fbdd540712d5377a8795c840a97ff71e7f3221d3fddc98769a15a87b36131"},
|
||||
{file = "greenlet-1.1.0-cp35-cp35m-win_amd64.whl", hash = "sha256:c767458511a59f6f597bfb0032a1c82a52c29ae228c2c0a6865cfeaeaac4c5f5"},
|
||||
{file = "greenlet-1.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2325123ff3a8ecc10ca76f062445efef13b6cf5a23389e2df3c02a4a527b89bc"},
|
||||
{file = "greenlet-1.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:598bcfd841e0b1d88e32e6a5ea48348a2c726461b05ff057c1b8692be9443c6e"},
|
||||
{file = "greenlet-1.1.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:be9768e56f92d1d7cd94185bab5856f3c5589a50d221c166cc2ad5eb134bd1dc"},
|
||||
{file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe7eac0d253915116ed0cd160a15a88981a1d194c1ef151e862a5c7d2f853d3"},
|
||||
{file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a6b035aa2c5fcf3dbbf0e3a8a5bc75286fc2d4e6f9cfa738788b433ec894919"},
|
||||
{file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca1c4a569232c063615f9e70ff9a1e2fee8c66a6fb5caf0f5e8b21a396deec3e"},
|
||||
{file = "greenlet-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:3096286a6072553b5dbd5efbefc22297e9d06a05ac14ba017233fedaed7584a8"},
|
||||
{file = "greenlet-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c35872b2916ab5a240d52a94314c963476c989814ba9b519bc842e5b61b464bb"},
|
||||
{file = "greenlet-1.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b97c9a144bbeec7039cca44df117efcbeed7209543f5695201cacf05ba3b5857"},
|
||||
{file = "greenlet-1.1.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:16183fa53bc1a037c38d75fdc59d6208181fa28024a12a7f64bb0884434c91ea"},
|
||||
{file = "greenlet-1.1.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6b1d08f2e7f2048d77343279c4d4faa7aef168b3e36039cba1917fffb781a8ed"},
|
||||
{file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14927b15c953f8f2d2a8dffa224aa78d7759ef95284d4c39e1745cf36e8cdd2c"},
|
||||
{file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bdcff4b9051fb1aa4bba4fceff6a5f770c6be436408efd99b76fc827f2a9319"},
|
||||
{file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70c7dd733a4c56838d1f1781e769081a25fade879510c5b5f0df76956abfa05"},
|
||||
{file = "greenlet-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:0de64d419b1cb1bfd4ea544bedea4b535ef3ae1e150b0f2609da14bbf48a4a5f"},
|
||||
{file = "greenlet-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8833e27949ea32d27f7e96930fa29404dd4f2feb13cce483daf52e8842ec246a"},
|
||||
{file = "greenlet-1.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c1580087ab493c6b43e66f2bdd165d9e3c1e86ef83f6c2c44a29f2869d2c5bd5"},
|
||||
{file = "greenlet-1.1.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ad80bb338cf9f8129c049837a42a43451fc7c8b57ad56f8e6d32e7697b115505"},
|
||||
{file = "greenlet-1.1.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:a9017ff5fc2522e45562882ff481128631bf35da444775bc2776ac5c61d8bcae"},
|
||||
{file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7920e3eccd26b7f4c661b746002f5ec5f0928076bd738d38d894bb359ce51927"},
|
||||
{file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:408071b64e52192869129a205e5b463abda36eff0cebb19d6e63369440e4dc99"},
|
||||
{file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be13a18cec649ebaab835dff269e914679ef329204704869f2f167b2c163a9da"},
|
||||
{file = "greenlet-1.1.0-cp38-cp38-win32.whl", hash = "sha256:22002259e5b7828b05600a762579fa2f8b33373ad95a0ee57b4d6109d0e589ad"},
|
||||
{file = "greenlet-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:206295d270f702bc27dbdbd7651e8ebe42d319139e0d90217b2074309a200da8"},
|
||||
{file = "greenlet-1.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:096cb0217d1505826ba3d723e8981096f2622cde1eb91af9ed89a17c10aa1f3e"},
|
||||
{file = "greenlet-1.1.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:03f28a5ea20201e70ab70518d151116ce939b412961c33827519ce620957d44c"},
|
||||
{file = "greenlet-1.1.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:7db68f15486d412b8e2cfcd584bf3b3a000911d25779d081cbbae76d71bd1a7e"},
|
||||
{file = "greenlet-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70bd1bb271e9429e2793902dfd194b653221904a07cbf207c3139e2672d17959"},
|
||||
{file = "greenlet-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f92731609d6625e1cc26ff5757db4d32b6b810d2a3363b0ff94ff573e5901f6f"},
|
||||
{file = "greenlet-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06d7ac89e6094a0a8f8dc46aa61898e9e1aec79b0f8b47b2400dd51a44dbc832"},
|
||||
{file = "greenlet-1.1.0-cp39-cp39-win32.whl", hash = "sha256:adb94a28225005890d4cf73648b5131e885c7b4b17bc762779f061844aabcc11"},
|
||||
{file = "greenlet-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa4230234d02e6f32f189fd40b59d5a968fe77e80f59c9c933384fe8ba535535"},
|
||||
{file = "greenlet-1.1.0.tar.gz", hash = "sha256:c87df8ae3f01ffb4483c796fe1b15232ce2b219f0b18126948616224d3f658ee"},
|
||||
{file = "greenlet-1.1.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:476ba9435afaead4382fbab8f1882f75e3fb2285c35c9285abb3dd30237f9142"},
|
||||
{file = "greenlet-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:44556302c0ab376e37939fd0058e1f0db2e769580d340fb03b01678d1ff25f68"},
|
||||
{file = "greenlet-1.1.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40abb7fec4f6294225d2b5464bb6d9552050ded14a7516588d6f010e7e366dcc"},
|
||||
{file = "greenlet-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:a11b6199a0b9dc868990456a2667167d0ba096c5224f6258e452bfbe5a9742c5"},
|
||||
{file = "greenlet-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e22a82d2b416d9227a500c6860cf13e74060cf10e7daf6695cbf4e6a94e0eee4"},
|
||||
{file = "greenlet-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bad269e442f1b7ffa3fa8820b3c3aa66f02a9f9455b5ba2db5a6f9eea96f56de"},
|
||||
{file = "greenlet-1.1.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:8ddb38fb6ad96c2ef7468ff73ba5c6876b63b664eebb2c919c224261ae5e8378"},
|
||||
{file = "greenlet-1.1.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:84782c80a433d87530ae3f4b9ed58d4a57317d9918dfcc6a59115fa2d8731f2c"},
|
||||
{file = "greenlet-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac991947ca6533ada4ce7095f0e28fe25d5b2f3266ad5b983ed4201e61596acf"},
|
||||
{file = "greenlet-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5317701c7ce167205c0569c10abc4bd01c7f4cf93f642c39f2ce975fa9b78a3c"},
|
||||
{file = "greenlet-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4870b018ca685ff573edd56b93f00a122f279640732bb52ce3a62b73ee5c4a92"},
|
||||
{file = "greenlet-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:990e0f5e64bcbc6bdbd03774ecb72496224d13b664aa03afd1f9b171a3269272"},
|
||||
{file = "greenlet-1.1.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:a414f8e14aa7bacfe1578f17c11d977e637d25383b6210587c29210af995ef04"},
|
||||
{file = "greenlet-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:e02780da03f84a671bb4205c5968c120f18df081236d7b5462b380fd4f0b497b"},
|
||||
{file = "greenlet-1.1.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:dfcb5a4056e161307d103bc013478892cfd919f1262c2bb8703220adcb986362"},
|
||||
{file = "greenlet-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:655ab836324a473d4cd8cf231a2d6f283ed71ed77037679da554e38e606a7117"},
|
||||
{file = "greenlet-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:6ce9d0784c3c79f3e5c5c9c9517bbb6c7e8aa12372a5ea95197b8a99402aa0e6"},
|
||||
{file = "greenlet-1.1.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:3fc6a447735749d651d8919da49aab03c434a300e9f0af1c886d560405840fd1"},
|
||||
{file = "greenlet-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8039f5fe8030c43cd1732d9a234fdcbf4916fcc32e21745ca62e75023e4d4649"},
|
||||
{file = "greenlet-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fddfb31aa2ac550b938d952bca8a87f1db0f8dc930ffa14ce05b5c08d27e7fd1"},
|
||||
{file = "greenlet-1.1.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97a807437b81f90f85022a9dcfd527deea38368a3979ccb49d93c9198b2c722"},
|
||||
{file = "greenlet-1.1.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf31e894dabb077a35bbe6963285d4515a387ff657bd25b0530c7168e48f167f"},
|
||||
{file = "greenlet-1.1.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eae94de9924bbb4d24960185363e614b1b62ff797c23dc3c8a7c75bbb8d187e"},
|
||||
{file = "greenlet-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:c1862f9f1031b1dee3ff00f1027fcd098ffc82120f43041fe67804b464bbd8a7"},
|
||||
{file = "greenlet-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:9b02e6039eafd75e029d8c58b7b1f3e450ca563ef1fe21c7e3e40b9936c8d03e"},
|
||||
{file = "greenlet-1.1.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:84488516639c3c5e5c0e52f311fff94ebc45b56788c2a3bfe9cf8e75670f4de3"},
|
||||
{file = "greenlet-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3f8fc59bc5d64fa41f58b0029794f474223693fd00016b29f4e176b3ee2cfd9f"},
|
||||
{file = "greenlet-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3e594015a2349ec6dcceda9aca29da8dc89e85b56825b7d1f138a3f6bb79dd4c"},
|
||||
{file = "greenlet-1.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e41f72f225192d5d4df81dad2974a8943b0f2d664a2a5cfccdf5a01506f5523c"},
|
||||
{file = "greenlet-1.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ff270fd05125dce3303e9216ccddc541a9e072d4fc764a9276d44dee87242b"},
|
||||
{file = "greenlet-1.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cde7ee190196cbdc078511f4df0be367af85636b84d8be32230f4871b960687"},
|
||||
{file = "greenlet-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:f253dad38605486a4590f9368ecbace95865fea0f2b66615d121ac91fd1a1563"},
|
||||
{file = "greenlet-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a91ee268f059583176c2c8b012a9fce7e49ca6b333a12bbc2dd01fc1a9783885"},
|
||||
{file = "greenlet-1.1.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:34e6675167a238bede724ee60fe0550709e95adaff6a36bcc97006c365290384"},
|
||||
{file = "greenlet-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bf3725d79b1ceb19e83fb1aed44095518c0fcff88fba06a76c0891cfd1f36837"},
|
||||
{file = "greenlet-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:5c3b735ccf8fc8048664ee415f8af5a3a018cc92010a0d7195395059b4b39b7d"},
|
||||
{file = "greenlet-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2002a59453858c7f3404690ae80f10c924a39f45f6095f18a985a1234c37334"},
|
||||
{file = "greenlet-1.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04e1849c88aa56584d4a0a6e36af5ec7cc37993fdc1fda72b56aa1394a92ded3"},
|
||||
{file = "greenlet-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8d4ed48eed7414ccb2aaaecbc733ed2a84c299714eae3f0f48db085342d5629"},
|
||||
{file = "greenlet-1.1.1-cp38-cp38-win32.whl", hash = "sha256:2f89d74b4f423e756a018832cd7a0a571e0a31b9ca59323b77ce5f15a437629b"},
|
||||
{file = "greenlet-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:d15cb6f8706678dc47fb4e4f8b339937b04eda48a0af1cca95f180db552e7663"},
|
||||
{file = "greenlet-1.1.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b050dbb96216db273b56f0e5960959c2b4cb679fe1e58a0c3906fa0a60c00662"},
|
||||
{file = "greenlet-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e0696525500bc8aa12eae654095d2260db4dc95d5c35af2b486eae1bf914ccd"},
|
||||
{file = "greenlet-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:07e6d88242e09b399682b39f8dfa1e7e6eca66b305de1ff74ed9eb1a7d8e539c"},
|
||||
{file = "greenlet-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98b491976ed656be9445b79bc57ed21decf08a01aaaf5fdabf07c98c108111f6"},
|
||||
{file = "greenlet-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e72db813c28906cdc59bd0da7c325d9b82aa0b0543014059c34c8c4ad20e16"},
|
||||
{file = "greenlet-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:090126004c8ab9cd0787e2acf63d79e80ab41a18f57d6448225bbfcba475034f"},
|
||||
{file = "greenlet-1.1.1-cp39-cp39-win32.whl", hash = "sha256:1796f2c283faab2b71c67e9b9aefb3f201fdfbee5cb55001f5ffce9125f63a45"},
|
||||
{file = "greenlet-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:4adaf53ace289ced90797d92d767d37e7cdc29f13bd3830c3f0a561277a4ae83"},
|
||||
{file = "greenlet-1.1.1.tar.gz", hash = "sha256:c0f22774cd8294078bdf7392ac73cf00bfa1e5e0ed644bd064fdabc5f2a2f481"},
|
||||
]
|
||||
gunicorn = [
|
||||
{file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"},
|
||||
|
@ -1623,8 +1641,8 @@ idna = [
|
|||
{file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"},
|
||||
]
|
||||
importlib-metadata = [
|
||||
{file = "importlib_metadata-4.6.3-py3-none-any.whl", hash = "sha256:51c6635429c77cf1ae634c997ff9e53ca3438b495f10a55ba28594dd69764a8b"},
|
||||
{file = "importlib_metadata-4.6.3.tar.gz", hash = "sha256:0645585859e9a6689c523927a5032f2ba5919f1f7d0e84bd4533312320de1ff9"},
|
||||
{file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"},
|
||||
{file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"},
|
||||
]
|
||||
iniconfig = [
|
||||
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
|
||||
|
@ -1764,12 +1782,12 @@ mkdocs = [
|
|||
{file = "mkdocs-1.2.2.tar.gz", hash = "sha256:a334f5bd98ec960638511366eb8c5abc9c99b9083a0ed2401d8791b112d6b078"},
|
||||
]
|
||||
mkdocs-material = [
|
||||
{file = "mkdocs-material-7.2.2.tar.gz", hash = "sha256:4f501e139e2f8546653e7d8777c9b97ca639d03d8c86345a60609864cc5bbb03"},
|
||||
{file = "mkdocs_material-7.2.2-py2.py3-none-any.whl", hash = "sha256:76de22213f0e0319b9bddf1bfa86530e93efb4a604e9ddf8f8419f0438572523"},
|
||||
{file = "mkdocs-material-7.2.7.tar.gz", hash = "sha256:2ce49ece52fc92a9728ea4a3aea941744bfe23442814a1e1db93368f858aee30"},
|
||||
{file = "mkdocs_material-7.2.7-py2.py3-none-any.whl", hash = "sha256:4be317aa17829746d9e36150207c5c6311fb82042dbd0a56ae2a3301a351fda1"},
|
||||
]
|
||||
mkdocs-material-extensions = [
|
||||
{file = "mkdocs-material-extensions-1.0.1.tar.gz", hash = "sha256:6947fb7f5e4291e3c61405bad3539d81e0b3cd62ae0d66ced018128af509c68f"},
|
||||
{file = "mkdocs_material_extensions-1.0.1-py3-none-any.whl", hash = "sha256:d90c807a88348aa6d1805657ec5c0b2d8d609c110e62b9dce4daf7fa981fa338"},
|
||||
{file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"},
|
||||
{file = "mkdocs_material_extensions-1.0.3-py3-none-any.whl", hash = "sha256:a82b70e533ce060b2a5d9eb2bc2e1be201cf61f901f93704b4acf6e3d5983a44"},
|
||||
]
|
||||
mypy-extensions = [
|
||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||
|
@ -1796,49 +1814,67 @@ pathvalidate = [
|
|||
{file = "pathvalidate-2.4.1.tar.gz", hash = "sha256:3c9bd94c7ec23e9cfb211ffbe356ae75f979d6c099a2c745ee9490f524f32468"},
|
||||
]
|
||||
pillow = [
|
||||
{file = "Pillow-8.3.1-1-cp36-cp36m-win_amd64.whl", hash = "sha256:fd7eef578f5b2200d066db1b50c4aa66410786201669fb76d5238b007918fb24"},
|
||||
{file = "Pillow-8.3.1-1-cp37-cp37m-win_amd64.whl", hash = "sha256:75e09042a3b39e0ea61ce37e941221313d51a9c26b8e54e12b3ececccb71718a"},
|
||||
{file = "Pillow-8.3.1-1-cp38-cp38-win_amd64.whl", hash = "sha256:c0e0550a404c69aab1e04ae89cca3e2a042b56ab043f7f729d984bf73ed2a093"},
|
||||
{file = "Pillow-8.3.1-1-cp39-cp39-win_amd64.whl", hash = "sha256:479ab11cbd69612acefa8286481f65c5dece2002ffaa4f9db62682379ca3bb77"},
|
||||
{file = "Pillow-8.3.1-1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f156d6ecfc747ee111c167f8faf5f4953761b5e66e91a4e6767e548d0f80129c"},
|
||||
{file = "Pillow-8.3.1-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:196560dba4da7a72c5e7085fccc5938ab4075fd37fe8b5468869724109812edd"},
|
||||
{file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c9569049d04aaacd690573a0398dbd8e0bf0255684fee512b413c2142ab723"},
|
||||
{file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c088a000dfdd88c184cc7271bfac8c5b82d9efa8637cd2b68183771e3cf56f04"},
|
||||
{file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fc214a6b75d2e0ea7745488da7da3c381f41790812988c7a92345978414fad37"},
|
||||
{file = "Pillow-8.3.1-cp36-cp36m-win32.whl", hash = "sha256:a17ca41f45cf78c2216ebfab03add7cc350c305c38ff34ef4eef66b7d76c5229"},
|
||||
{file = "Pillow-8.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:67b3666b544b953a2777cb3f5a922e991be73ab32635666ee72e05876b8a92de"},
|
||||
{file = "Pillow-8.3.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:ff04c373477723430dce2e9d024c708a047d44cf17166bf16e604b379bf0ca14"},
|
||||
{file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9364c81b252d8348e9cc0cb63e856b8f7c1b340caba6ee7a7a65c968312f7dab"},
|
||||
{file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a2f381932dca2cf775811a008aa3027671ace723b7a38838045b1aee8669fdcf"},
|
||||
{file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d0da39795049a9afcaadec532e7b669b5ebbb2a9134576ebcc15dd5bdae33cc0"},
|
||||
{file = "Pillow-8.3.1-cp37-cp37m-win32.whl", hash = "sha256:2b6dfa068a8b6137da34a4936f5a816aba0ecc967af2feeb32c4393ddd671cba"},
|
||||
{file = "Pillow-8.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a4eef1ff2d62676deabf076f963eda4da34b51bc0517c70239fafed1d5b51500"},
|
||||
{file = "Pillow-8.3.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:660a87085925c61a0dcc80efb967512ac34dbb256ff7dd2b9b4ee8dbdab58cf4"},
|
||||
{file = "Pillow-8.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:15a2808e269a1cf2131930183dcc0419bc77bb73eb54285dde2706ac9939fa8e"},
|
||||
{file = "Pillow-8.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:969cc558cca859cadf24f890fc009e1bce7d7d0386ba7c0478641a60199adf79"},
|
||||
{file = "Pillow-8.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2ee77c14a0299d0541d26f3d8500bb57e081233e3fa915fa35abd02c51fa7fae"},
|
||||
{file = "Pillow-8.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c11003197f908878164f0e6da15fce22373ac3fc320cda8c9d16e6bba105b844"},
|
||||
{file = "Pillow-8.3.1-cp38-cp38-win32.whl", hash = "sha256:3f08bd8d785204149b5b33e3b5f0ebbfe2190ea58d1a051c578e29e39bfd2367"},
|
||||
{file = "Pillow-8.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:70af7d222df0ff81a2da601fab42decb009dc721545ed78549cb96e3a1c5f0c8"},
|
||||
{file = "Pillow-8.3.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:37730f6e68bdc6a3f02d2079c34c532330d206429f3cee651aab6b66839a9f0e"},
|
||||
{file = "Pillow-8.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bc3c7ef940eeb200ca65bd83005eb3aae8083d47e8fcbf5f0943baa50726856"},
|
||||
{file = "Pillow-8.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c35d09db702f4185ba22bb33ef1751ad49c266534339a5cebeb5159d364f6f82"},
|
||||
{file = "Pillow-8.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b2efa07f69dc395d95bb9ef3299f4ca29bcb2157dc615bae0b42c3c20668ffc"},
|
||||
{file = "Pillow-8.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cc866706d56bd3a7dbf8bac8660c6f6462f2f2b8a49add2ba617bc0c54473d83"},
|
||||
{file = "Pillow-8.3.1-cp39-cp39-win32.whl", hash = "sha256:9a211b663cf2314edbdb4cf897beeb5c9ee3810d1d53f0e423f06d6ebbf9cd5d"},
|
||||
{file = "Pillow-8.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:c2a5ff58751670292b406b9f06e07ed1446a4b13ffced6b6cab75b857485cbc8"},
|
||||
{file = "Pillow-8.3.1-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c379425c2707078dfb6bfad2430728831d399dc95a7deeb92015eb4c92345eaf"},
|
||||
{file = "Pillow-8.3.1-pp36-pypy36_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:114f816e4f73f9ec06997b2fde81a92cbf0777c9e8f462005550eed6bae57e63"},
|
||||
{file = "Pillow-8.3.1-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8960a8a9f4598974e4c2aeb1bff9bdd5db03ee65fd1fce8adf3223721aa2a636"},
|
||||
{file = "Pillow-8.3.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:147bd9e71fb9dcf08357b4d530b5167941e222a6fd21f869c7911bac40b9994d"},
|
||||
{file = "Pillow-8.3.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1fd5066cd343b5db88c048d971994e56b296868766e461b82fa4e22498f34d77"},
|
||||
{file = "Pillow-8.3.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4ebde71785f8bceb39dcd1e7f06bcc5d5c3cf48b9f69ab52636309387b097c8"},
|
||||
{file = "Pillow-8.3.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1c03e24be975e2afe70dfc5da6f187eea0b49a68bb2b69db0f30a61b7031cee4"},
|
||||
{file = "Pillow-8.3.1.tar.gz", hash = "sha256:2cac53839bfc5cece8fdbe7f084d5e3ee61e1303cccc86511d351adcb9e2c792"},
|
||||
{file = "Pillow-8.3.2-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:c691b26283c3a31594683217d746f1dad59a7ae1d4cfc24626d7a064a11197d4"},
|
||||
{file = "Pillow-8.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f514c2717012859ccb349c97862568fdc0479aad85b0270d6b5a6509dbc142e2"},
|
||||
{file = "Pillow-8.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be25cb93442c6d2f8702c599b51184bd3ccd83adebd08886b682173e09ef0c3f"},
|
||||
{file = "Pillow-8.3.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d675a876b295afa114ca8bf42d7f86b5fb1298e1b6bb9a24405a3f6c8338811c"},
|
||||
{file = "Pillow-8.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59697568a0455764a094585b2551fd76bfd6b959c9f92d4bdec9d0e14616303a"},
|
||||
{file = "Pillow-8.3.2-cp310-cp310-win32.whl", hash = "sha256:2d5e9dc0bf1b5d9048a94c48d0813b6c96fccfa4ccf276d9c36308840f40c228"},
|
||||
{file = "Pillow-8.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:11c27e74bab423eb3c9232d97553111cc0be81b74b47165f07ebfdd29d825875"},
|
||||
{file = "Pillow-8.3.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:11eb7f98165d56042545c9e6db3ce394ed8b45089a67124298f0473b29cb60b2"},
|
||||
{file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f23b2d3079522fdf3c09de6517f625f7a964f916c956527bed805ac043799b8"},
|
||||
{file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19ec4cfe4b961edc249b0e04b5618666c23a83bc35842dea2bfd5dfa0157f81b"},
|
||||
{file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5a31c07cea5edbaeb4bdba6f2b87db7d3dc0f446f379d907e51cc70ea375629"},
|
||||
{file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15ccb81a6ffc57ea0137f9f3ac2737ffa1d11f786244d719639df17476d399a7"},
|
||||
{file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8f284dc1695caf71a74f24993b7c7473d77bc760be45f776a2c2f4e04c170550"},
|
||||
{file = "Pillow-8.3.2-cp36-cp36m-win32.whl", hash = "sha256:4abc247b31a98f29e5224f2d31ef15f86a71f79c7f4d2ac345a5d551d6393073"},
|
||||
{file = "Pillow-8.3.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a048dad5ed6ad1fad338c02c609b862dfaa921fcd065d747194a6805f91f2196"},
|
||||
{file = "Pillow-8.3.2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:06d1adaa284696785375fa80a6a8eb309be722cf4ef8949518beb34487a3df71"},
|
||||
{file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd24054aaf21e70a51e2a2a5ed1183560d3a69e6f9594a4bfe360a46f94eba83"},
|
||||
{file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a330bf7014ee034046db43ccbb05c766aa9e70b8d6c5260bfc38d73103b0ba"},
|
||||
{file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13654b521fb98abdecec105ea3fb5ba863d1548c9b58831dd5105bb3873569f1"},
|
||||
{file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1bd983c565f92779be456ece2479840ec39d386007cd4ae83382646293d681b"},
|
||||
{file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4326ea1e2722f3dc00ed77c36d3b5354b8fb7399fb59230249ea6d59cbed90da"},
|
||||
{file = "Pillow-8.3.2-cp37-cp37m-win32.whl", hash = "sha256:085a90a99404b859a4b6c3daa42afde17cb3ad3115e44a75f0d7b4a32f06a6c9"},
|
||||
{file = "Pillow-8.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:18a07a683805d32826c09acfce44a90bf474e6a66ce482b1c7fcd3757d588df3"},
|
||||
{file = "Pillow-8.3.2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4e59e99fd680e2b8b11bbd463f3c9450ab799305d5f2bafb74fefba6ac058616"},
|
||||
{file = "Pillow-8.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4d89a2e9219a526401015153c0e9dd48319ea6ab9fe3b066a20aa9aee23d9fd3"},
|
||||
{file = "Pillow-8.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56fd98c8294f57636084f4b076b75f86c57b2a63a8410c0cd172bc93695ee979"},
|
||||
{file = "Pillow-8.3.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b11c9d310a3522b0fd3c35667914271f570576a0e387701f370eb39d45f08a4"},
|
||||
{file = "Pillow-8.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0412516dcc9de9b0a1e0ae25a280015809de8270f134cc2c1e32c4eeb397cf30"},
|
||||
{file = "Pillow-8.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bcb04ff12e79b28be6c9988f275e7ab69f01cc2ba319fb3114f87817bb7c74b6"},
|
||||
{file = "Pillow-8.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0b9911ec70731711c3b6ebcde26caea620cbdd9dcb73c67b0730c8817f24711b"},
|
||||
{file = "Pillow-8.3.2-cp38-cp38-win32.whl", hash = "sha256:ce2e5e04bb86da6187f96d7bab3f93a7877830981b37f0287dd6479e27a10341"},
|
||||
{file = "Pillow-8.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:35d27687f027ad25a8d0ef45dd5208ef044c588003cdcedf05afb00dbc5c2deb"},
|
||||
{file = "Pillow-8.3.2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:04835e68ef12904bc3e1fd002b33eea0779320d4346082bd5b24bec12ad9c3e9"},
|
||||
{file = "Pillow-8.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:10e00f7336780ca7d3653cf3ac26f068fa11b5a96894ea29a64d3dc4b810d630"},
|
||||
{file = "Pillow-8.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cde7a4d3687f21cffdf5bb171172070bb95e02af448c4c8b2f223d783214056"},
|
||||
{file = "Pillow-8.3.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c3ff00110835bdda2b1e2b07f4a2548a39744bb7de5946dc8e95517c4fb2ca6"},
|
||||
{file = "Pillow-8.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d409030bf3bd05fa66fb5fdedc39c521b397f61ad04309c90444e893d05f7d"},
|
||||
{file = "Pillow-8.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bff50ba9891be0a004ef48828e012babaaf7da204d81ab9be37480b9020a82b"},
|
||||
{file = "Pillow-8.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7dbfbc0020aa1d9bc1b0b8bcf255a7d73f4ad0336f8fd2533fcc54a4ccfb9441"},
|
||||
{file = "Pillow-8.3.2-cp39-cp39-win32.whl", hash = "sha256:963ebdc5365d748185fdb06daf2ac758116deecb2277ec5ae98139f93844bc09"},
|
||||
{file = "Pillow-8.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:cc9d0dec711c914ed500f1d0d3822868760954dce98dfb0b7382a854aee55d19"},
|
||||
{file = "Pillow-8.3.2-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2c661542c6f71dfd9dc82d9d29a8386287e82813b0375b3a02983feac69ef864"},
|
||||
{file = "Pillow-8.3.2-pp36-pypy36_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:548794f99ff52a73a156771a0402f5e1c35285bd981046a502d7e4793e8facaa"},
|
||||
{file = "Pillow-8.3.2-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8b68f565a4175e12e68ca900af8910e8fe48aaa48fd3ca853494f384e11c8bcd"},
|
||||
{file = "Pillow-8.3.2-pp36-pypy36_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:838eb85de6d9307c19c655c726f8d13b8b646f144ca6b3771fa62b711ebf7624"},
|
||||
{file = "Pillow-8.3.2-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:feb5db446e96bfecfec078b943cc07744cc759893cef045aa8b8b6d6aaa8274e"},
|
||||
{file = "Pillow-8.3.2-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:fc0db32f7223b094964e71729c0361f93db43664dd1ec86d3df217853cedda87"},
|
||||
{file = "Pillow-8.3.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fd4fd83aa912d7b89b4b4a1580d30e2a4242f3936882a3f433586e5ab97ed0d5"},
|
||||
{file = "Pillow-8.3.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d0c8ebbfd439c37624db98f3877d9ed12c137cadd99dde2d2eae0dab0bbfc355"},
|
||||
{file = "Pillow-8.3.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cb3dd7f23b044b0737317f892d399f9e2f0b3a02b22b2c692851fb8120d82c6"},
|
||||
{file = "Pillow-8.3.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a66566f8a22561fc1a88dc87606c69b84fa9ce724f99522cf922c801ec68f5c1"},
|
||||
{file = "Pillow-8.3.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ce651ca46d0202c302a535d3047c55a0131a720cf554a578fc1b8a2aff0e7d96"},
|
||||
{file = "Pillow-8.3.2.tar.gz", hash = "sha256:dde3f3ed8d00c72631bc19cbfff8ad3b6215062a5eed402381ad365f82f0c18c"},
|
||||
]
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.3.0-py3-none-any.whl", hash = "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"},
|
||||
{file = "platformdirs-2.3.0.tar.gz", hash = "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
|
||||
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
|
||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||
]
|
||||
psycopg2-binary = [
|
||||
{file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"},
|
||||
|
@ -1932,16 +1968,16 @@ pyflakes = [
|
|||
{file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
|
||||
]
|
||||
pygments = [
|
||||
{file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"},
|
||||
{file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"},
|
||||
{file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"},
|
||||
{file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"},
|
||||
]
|
||||
pyhumps = [
|
||||
{file = "pyhumps-3.0.2-py3-none-any.whl", hash = "sha256:367b1aadcaa64f8196a3cd14f56559a5602950aeb8486f49318e7394f5e18052"},
|
||||
{file = "pyhumps-3.0.2.tar.gz", hash = "sha256:042b4b6eec6c1f862f8310c0eebbae19293e9edab8cafb030ff78c890ef1aa34"},
|
||||
]
|
||||
pylint = [
|
||||
{file = "pylint-2.9.6-py3-none-any.whl", hash = "sha256:2e1a0eb2e8ab41d6b5dbada87f066492bb1557b12b76c47c2ee8aa8a11186594"},
|
||||
{file = "pylint-2.9.6.tar.gz", hash = "sha256:8b838c8983ee1904b2de66cce9d0b96649a91901350e956d78f289c3bc87b48e"},
|
||||
{file = "pylint-2.11.1-py3-none-any.whl", hash = "sha256:0f358e221c45cbd4dad2a1e4b883e75d28acdcccd29d40c76eb72b307269b126"},
|
||||
{file = "pylint-2.11.1.tar.gz", hash = "sha256:2c9843fff1a88ca0ad98a256806c82c5a8f86086e7ccbdb93297d86c3f90c436"},
|
||||
]
|
||||
pymdown-extensions = [
|
||||
{file = "pymdown-extensions-8.2.tar.gz", hash = "sha256:b6daa94aad9e1310f9c64c8b1f01e4ce82937ab7eb53bfc92876a97aca02a6f4"},
|
||||
|
@ -1956,8 +1992,8 @@ pyrdfa3 = [
|
|||
{file = "pyRdfa3-3.5.3.tar.gz", hash = "sha256:157663a92b87df345b6f69bde235dff5f797891608e12fe1e4fa8dad687131ae"},
|
||||
]
|
||||
pytest = [
|
||||
{file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"},
|
||||
{file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"},
|
||||
{file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
|
||||
{file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"},
|
||||
]
|
||||
pytest-cov = [
|
||||
{file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"},
|
||||
|
@ -2013,54 +2049,59 @@ pyyaml-env-tag = [
|
|||
{file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
|
||||
]
|
||||
rdflib = [
|
||||
{file = "rdflib-6.0.0-py3-none-any.whl", hash = "sha256:bb24f0058070d5843503e15b37c597bc3858d328d11acd9476efad3aa62f555d"},
|
||||
{file = "rdflib-6.0.0.tar.gz", hash = "sha256:7ce4d757eb26f4dd43205ec340d8c097f29e5adfe45d6ea20238c731dc679879"},
|
||||
{file = "rdflib-6.0.1-py3-none-any.whl", hash = "sha256:a775069ab1c3d38b7e04666603666fb8a31937a4671a5afc91ca136109f8047a"},
|
||||
{file = "rdflib-6.0.1.tar.gz", hash = "sha256:f071caff0b68634e4a7bd1d66ea3416ac98f1cc3b915938147ea899c32608728"},
|
||||
]
|
||||
rdflib-jsonld = [
|
||||
{file = "rdflib-jsonld-0.5.0.tar.gz", hash = "sha256:4f7d55326405071c7bce9acf5484643bcb984eadb84a6503053367da207105ed"},
|
||||
{file = "rdflib-jsonld-0.6.2.tar.gz", hash = "sha256:107cd3019d41354c31687e64af5e3fd3c3e3fa5052ce635f5ce595fd31853a63"},
|
||||
{file = "rdflib_jsonld-0.6.2-py2.py3-none-any.whl", hash = "sha256:011afe67672353ca9978ab9a4bee964dff91f14042f2d8a28c22a573779d2f8b"},
|
||||
]
|
||||
recipe-scrapers = [
|
||||
{file = "recipe_scrapers-13.3.5-py3-none-any.whl", hash = "sha256:98ef4fd27ad70c3026aff255a4081f7bc072d9f2c3590753b1f11a66e42202e6"},
|
||||
{file = "recipe_scrapers-13.3.5.tar.gz", hash = "sha256:02db921b596ea7ea1ceb552ee10670fd307315b1b196c35ea133da49d71d41c8"},
|
||||
{file = "recipe_scrapers-13.4.0-py3-none-any.whl", hash = "sha256:f38a0c6c0e6394ce3062e715ac43a6d1b40c0d7930d0ec92849a154f7b0501ad"},
|
||||
{file = "recipe_scrapers-13.4.0.tar.gz", hash = "sha256:40296e0e77cb45018ada196dc1d3d58a8fc37e52361ba11b0b0ab286940406c0"},
|
||||
]
|
||||
regex = [
|
||||
{file = "regex-2021.7.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:6afe6a627888c9a6cfbb603d1d017ce204cebd589d66e0703309b8048c3b0854"},
|
||||
{file = "regex-2021.7.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ccb3d2190476d00414aab36cca453e4596e8f70a206e2aa8db3d495a109153d2"},
|
||||
{file = "regex-2021.7.6-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:ed693137a9187052fc46eedfafdcb74e09917166362af4cc4fddc3b31560e93d"},
|
||||
{file = "regex-2021.7.6-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:99d8ab206a5270c1002bfcf25c51bf329ca951e5a169f3b43214fdda1f0b5f0d"},
|
||||
{file = "regex-2021.7.6-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:b85ac458354165405c8a84725de7bbd07b00d9f72c31a60ffbf96bb38d3e25fa"},
|
||||
{file = "regex-2021.7.6-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:3f5716923d3d0bfb27048242a6e0f14eecdb2e2a7fac47eda1d055288595f222"},
|
||||
{file = "regex-2021.7.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5983c19d0beb6af88cb4d47afb92d96751fb3fa1784d8785b1cdf14c6519407"},
|
||||
{file = "regex-2021.7.6-cp36-cp36m-win32.whl", hash = "sha256:c92831dac113a6e0ab28bc98f33781383fe294df1a2c3dfd1e850114da35fd5b"},
|
||||
{file = "regex-2021.7.6-cp36-cp36m-win_amd64.whl", hash = "sha256:791aa1b300e5b6e5d597c37c346fb4d66422178566bbb426dd87eaae475053fb"},
|
||||
{file = "regex-2021.7.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:564a4c8a29435d1f2256ba247a0315325ea63335508ad8ed938a4f14c4116a5d"},
|
||||
{file = "regex-2021.7.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:59c00bb8dd8775473cbfb967925ad2c3ecc8886b3b2d0c90a8e2707e06c743f0"},
|
||||
{file = "regex-2021.7.6-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:9a854b916806c7e3b40e6616ac9e85d3cdb7649d9e6590653deb5b341a736cec"},
|
||||
{file = "regex-2021.7.6-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:db2b7df831c3187a37f3bb80ec095f249fa276dbe09abd3d35297fc250385694"},
|
||||
{file = "regex-2021.7.6-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:173bc44ff95bc1e96398c38f3629d86fa72e539c79900283afa895694229fe6a"},
|
||||
{file = "regex-2021.7.6-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:15dddb19823f5147e7517bb12635b3c82e6f2a3a6b696cc3e321522e8b9308ad"},
|
||||
{file = "regex-2021.7.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ddeabc7652024803666ea09f32dd1ed40a0579b6fbb2a213eba590683025895"},
|
||||
{file = "regex-2021.7.6-cp37-cp37m-win32.whl", hash = "sha256:f080248b3e029d052bf74a897b9d74cfb7643537fbde97fe8225a6467fb559b5"},
|
||||
{file = "regex-2021.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:d8bbce0c96462dbceaa7ac4a7dfbbee92745b801b24bce10a98d2f2b1ea9432f"},
|
||||
{file = "regex-2021.7.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:422dec1e7cbb2efbbe50e3f1de36b82906def93ed48da12d1714cabcd993d7f0"},
|
||||
{file = "regex-2021.7.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cbe23b323988a04c3e5b0c387fe3f8f363bf06c0680daf775875d979e376bd26"},
|
||||
{file = "regex-2021.7.6-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:0eb2c6e0fcec5e0f1d3bcc1133556563222a2ffd2211945d7b1480c1b1a42a6f"},
|
||||
{file = "regex-2021.7.6-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1c78780bf46d620ff4fff40728f98b8afd8b8e35c3efd638c7df67be2d5cddbf"},
|
||||
{file = "regex-2021.7.6-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bc84fb254a875a9f66616ed4538542fb7965db6356f3df571d783f7c8d256edd"},
|
||||
{file = "regex-2021.7.6-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:598c0a79b4b851b922f504f9f39a863d83ebdfff787261a5ed061c21e67dd761"},
|
||||
{file = "regex-2021.7.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875c355360d0f8d3d827e462b29ea7682bf52327d500a4f837e934e9e4656068"},
|
||||
{file = "regex-2021.7.6-cp38-cp38-win32.whl", hash = "sha256:e586f448df2bbc37dfadccdb7ccd125c62b4348cb90c10840d695592aa1b29e0"},
|
||||
{file = "regex-2021.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:2fe5e71e11a54e3355fa272137d521a40aace5d937d08b494bed4529964c19c4"},
|
||||
{file = "regex-2021.7.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4f64fc59fd5b10557f6cd0937e1597af022ad9b27d454e182485f1db3008f417"},
|
||||
{file = "regex-2021.7.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:89e5528803566af4df368df2d6f503c84fbfb8249e6631c7b025fe23e6bd0cde"},
|
||||
{file = "regex-2021.7.6-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2366fe0479ca0e9afa534174faa2beae87847d208d457d200183f28c74eaea59"},
|
||||
{file = "regex-2021.7.6-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f9392a4555f3e4cb45310a65b403d86b589adc773898c25a39184b1ba4db8985"},
|
||||
{file = "regex-2021.7.6-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:2bceeb491b38225b1fee4517107b8491ba54fba77cf22a12e996d96a3c55613d"},
|
||||
{file = "regex-2021.7.6-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:f98dc35ab9a749276f1a4a38ab3e0e2ba1662ce710f6530f5b0a6656f1c32b58"},
|
||||
{file = "regex-2021.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:319eb2a8d0888fa6f1d9177705f341bc9455a2c8aca130016e52c7fe8d6c37a3"},
|
||||
{file = "regex-2021.7.6-cp39-cp39-win32.whl", hash = "sha256:eaf58b9e30e0e546cdc3ac06cf9165a1ca5b3de8221e9df679416ca667972035"},
|
||||
{file = "regex-2021.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c"},
|
||||
{file = "regex-2021.7.6.tar.gz", hash = "sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d"},
|
||||
{file = "regex-2021.8.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2"},
|
||||
{file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a"},
|
||||
{file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0"},
|
||||
{file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb"},
|
||||
{file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a"},
|
||||
{file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308"},
|
||||
{file = "regex-2021.8.28-cp310-cp310-win32.whl", hash = "sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed"},
|
||||
{file = "regex-2021.8.28-cp310-cp310-win_amd64.whl", hash = "sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8"},
|
||||
{file = "regex-2021.8.28-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c"},
|
||||
{file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c"},
|
||||
{file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13"},
|
||||
{file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0"},
|
||||
{file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1"},
|
||||
{file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f"},
|
||||
{file = "regex-2021.8.28-cp36-cp36m-win32.whl", hash = "sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354"},
|
||||
{file = "regex-2021.8.28-cp36-cp36m-win_amd64.whl", hash = "sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645"},
|
||||
{file = "regex-2021.8.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a"},
|
||||
{file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e"},
|
||||
{file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892"},
|
||||
{file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791"},
|
||||
{file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"},
|
||||
{file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906"},
|
||||
{file = "regex-2021.8.28-cp37-cp37m-win32.whl", hash = "sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a"},
|
||||
{file = "regex-2021.8.28-cp37-cp37m-win_amd64.whl", hash = "sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc"},
|
||||
{file = "regex-2021.8.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd"},
|
||||
{file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797"},
|
||||
{file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f"},
|
||||
{file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256"},
|
||||
{file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b"},
|
||||
{file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e"},
|
||||
{file = "regex-2021.8.28-cp38-cp38-win32.whl", hash = "sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d"},
|
||||
{file = "regex-2021.8.28-cp38-cp38-win_amd64.whl", hash = "sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2"},
|
||||
{file = "regex-2021.8.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468"},
|
||||
{file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb"},
|
||||
{file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d"},
|
||||
{file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983"},
|
||||
{file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8"},
|
||||
{file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed"},
|
||||
{file = "regex-2021.8.28-cp39-cp39-win32.whl", hash = "sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374"},
|
||||
{file = "regex-2021.8.28-cp39-cp39-win_amd64.whl", hash = "sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73"},
|
||||
{file = "regex-2021.8.28.tar.gz", hash = "sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1"},
|
||||
]
|
||||
requests = [
|
||||
{file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
|
||||
|
@ -2072,8 +2113,8 @@ requests-oauthlib = [
|
|||
{file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"},
|
||||
]
|
||||
rich = [
|
||||
{file = "rich-10.7.0-py3-none-any.whl", hash = "sha256:517b4e0efd064dd1fe821ca93dd3095d73380ceac1f0a07173d507d9b18f1396"},
|
||||
{file = "rich-10.7.0.tar.gz", hash = "sha256:13ac80676e12cf528dc4228dc682c8402f82577c2aa67191e294350fa2c3c4e9"},
|
||||
{file = "rich-10.10.0-py3-none-any.whl", hash = "sha256:0b8cbcb0b8d476a7f002feaed9f35e51615f673c6c291d76ddf0c555574fd3c7"},
|
||||
{file = "rich-10.10.0.tar.gz", hash = "sha256:bacf58b25fea6b920446fe4e7abdc6c7664c4530c4098e7a1bc79b16b8551dfa"},
|
||||
]
|
||||
rsa = [
|
||||
{file = "rsa-4.7.2-py3-none-any.whl", hash = "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2"},
|
||||
|
@ -2092,36 +2133,36 @@ soupsieve = [
|
|||
{file = "soupsieve-2.2.1.tar.gz", hash = "sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc"},
|
||||
]
|
||||
sqlalchemy = [
|
||||
{file = "SQLAlchemy-1.4.22-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:488608953385d6c127d2dcbc4b11f8d7f2f30b89f6bd27c01b042253d985cc2f"},
|
||||
{file = "SQLAlchemy-1.4.22-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:5d856cc50fd26fc8dd04892ed5a5a3d7eeb914fea2c2e484183e2d84c14926e0"},
|
||||
{file = "SQLAlchemy-1.4.22-cp27-cp27m-win32.whl", hash = "sha256:a00d9c6d3a8afe1d1681cd8a5266d2f0ed684b0b44bada2ca82403b9e8b25d39"},
|
||||
{file = "SQLAlchemy-1.4.22-cp27-cp27m-win_amd64.whl", hash = "sha256:5908ea6c652a050d768580d01219c98c071e71910ab8e7b42c02af4010608397"},
|
||||
{file = "SQLAlchemy-1.4.22-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b7fb937c720847879c7402fe300cfdb2aeff22349fa4ea3651bca4e2d6555939"},
|
||||
{file = "SQLAlchemy-1.4.22-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:9bfe882d5a1bbde0245dca0bd48da0976bd6634cf2041d2fdf0417c5463e40e5"},
|
||||
{file = "SQLAlchemy-1.4.22-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eedd76f135461cf237534a6dc0d1e0f6bb88a1dc193678fab48a11d223462da5"},
|
||||
{file = "SQLAlchemy-1.4.22-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6a16c7c4452293da5143afa3056680db2d187b380b3ef4d470d4e29885720de3"},
|
||||
{file = "SQLAlchemy-1.4.22-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d23ea797a5e0be71bc5454b9ae99158ea0edc79e2393c6e9a2354de88329c0"},
|
||||
{file = "SQLAlchemy-1.4.22-cp36-cp36m-win32.whl", hash = "sha256:a5e14cb0c0a4ac095395f24575a0e7ab5d1be27f5f9347f1762f21505e3ba9f1"},
|
||||
{file = "SQLAlchemy-1.4.22-cp36-cp36m-win_amd64.whl", hash = "sha256:bc34a007e604091ca3a4a057525efc4cefd2b7fe970f44d20b9cfa109ab1bddb"},
|
||||
{file = "SQLAlchemy-1.4.22-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:756f5d2f5b92d27450167247fb574b09c4cd192a3f8c2e493b3e518a204ee543"},
|
||||
{file = "SQLAlchemy-1.4.22-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fcbb4b4756b250ed19adc5e28c005b8ed56fdb5c21efa24c6822c0575b4964d"},
|
||||
{file = "SQLAlchemy-1.4.22-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:09dbb4bc01a734ccddbf188deb2a69aede4b3c153a72b6d5c6900be7fb2945b1"},
|
||||
{file = "SQLAlchemy-1.4.22-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f028ef6a1d828bc754852a022b2160e036202ac8658a6c7d34875aafd14a9a15"},
|
||||
{file = "SQLAlchemy-1.4.22-cp37-cp37m-win32.whl", hash = "sha256:68393d3fd31469845b6ba11f5b4209edbea0b58506be0e077aafbf9aa2e21e11"},
|
||||
{file = "SQLAlchemy-1.4.22-cp37-cp37m-win_amd64.whl", hash = "sha256:891927a49b2363a4199763a9d436d97b0b42c65922a4ea09025600b81a00d17e"},
|
||||
{file = "SQLAlchemy-1.4.22-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:fd2102a8f8a659522719ed73865dff3d3cc76eb0833039dc473e0ad3041d04be"},
|
||||
{file = "SQLAlchemy-1.4.22-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4014978de28163cd8027434916a92d0f5bb1a3a38dff5e8bf8bff4d9372a9117"},
|
||||
{file = "SQLAlchemy-1.4.22-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f814d80844969b0d22ea63663da4de5ca1c434cfbae226188901e5d368792c17"},
|
||||
{file = "SQLAlchemy-1.4.22-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d09a760b0a045b4d799102ae7965b5491ccf102123f14b2a8cc6c01d1021a2d9"},
|
||||
{file = "SQLAlchemy-1.4.22-cp38-cp38-win32.whl", hash = "sha256:26daa429f039e29b1e523bf763bfab17490556b974c77b5ca7acb545b9230e9a"},
|
||||
{file = "SQLAlchemy-1.4.22-cp38-cp38-win_amd64.whl", hash = "sha256:12bac5fa1a6ea870bdccb96fe01610641dd44ebe001ed91ef7fcd980e9702db5"},
|
||||
{file = "SQLAlchemy-1.4.22-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:39b5d36ab71f73c068cdcf70c38075511de73616e6c7fdd112d6268c2704d9f5"},
|
||||
{file = "SQLAlchemy-1.4.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5102b9face693e8b2db3b2539c7e1a5d9a5b4dc0d79967670626ffd2f710d6e6"},
|
||||
{file = "SQLAlchemy-1.4.22-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c9373ef67a127799027091fa53449125351a8c943ddaa97bec4e99271dbb21f4"},
|
||||
{file = "SQLAlchemy-1.4.22-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36a089dc604032d41343d86290ce85d4e6886012eea73faa88001260abf5ff81"},
|
||||
{file = "SQLAlchemy-1.4.22-cp39-cp39-win32.whl", hash = "sha256:b48148ceedfb55f764562e04c00539bb9ea72bf07820ca15a594a9a049ff6b0e"},
|
||||
{file = "SQLAlchemy-1.4.22-cp39-cp39-win_amd64.whl", hash = "sha256:1fdae7d980a2fa617d119d0dc13ecb5c23cc63a8b04ffcb5298f2c59d86851e9"},
|
||||
{file = "SQLAlchemy-1.4.22.tar.gz", hash = "sha256:ec1be26cdccd60d180359a527d5980d959a26269a2c7b1b327a1eea0cab37ed8"},
|
||||
{file = "SQLAlchemy-1.4.23-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:25e9b2e5ca088879ce3740d9ccd4d58cb9061d49566a0b5e12166f403d6f4da0"},
|
||||
{file = "SQLAlchemy-1.4.23-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d9667260125688c71ccf9af321c37e9fb71c2693575af8210f763bfbbee847c7"},
|
||||
{file = "SQLAlchemy-1.4.23-cp27-cp27m-win32.whl", hash = "sha256:cec1a4c6ddf5f82191301a25504f0e675eccd86635f0d5e4c69e0661691931c5"},
|
||||
{file = "SQLAlchemy-1.4.23-cp27-cp27m-win_amd64.whl", hash = "sha256:ae07895b55c7d58a7dd47438f437ac219c0f09d24c2e7d69fdebc1ea75350f00"},
|
||||
{file = "SQLAlchemy-1.4.23-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:967307ea52985985224a79342527c36ec2d1daa257a39748dd90e001a4be4d90"},
|
||||
{file = "SQLAlchemy-1.4.23-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:be185b3daf651c6c0639987a916bf41e97b60e68f860f27c9cb6574385f5cbb4"},
|
||||
{file = "SQLAlchemy-1.4.23-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a0d3b3d51c83a66f5b72c57e1aad061406e4c390bd42cf1fda94effe82fac81"},
|
||||
{file = "SQLAlchemy-1.4.23-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a8395c4db3e1450eef2b68069abf500cc48af4b442a0d98b5d3c9535fe40cde8"},
|
||||
{file = "SQLAlchemy-1.4.23-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b128a78581faea7a5ee626ad4471353eee051e4e94616dfeff4742b6e5ba262"},
|
||||
{file = "SQLAlchemy-1.4.23-cp36-cp36m-win32.whl", hash = "sha256:43fc207be06e50158e4dae4cc4f27ce80afbdbfa7c490b3b22feb64f6d9775a0"},
|
||||
{file = "SQLAlchemy-1.4.23-cp36-cp36m-win_amd64.whl", hash = "sha256:e9d4f4552aa5e0d1417fc64a2ce1cdf56a30bab346ba6b0dd5e838eb56db4d29"},
|
||||
{file = "SQLAlchemy-1.4.23-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:512f52a8872e8d63d898e4e158eda17e2ee40b8d2496b3b409422e71016db0bd"},
|
||||
{file = "SQLAlchemy-1.4.23-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:355024cf061ed04271900414eb4a22671520241d2216ddb691bdd8a992172389"},
|
||||
{file = "SQLAlchemy-1.4.23-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:82c03325111eab88d64e0ff48b6fe15c75d23787429fa1d84c0995872e702787"},
|
||||
{file = "SQLAlchemy-1.4.23-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0aa312f9906ecebe133d7f44168c3cae4c76f27a25192fa7682f3fad505543c9"},
|
||||
{file = "SQLAlchemy-1.4.23-cp37-cp37m-win32.whl", hash = "sha256:059c5f41e8630f51741a234e6ba2a034228c11b3b54a15478e61d8b55fa8bd9d"},
|
||||
{file = "SQLAlchemy-1.4.23-cp37-cp37m-win_amd64.whl", hash = "sha256:cd68c5f9d13ffc8f4d6802cceee786678c5b1c668c97bc07b9f4a60883f36cd1"},
|
||||
{file = "SQLAlchemy-1.4.23-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:6a8dbf3d46e889d864a57ee880c4ad3a928db5aa95e3d359cbe0da2f122e50c4"},
|
||||
{file = "SQLAlchemy-1.4.23-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c15191f2430a30082f540ec6f331214746fc974cfdf136d7a1471d1c61d68ff"},
|
||||
{file = "SQLAlchemy-1.4.23-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd0e85dd2067159848c7672acd517f0c38b7b98867a347411ea01b432003f8d9"},
|
||||
{file = "SQLAlchemy-1.4.23-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:370f4688ce47f0dc1e677a020a4d46252a31a2818fd67f5c256417faefc938af"},
|
||||
{file = "SQLAlchemy-1.4.23-cp38-cp38-win32.whl", hash = "sha256:bd41f8063a9cd11b76d6d7d6af8139ab3c087f5dbbe5a50c02cb8ece7da34d67"},
|
||||
{file = "SQLAlchemy-1.4.23-cp38-cp38-win_amd64.whl", hash = "sha256:2bca9a6e30ee425cc321d988a152a5fe1be519648e7541ac45c36cd4f569421f"},
|
||||
{file = "SQLAlchemy-1.4.23-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:4803a481d4c14ce6ad53dc35458c57821863e9a079695c27603d38355e61fb7f"},
|
||||
{file = "SQLAlchemy-1.4.23-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07b9099a95dd2b2620498544300eda590741ac54915c6b20809b6de7e3c58090"},
|
||||
{file = "SQLAlchemy-1.4.23-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:37f2bd1b8e32c5999280f846701712347fc0ee7370e016ede2283c71712e127a"},
|
||||
{file = "SQLAlchemy-1.4.23-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:448612570aa1437a5d1b94ada161805778fe80aba5b9a08a403e8ae4e071ded6"},
|
||||
{file = "SQLAlchemy-1.4.23-cp39-cp39-win32.whl", hash = "sha256:e0ce4a2e48fe0a9ea3a5160411a4c5135da5255ed9ac9c15f15f2bcf58c34194"},
|
||||
{file = "SQLAlchemy-1.4.23-cp39-cp39-win_amd64.whl", hash = "sha256:0aa746d1173587743960ff17b89b540e313aacfe6c1e9c81aa48393182c36d4f"},
|
||||
{file = "SQLAlchemy-1.4.23.tar.gz", hash = "sha256:76ff246881f528089bf19385131b966197bb494653990396d2ce138e2a447583"},
|
||||
]
|
||||
starlette = [
|
||||
{file = "starlette-0.13.6-py3-none-any.whl", hash = "sha256:bd2ffe5e37fb75d014728511f8e68ebf2c80b0fa3d04ca1479f4dc752ae31ac9"},
|
||||
|
@ -2168,9 +2209,9 @@ typed-ast = [
|
|||
{file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"},
|
||||
{file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"},
|
||||
{file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"},
|
||||
{file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
|
||||
{file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
|
||||
{file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
|
||||
]
|
||||
tzlocal = [
|
||||
{file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"},
|
||||
|
@ -2185,16 +2226,22 @@ uvicorn = [
|
|||
{file = "uvicorn-0.13.4.tar.gz", hash = "sha256:3292251b3c7978e8e4a7868f4baf7f7f7bb7e40c759ecc125c37e99cdea34202"},
|
||||
]
|
||||
uvloop = [
|
||||
{file = "uvloop-0.15.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:e71fb9038bfcd7646ca126c5ef19b17e48d4af9e838b2bcfda7a9f55a6552a32"},
|
||||
{file = "uvloop-0.15.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7522df4e45e4f25b50adbbbeb5bb9847495c438a628177099d2721f2751ff825"},
|
||||
{file = "uvloop-0.15.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae2b325c0f6d748027f7463077e457006b4fdb35a8788f01754aadba825285ee"},
|
||||
{file = "uvloop-0.15.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:0de811931e90ae2da9e19ce70ffad73047ab0c1dba7c6e74f9ae1a3aabeb89bd"},
|
||||
{file = "uvloop-0.15.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7f4b8a905df909a407c5791fb582f6c03b0d3b491ecdc1cdceaefbc9bf9e08f6"},
|
||||
{file = "uvloop-0.15.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d8ffe44ae709f839c54bacf14ed283f41bee90430c3b398e521e10f8d117b3a"},
|
||||
{file = "uvloop-0.15.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:63a3288abbc9c8ee979d7e34c34e780b2fbab3e7e53d00b6c80271119f277399"},
|
||||
{file = "uvloop-0.15.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5cda65fc60a645470b8525ce014516b120b7057b576fa876cdfdd5e60ab1efbb"},
|
||||
{file = "uvloop-0.15.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ff05116ede1ebdd81802df339e5b1d4cab1dfbd99295bf27e90b4cec64d70e9"},
|
||||
{file = "uvloop-0.15.3.tar.gz", hash = "sha256:905f0adb0c09c9f44222ee02f6b96fd88b493478fffb7a345287f9444e926030"},
|
||||
{file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"},
|
||||
{file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30ba9dcbd0965f5c812b7c2112a1ddf60cf904c1c160f398e7eed3a6b82dcd9c"},
|
||||
{file = "uvloop-0.16.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bd53f7f5db562f37cd64a3af5012df8cac2c464c97e732ed556800129505bd64"},
|
||||
{file = "uvloop-0.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:772206116b9b57cd625c8a88f2413df2fcfd0b496eb188b82a43bed7af2c2ec9"},
|
||||
{file = "uvloop-0.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b572256409f194521a9895aef274cea88731d14732343da3ecdb175228881638"},
|
||||
{file = "uvloop-0.16.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:04ff57aa137230d8cc968f03481176041ae789308b4d5079118331ab01112450"},
|
||||
{file = "uvloop-0.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a19828c4f15687675ea912cc28bbcb48e9bb907c801873bd1519b96b04fb805"},
|
||||
{file = "uvloop-0.16.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e814ac2c6f9daf4c36eb8e85266859f42174a4ff0d71b99405ed559257750382"},
|
||||
{file = "uvloop-0.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bd8f42ea1ea8f4e84d265769089964ddda95eb2bb38b5cbe26712b0616c3edee"},
|
||||
{file = "uvloop-0.16.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:647e481940379eebd314c00440314c81ea547aa636056f554d491e40503c8464"},
|
||||
{file = "uvloop-0.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e0d26fa5875d43ddbb0d9d79a447d2ace4180d9e3239788208527c4784f7cab"},
|
||||
{file = "uvloop-0.16.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ccd57ae8db17d677e9e06192e9c9ec4bd2066b77790f9aa7dede2cc4008ee8f"},
|
||||
{file = "uvloop-0.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:089b4834fd299d82d83a25e3335372f12117a7d38525217c2258e9b9f4578897"},
|
||||
{file = "uvloop-0.16.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98d117332cc9e5ea8dfdc2b28b0a23f60370d02e1395f88f40d1effd2cb86c4f"},
|
||||
{file = "uvloop-0.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e5f2e2ff51aefe6c19ee98af12b4ae61f5be456cd24396953244a30880ad861"},
|
||||
{file = "uvloop-0.16.0.tar.gz", hash = "sha256:f74bc20c7b67d1c27c72601c78cf95be99d5c2cdd4514502b4f3eb0933ff1228"},
|
||||
]
|
||||
validators = [
|
||||
{file = "validators-0.18.2-py3-none-any.whl", hash = "sha256:0143dcca8a386498edaf5780cbd5960da1a4c85e0719f3ee5c9b41249c4fefbd"},
|
||||
|
@ -2205,27 +2252,29 @@ w3lib = [
|
|||
{file = "w3lib-1.22.0.tar.gz", hash = "sha256:0ad6d0203157d61149fd45aaed2e24f53902989c32fc1dccc2e2bfba371560df"},
|
||||
]
|
||||
watchdog = [
|
||||
{file = "watchdog-2.1.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9628f3f85375a17614a2ab5eac7665f7f7be8b6b0a2a228e6f6a2e91dd4bfe26"},
|
||||
{file = "watchdog-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:acc4e2d5be6f140f02ee8590e51c002829e2c33ee199036fcd61311d558d89f4"},
|
||||
{file = "watchdog-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:85b851237cf3533fabbc034ffcd84d0fa52014b3121454e5f8b86974b531560c"},
|
||||
{file = "watchdog-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a12539ecf2478a94e4ba4d13476bb2c7a2e0a2080af2bb37df84d88b1b01358a"},
|
||||
{file = "watchdog-2.1.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6fe9c8533e955c6589cfea6f3f0a1a95fb16867a211125236c82e1815932b5d7"},
|
||||
{file = "watchdog-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d9456f0433845e7153b102fffeb767bde2406b76042f2216838af3b21707894e"},
|
||||
{file = "watchdog-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fd8c595d5a93abd441ee7c5bb3ff0d7170e79031520d113d6f401d0cf49d7c8f"},
|
||||
{file = "watchdog-2.1.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0bcfe904c7d404eb6905f7106c54873503b442e8e918cc226e1828f498bdc0ca"},
|
||||
{file = "watchdog-2.1.3-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bf84bd94cbaad8f6b9cbaeef43080920f4cb0e61ad90af7106b3de402f5fe127"},
|
||||
{file = "watchdog-2.1.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b8ddb2c9f92e0c686ea77341dcb58216fa5ff7d5f992c7278ee8a392a06e86bb"},
|
||||
{file = "watchdog-2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8805a5f468862daf1e4f4447b0ccf3acaff626eaa57fbb46d7960d1cf09f2e6d"},
|
||||
{file = "watchdog-2.1.3-py3-none-manylinux2014_armv7l.whl", hash = "sha256:3e305ea2757f81d8ebd8559d1a944ed83e3ab1bdf68bcf16ec851b97c08dc035"},
|
||||
{file = "watchdog-2.1.3-py3-none-manylinux2014_i686.whl", hash = "sha256:431a3ea70b20962e6dee65f0eeecd768cd3085ea613ccb9b53c8969de9f6ebd2"},
|
||||
{file = "watchdog-2.1.3-py3-none-manylinux2014_ppc64.whl", hash = "sha256:e4929ac2aaa2e4f1a30a36751160be391911da463a8799460340901517298b13"},
|
||||
{file = "watchdog-2.1.3-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:201cadf0b8c11922f54ec97482f95b2aafca429c4c3a4bb869a14f3c20c32686"},
|
||||
{file = "watchdog-2.1.3-py3-none-manylinux2014_s390x.whl", hash = "sha256:3a7d242a7963174684206093846537220ee37ba9986b824a326a8bb4ef329a33"},
|
||||
{file = "watchdog-2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:54e057727dd18bd01a3060dbf5104eb5a495ca26316487e0f32a394fd5fe725a"},
|
||||
{file = "watchdog-2.1.3-py3-none-win32.whl", hash = "sha256:b5fc5c127bad6983eecf1ad117ab3418949f18af9c8758bd10158be3647298a9"},
|
||||
{file = "watchdog-2.1.3-py3-none-win_amd64.whl", hash = "sha256:44acad6f642996a2b50bb9ce4fb3730dde08f23e79e20cd3d8e2a2076b730381"},
|
||||
{file = "watchdog-2.1.3-py3-none-win_ia64.whl", hash = "sha256:0bcdf7b99b56a3ae069866c33d247c9994ffde91b620eaf0306b27e099bd1ae0"},
|
||||
{file = "watchdog-2.1.3.tar.gz", hash = "sha256:e5236a8e8602ab6db4b873664c2d356c365ab3cac96fbdec4970ad616415dd45"},
|
||||
{file = "watchdog-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5f57ce4f7e498278fb2a091f39359930144a0f2f90ea8cbf4523c4e25de34028"},
|
||||
{file = "watchdog-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8b74d0d92a69a7ab5f101f9fe74e44ba017be269efa824337366ccbb4effde85"},
|
||||
{file = "watchdog-2.1.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59767f476cd1f48531bf378f0300565d879688c82da8369ca8c52f633299523c"},
|
||||
{file = "watchdog-2.1.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:814d396859c95598f7576d15bc257c3bd3ba61fa4bc1db7dfc18f09070ded7da"},
|
||||
{file = "watchdog-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28777dbed3bbd95f9c70f461443990a36c07dbf49ae7cd69932cdd1b8fb2850c"},
|
||||
{file = "watchdog-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5cf78f794c9d7bc64a626ef4f71aff88f57a7ae288e0b359a9c6ea711a41395f"},
|
||||
{file = "watchdog-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:43bf728eb7830559f329864ab5da2302c15b2efbac24ad84ccc09949ba753c40"},
|
||||
{file = "watchdog-2.1.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a7053d4d22dc95c5e0c90aeeae1e4ed5269d2f04001798eec43a654a03008d22"},
|
||||
{file = "watchdog-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6f3ad1d973fe8fc8fe64ba38f6a934b74346342fa98ef08ad5da361a05d46044"},
|
||||
{file = "watchdog-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41d44ef21a77a32b55ce9bf59b75777063751f688de51098859b7c7f6466589a"},
|
||||
{file = "watchdog-2.1.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed4ca4351cd2bb0d863ee737a2011ca44d8d8be19b43509bd4507f8a449b376b"},
|
||||
{file = "watchdog-2.1.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8874d5ad6b7f43b18935d9b0183e29727a623a216693d6938d07dfd411ba462f"},
|
||||
{file = "watchdog-2.1.5-py3-none-manylinux2014_aarch64.whl", hash = "sha256:50a7f81f99d238f72185f481b493f9de80096e046935b60ea78e1276f3d76960"},
|
||||
{file = "watchdog-2.1.5-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e40e33a4889382824846b4baa05634e1365b47c6fa40071dc2d06b4d7c715fc1"},
|
||||
{file = "watchdog-2.1.5-py3-none-manylinux2014_i686.whl", hash = "sha256:78b1514067ff4089f4dac930b043a142997a5b98553120919005e97fbaba6546"},
|
||||
{file = "watchdog-2.1.5-py3-none-manylinux2014_ppc64.whl", hash = "sha256:58ae842300cbfe5e62fb068c83901abe76e4f413234b7bec5446e4275eb1f9cb"},
|
||||
{file = "watchdog-2.1.5-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:b0cc7d8b7d60da6c313779d85903ce39a63d89d866014b085f720a083d5f3e9a"},
|
||||
{file = "watchdog-2.1.5-py3-none-manylinux2014_s390x.whl", hash = "sha256:e60d3bb7166b7cb830b86938d1eb0e6cfe23dfd634cce05c128f8f9967895193"},
|
||||
{file = "watchdog-2.1.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:51af09ae937ada0e9a10cc16988ec03c649754a91526170b6839b89fc56d6acb"},
|
||||
{file = "watchdog-2.1.5-py3-none-win32.whl", hash = "sha256:9391003635aa783957b9b11175d9802d3272ed67e69ef2e3394c0b6d9d24fa9a"},
|
||||
{file = "watchdog-2.1.5-py3-none-win_amd64.whl", hash = "sha256:eab14adfc417c2c983fbcb2c73ef3f28ba6990d1fff45d1180bf7e38bda0d98d"},
|
||||
{file = "watchdog-2.1.5-py3-none-win_ia64.whl", hash = "sha256:a2888a788893c4ef7e562861ec5433875b7915f930a5a7ed3d32c048158f1be5"},
|
||||
{file = "watchdog-2.1.5.tar.gz", hash = "sha256:5563b005907613430ef3d4aaac9c78600dd5704e84764cb6deda4b3d72807f09"},
|
||||
]
|
||||
watchgod = [
|
||||
{file = "watchgod-0.7-py3-none-any.whl", hash = "sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7"},
|
||||
|
|
|
@ -32,7 +32,7 @@ passlib = "^1.7.4"
|
|||
lxml = "4.6.2"
|
||||
Pillow = "^8.2.0"
|
||||
pathvalidate = "^2.4.1"
|
||||
apprise = "^0.9.2"
|
||||
apprise = "0.9.3"
|
||||
recipe-scrapers = "^13.2.7"
|
||||
psycopg2-binary = {version = "^2.9.1", optional = true}
|
||||
gunicorn = "^20.1.0"
|
||||
|
|
|
@ -22,12 +22,15 @@ def test_create_webhook(api_client: TestClient, unique_user: TestUser, webhook_d
|
|||
assert response.status_code == 201
|
||||
|
||||
|
||||
def test_read_webhook(api_client: TestClient, webhook_data, unique_user: TestUser):
|
||||
def test_read_webhook(api_client: TestClient, unique_user: TestUser, webhook_data):
|
||||
response = api_client.post(Routes.base, json=webhook_data, headers=unique_user.token)
|
||||
response = api_client.get(Routes.item(1), headers=unique_user.token)
|
||||
|
||||
webhook = response.json()
|
||||
|
||||
assert webhook["id"] == 1
|
||||
print(webhook)
|
||||
|
||||
assert webhook["id"]
|
||||
assert webhook["name"] == webhook_data["name"]
|
||||
assert webhook["url"] == webhook_data["url"]
|
||||
assert webhook["time"] == webhook_data["time"]
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.schema.recipe.recipe_ingredient import CreateIngredientFood
|
||||
from tests.utils.factories import random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
class Routes:
|
||||
base = "/api/foods"
|
||||
|
||||
def item(item_id: int) -> str:
|
||||
return f"{Routes.base}/{item_id}"
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def food(api_client: TestClient, unique_user: TestUser) -> dict:
|
||||
data = CreateIngredientFood(
|
||||
name=random_string(10),
|
||||
description=random_string(10),
|
||||
).dict(by_alias=True)
|
||||
|
||||
response = api_client.post(Routes.base, json=data, headers=unique_user.token)
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
yield response.json()
|
||||
|
||||
response = api_client.delete(Routes.item(response.json()["id"]), headers=unique_user.token)
|
||||
|
||||
|
||||
def test_create_food(api_client: TestClient, unique_user: TestUser):
|
||||
data = CreateIngredientFood(
|
||||
name=random_string(10),
|
||||
description=random_string(10),
|
||||
).dict(by_alias=True)
|
||||
|
||||
response = api_client.post(Routes.base, json=data, headers=unique_user.token)
|
||||
assert response.status_code == 201
|
||||
|
||||
|
||||
def test_read_food(api_client: TestClient, food: dict, unique_user: TestUser):
|
||||
response = api_client.get(Routes.item(food["id"]), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
as_json = response.json()
|
||||
|
||||
assert as_json["id"] == food["id"]
|
||||
assert as_json["name"] == food["name"]
|
||||
assert as_json["description"] == food["description"]
|
||||
|
||||
|
||||
def test_update_food(api_client: TestClient, food: dict, unique_user: TestUser):
|
||||
update_data = {
|
||||
"id": food["id"],
|
||||
"name": random_string(10),
|
||||
"description": random_string(10),
|
||||
}
|
||||
response = api_client.put(Routes.item(food["id"]), json=update_data, headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
as_json = response.json()
|
||||
|
||||
assert as_json["id"] == food["id"]
|
||||
assert as_json["name"] == update_data["name"]
|
||||
assert as_json["description"] == update_data["description"]
|
||||
|
||||
|
||||
def test_delete_food(api_client: TestClient, food: dict, unique_user: TestUser):
|
||||
id = food["id"]
|
||||
|
||||
response = api_client.delete(Routes.item(id), headers=unique_user.token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(Routes.item(id), headers=unique_user.token)
|
||||
print(response.json())
|
||||
assert response.status_code == 404
|
|
@ -0,0 +1,84 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.schema.recipe.recipe_ingredient import CreateIngredientUnit
|
||||
from tests.utils.factories import random_bool, random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
class Routes:
|
||||
base = "/api/units"
|
||||
|
||||
def item(item_id: int) -> str:
|
||||
return f"{Routes.base}/{item_id}"
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def unit(api_client: TestClient, unique_user: TestUser) -> dict:
|
||||
data = CreateIngredientUnit(
|
||||
name=random_string(10),
|
||||
description=random_string(10),
|
||||
fraction=random_bool(),
|
||||
abbreviation=random_string(3) + ".",
|
||||
).dict(by_alias=True)
|
||||
|
||||
response = api_client.post(Routes.base, json=data, headers=unique_user.token)
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
yield response.json()
|
||||
|
||||
response = api_client.delete(Routes.item(response.json()["id"]), headers=unique_user.token)
|
||||
|
||||
|
||||
def test_create_unit(api_client: TestClient, unique_user: TestUser):
|
||||
data = CreateIngredientUnit(
|
||||
name=random_string(10),
|
||||
description=random_string(10),
|
||||
).dict(by_alias=True)
|
||||
|
||||
response = api_client.post(Routes.base, json=data, headers=unique_user.token)
|
||||
assert response.status_code == 201
|
||||
|
||||
|
||||
def test_read_unit(api_client: TestClient, unit: dict, unique_user: TestUser):
|
||||
response = api_client.get(Routes.item(unit["id"]), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
as_json = response.json()
|
||||
|
||||
assert as_json["id"] == unit["id"]
|
||||
assert as_json["name"] == unit["name"]
|
||||
assert as_json["description"] == unit["description"]
|
||||
assert as_json["fraction"] == unit["fraction"]
|
||||
assert as_json["abbreviation"] == unit["abbreviation"]
|
||||
|
||||
|
||||
def test_update_unit(api_client: TestClient, unit: dict, unique_user: TestUser):
|
||||
update_data = {
|
||||
"id": unit["id"],
|
||||
"name": random_string(10),
|
||||
"description": random_string(10),
|
||||
"fraction": not unit["fraction"],
|
||||
"abbreviation": random_string(3) + ".",
|
||||
}
|
||||
response = api_client.put(Routes.item(unit["id"]), json=update_data, headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
as_json = response.json()
|
||||
|
||||
assert as_json["id"] == unit["id"]
|
||||
assert as_json["name"] == update_data["name"]
|
||||
assert as_json["description"] == update_data["description"]
|
||||
assert as_json["fraction"] == update_data["fraction"]
|
||||
assert as_json["abbreviation"] == update_data["abbreviation"]
|
||||
|
||||
|
||||
def test_delete_unit(api_client: TestClient, unit: dict, unique_user: TestUser):
|
||||
id = unit["id"]
|
||||
|
||||
response = api_client.delete(Routes.item(id), headers=unique_user.token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(Routes.item(id), headers=unique_user.token)
|
||||
assert response.status_code == 404
|
|
@ -12,6 +12,10 @@ def random_email(length=10) -> str:
|
|||
return "".join(random.choice(string.ascii_lowercase + string.digits) for _ in range(length)) + "@fake.com"
|
||||
|
||||
|
||||
def random_bool() -> bool:
|
||||
return bool(random.getrandbits(1))
|
||||
|
||||
|
||||
def user_registration_factory() -> CreateUserRegistration:
|
||||
return CreateUserRegistration(
|
||||
group=random_string(),
|
||||
|
|
Loading…
Reference in a new issue