notifications first pass

This commit is contained in:
hay-kot 2021-05-07 23:05:30 -08:00
commit 566e947ffc
12 changed files with 571 additions and 76 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View file

@ -12,6 +12,9 @@ const aboutURLs = {
statistics: `${prefix}/statistics`,
events: `${prefix}/events`,
event: id => `${prefix}/events/${id}`,
allNotifications: `${prefix}/events/notifications`,
notification: id => `${prefix}/events/notifications/${id}`,
};
export const aboutAPI = {
@ -27,6 +30,21 @@ export const aboutAPI = {
const resposne = await apiReq.delete(aboutURLs.events);
return resposne.data;
},
async allEventNotifications() {
const response = await apiReq.get(aboutURLs.allNotifications);
return response.data;
},
async createNotification(data) {
const response = await apiReq.post(aboutURLs.allNotifications, data);
return response.data;
},
async deleteNotification(id) {
const response = await apiReq.delete(aboutURLs.notification(id));
return response.data;
},
// async getAppInfo() {
// const response = await apiReq.get(aboutURLs.version);
// return response.data;

View file

@ -0,0 +1,198 @@
<template>
<div>
<v-card outlined class="mt-n1">
<v-card-actions>
<v-spacer></v-spacer>
<BaseDialog title-icon="mdi-bell-alert" @submit="createNotification" title="New Notification">
<template v-slot:open="{ open }">
<v-btn small color="info" @click="open">
<v-icon left>
mdi-plus
</v-icon>
Notification
</v-btn>
</template>
<v-card-text class="mt-2">
We use the <a href="https://github.com/caronc/apprise/wiki" target="_blanks"> Apprise </a> library to
generate notifications. They offer many options for services to use. Refer to their wiki for a comprehensive
guide on how to create the URL for your service. If available, selecting the type of your notification can
include extra features Here are some common choices.
<div class="d-flex justify-space-around mt-1">
<a href="https://github.com/caronc/apprise/wiki/Notify_gotify" target="_blanks"> Gotify </a>
<a href="https://github.com/caronc/apprise/wiki/Notify_discord" target="_blanks"> Discord </a>
<a href="https://github.com/caronc/apprise/wiki/Notify_homeassistant" target="_blanks">
Home Assistant
</a>
<a href="https://github.com/caronc/apprise/wiki/Notify_matrix" target="_blanks"> Matrix </a>
<a href="https://github.com/caronc/apprise/wiki/Notify_pushover" target="_blanks"> Pushover </a>
</div>
<v-form>
<v-select label="Type" :items="notificationTypes" item-value="text" v-model="newNotification.type">
</v-select>
<v-text-field label="Name" v-model="newNotification.name"> </v-text-field>
<v-text-field label="Notification URL" v-model="newNotification.notificationUrl"> </v-text-field>
<v-subheader class="pa-0 mb-0">
Select the events you would like to recieve notifications for on this URL
</v-subheader>
<div class="px-3 dflex row justify-space-between mt-3">
<v-switch
class="ma-0 py-0"
v-for="(item, key, index) in newNotificationOptions"
:key="index"
v-model="newNotificationOptions[key]"
:label="key"
>
</v-switch>
</div>
</v-form>
</v-card-text>
</BaseDialog>
</v-card-actions>
<v-card-text>
<v-simple-table>
<template v-slot:default>
<thead>
<tr>
<th class="text-left">
Type
</th>
<th class="text-left">
Name
</th>
<th class="text-left">
General
</th>
<th class="text-left">
Recipe
</th>
<th class="text-left">
Backup
</th>
<th class="text-left">
Scheduled
</th>
<th class="text-left">
Migration
</th>
<th class="text-left">
Group
</th>
<th class="text-left">
User
</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in notifications" :key="index">
<td>
<v-avatar size="35" class="ma-1" color="primary">
<v-icon dark v-if="getIcon(item.type).icon"> {{ getIcon(item.type).icon }}</v-icon>
<v-img v-else :src="getIcon(item.type).image"> </v-img>
</v-avatar>
{{ item.type }}
</td>
<td>
{{ item.name }}
</td>
<td>
<v-icon color="success"> {{ item.general ? "mdi-check" : "" }} </v-icon>
</td>
<td>
<v-icon color="success"> {{ item.recipe ? "mdi-check" : "" }} </v-icon>
</td>
<td>
<v-icon color="success"> {{ item.backup ? "mdi-check" : "" }} </v-icon>
</td>
<td>
<v-icon color="success"> {{ item.scheduled ? "mdi-check" : "" }} </v-icon>
</td>
<td>
<v-icon color="success"> {{ item.migration ? "mdi-check" : "" }} </v-icon>
</td>
<td>
<v-icon color="success"> {{ item.group ? "mdi-check" : "" }} </v-icon>
</td>
<td>
<v-icon color="success"> {{ item.user ? "mdi-check" : "" }} </v-icon>
</td>
<td>
<v-btn small icon color="error" @click="deleteNotification(item.id)">
<v-icon> mdi-delete </v-icon>
</v-btn>
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-card-text>
</v-card>
</div>
</template>
<script>
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
import { api } from "@/api";
export default {
components: {
BaseDialog,
},
data() {
return {
notifications: [],
newNotification: {
type: "General",
name: "",
notificationUrl: "",
},
newNotificationOptions: {
general: true,
recipe: true,
backup: true,
scheduled: true,
migration: true,
group: true,
user: true,
},
notificationTypes: [
{
text: "General",
icon: "mdi-webhook",
},
{
text: "Discord",
icon: "mdi-discord",
},
{
text: "Gotify",
image: "./static/gotify.png",
},
],
};
},
mounted() {
this.getAllNotifications();
},
methods: {
getIcon(textValue) {
return this.notificationTypes.find(x => x.text === textValue);
},
async getAllNotifications() {
this.notifications = await api.about.allEventNotifications();
},
async createNotification() {
await api.about.createNotification({ ...this.newNotification, ...this.newNotificationOptions });
this.getAllNotifications();
},
async deleteNotification(id) {
await api.about.deleteNotification(id);
this.getAllNotifications();
},
},
};
</script>
<style lang="scss" scoped>
</style>

View file

@ -4,6 +4,10 @@
<v-tabs v-model="tab" background-color="primary" centered dark icons-and-text>
<v-tabs-slider></v-tabs-slider>
<v-tab href="#event-notifications">
Notify
<v-icon>mdi-bell-alert</v-icon>
</v-tab>
<v-tab href="#category-editor">
{{ $t("recipe.categories") }}
<v-icon>mdi-tag-multiple-outline</v-icon>
@ -20,20 +24,23 @@
</v-tabs>
<v-tabs-items v-model="tab">
<v-tab-item value="event-notifications"> <EventNotification /></v-tab-item>
<v-tab-item value="category-editor"> <CategoryTagEditor :is-tags="false"/></v-tab-item>
<v-tab-item value="tag-editor"> <CategoryTagEditor :is-tags="true" /> </v-tab-item>
<v-tab-item value="organize"> <RecipeOrganizer :is-tags="true" /> </v-tab-item>
<v-tab-item value="organize"> <RecipeOrganizer /> </v-tab-item>
</v-tabs-items>
</v-card>
</div>
</template>
<script>
import EventNotification from "./EventNotification";
import CategoryTagEditor from "./CategoryTagEditor";
import RecipeOrganizer from "./RecipeOrganizer";
export default {
components: {
CategoryTagEditor,
EventNotification,
RecipeOrganizer,
},
computed: {

View file

@ -1,7 +1,7 @@
from logging import getLogger
from mealie.db.db_base import BaseDocument
from mealie.db.models.event import Event
from mealie.db.models.event import Event, EventNotification
from mealie.db.models.group import Group
from mealie.db.models.mealplan import MealPlanModel
from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag
@ -10,6 +10,7 @@ from mealie.db.models.sign_up import SignUp
from mealie.db.models.theme import SiteThemeModel
from mealie.db.models.users import LongLiveToken, User
from mealie.schema.category import RecipeCategoryResponse, RecipeTagResponse
from mealie.schema.event_notifications import EventNotificationIn
from mealie.schema.events import Event as EventSchema
from mealie.schema.meal import MealPlanInDB
from mealie.schema.recipe import Recipe
@ -156,6 +157,13 @@ class _Events(BaseDocument):
self.schema = EventSchema
class _EventNotification(BaseDocument):
def __init__(self) -> None:
self.primary_key = "id"
self.sql_model = EventNotification
self.schema = EventNotificationIn
class Database:
def __init__(self) -> None:
self.recipes = _Recipes()
@ -170,6 +178,7 @@ class Database:
self.groups = _Groups()
self.custom_pages = _CustomPages()
self.events = _Events()
self.event_notifications = _EventNotification()
db = Database()

View file

@ -1,14 +1,45 @@
import sqlalchemy as sa
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
from sqlalchemy import Boolean, Column, DateTime, Integer, String
class EventNotification(SqlAlchemyBase, BaseMixins):
__tablename__ = "event_notifications"
id = Column(Integer, primary_key=True)
name = Column(String)
type = Column(String)
notification_url = Column(String)
# Event Types
general = Column(Boolean, default=False)
recipe = Column(Boolean, default=False)
backup = Column(Boolean, default=False)
scheduled = Column(Boolean, default=False)
migration = Column(Boolean, default=False)
group = Column(Boolean, default=False)
user = Column(Boolean, default=False)
def __init__(
self, name, notification_url, type, general, recipe, backup, scheduled, migration, group, user, *args, **kwargs
) -> None:
self.name = name
self.notification_url = notification_url
self.type = type
self.general = general
self.recipe = recipe
self.backup = backup
self.scheduled = scheduled
self.migration = migration
self.group = group
self.user = user
class Event(SqlAlchemyBase, BaseMixins):
__tablename__ = "events"
id = sa.Column(sa.Integer, primary_key=True)
title = sa.Column(sa.String)
text = sa.Column(sa.String)
time_stamp = sa.Column(sa.DateTime)
category = sa.Column(sa.String)
id = Column(Integer, primary_key=True)
title = Column(String)
text = Column(String)
time_stamp = Column(DateTime)
category = Column(String)
def __init__(self, title, text, time_stamp, category, *args, **kwargs) -> None:
self.title = title

View file

@ -2,27 +2,71 @@ from fastapi import APIRouter, Depends
from mealie.db.database import db
from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.event_notifications import EventNotificationIn, EventNotificationOut
from mealie.schema.events import EventsOut
from mealie.schema.user import UserInDB
from sqlalchemy.orm.session import Session
router = APIRouter(prefix="/events", tags=["App Events"])
@router.get("", response_model=EventsOut)
async def get_events(session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
async def get_events(session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)):
""" 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"))
@router.delete("")
async def delete_events(session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
async def delete_events(
session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
):
""" Get event from the Database """
# Get Item
return db.events.delete_all(session)
@router.delete("/{id}")
async def delete_event(id: int, session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
async def delete_event(
id: int, session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
):
""" Delete event from the Database """
return db.events.delete(session, id)
@router.post("/notifications")
async def create_event_notification(
event_data: EventNotificationIn,
session: Session = Depends(generate_session),
current_user: UserInDB = Depends(get_current_user),
):
""" Create event_notification in the Database """
return db.event_notifications.create(session, event_data)
@router.get("/notifications", response_model=list[EventNotificationOut])
async def get_all_event_notification(
session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
):
""" Get all event_notification from the Database """
# Get Item
return db.event_notifications.get_all(session, override_schema=EventNotificationOut)
@router.put("/notifications/{id}")
async def update_event_notification(
id: int, session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
):
""" Update event_notification in the Database """
# Update Item
return {"details": "not yet implemented"}
@router.delete("/notifications/{id}")
async def delete_event_notification(
id: int, session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)
):
""" Delete event_notification from the Database """
# Delete Item
return db.event_notifications.delete(session, id)

View file

@ -0,0 +1,59 @@
from enum import Enum
from typing import Optional
from fastapi_camelcase import CamelModel
class DeclaredTypes(str, Enum):
general = "General"
discord = "Discord"
gotify = "Gotify"
class EventNotificationOut(CamelModel):
id: Optional[int]
name: str = ""
type: DeclaredTypes = DeclaredTypes.general
general: bool = True
recipe: bool = True
backup: bool = True
scheduled: bool = True
migration: bool = True
group: bool = True
user: bool = True
class Config:
orm_mode = True
class EventNotificationIn(EventNotificationOut):
notification_url: str = ""
class Config:
orm_mode = True
class Discord(CamelModel):
webhook_id: str
webhook_token: str
@property
def create_url(self) -> str:
return f"discord://{self.webhook_id}/{self.webhook_token}/"
class GotifyPriority(str, Enum):
low = "low"
moderate = "moderate"
normal = "normal"
high = "high"
class Gotify(CamelModel):
hostname: str
token: str
priority: GotifyPriority = GotifyPriority.normal
@property
def create_url(self) -> str:
return f"gotifys://{self.hostname}/{self.token}/?priority={self.priority}"

View file

@ -0,0 +1,32 @@
import apprise
from mealie.schema.event_notifications import EventNotificationIn
from mealie.schema.events import Event
def post_notifications(event: Event, notification_dests=list[EventNotificationIn]):
# Create an Apprise instance
# discord = Discord(
# webhook_id="840358511842295829",
# webhook_token="EtcH6ACFM-qpHRkPw1TUTc_r8AiVMlKYhGEzlANvXj7SlGGFZt18dkYy96ayZHZ8HaI9",
# )
# Create our asset object
asset = apprise.AppriseAsset(async_mode=False)
# Create our object
apobj = apprise.Apprise(asset=asset)
# Add all of the notification services by their server url.
# A sample email notification:
for dest in notification_dests:
dest: EventNotificationIn
apobj.add(dest.notification_url)
# A sample pushbullet notification
# Then notify these services any time you desire. The below would
# notify all of the services loaded into our Apprise object.
apobj.notify(
body=event.text,
title=event.title,
)

View file

@ -1,6 +1,7 @@
from mealie.db.database import db
from mealie.db.db_setup import create_session
from mealie.schema.events import Event, EventCategory
from mealie.services.event_notifications import post_notifications
from sqlalchemy.orm.session import Session
@ -9,6 +10,9 @@ def save_event(title, text, category, session: Session):
session = session or create_session()
db.events.create(session, event.dict())
notification_objects = db.event_notifications.get(session=session, match_value=True, match_key=category, limit=9999)
post_notifications(event, notification_objects)
def create_general_event(title, text, session=None):
category = EventCategory.general
@ -17,6 +21,7 @@ def create_general_event(title, text, session=None):
def create_recipe_event(title, text, session=None):
category = EventCategory.recipe
save_event(title=title, text=text, category=category, session=session)

95
poetry.lock generated
View file

@ -22,6 +22,23 @@ category = "main"
optional = false
python-versions = "*"
[[package]]
name = "apprise"
version = "0.9.2"
description = "Push Notifications that work with just about every platform!"
category = "main"
optional = false
python-versions = ">=2.7"
[package.dependencies]
click = ">=5.0"
cryptography = "*"
markdown = "*"
PyYAML = "*"
requests = "*"
requests-oauthlib = "*"
six = "*"
[[package]]
name = "apscheduler"
version = "3.7.0"
@ -189,6 +206,25 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
toml = ["toml"]
[[package]]
name = "cryptography"
version = "3.4.7"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
cffi = ">=1.12"
[package.extras]
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
sdist = ["setuptools-rust (>=0.11.4)"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
[[package]]
name = "decorator"
version = "5.0.7"
@ -464,7 +500,7 @@ source = ["Cython (>=0.29.7)"]
name = "markdown"
version = "3.3.4"
description = "Python implementation of Markdown."
category = "dev"
category = "main"
optional = false
python-versions = ">=3.6"
@ -573,6 +609,19 @@ plot = ["matplotlib"]
tgrep = ["pyparsing"]
twitter = ["twython"]
[[package]]
name = "oauthlib"
version = "3.1.0"
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.extras]
rsa = ["cryptography"]
signals = ["blinker"]
signedtoken = ["cryptography", "pyjwt (>=1.0.0)"]
[[package]]
name = "packaging"
version = "20.9"
@ -916,6 +965,21 @@ urllib3 = ">=1.21.1,<1.27"
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
[[package]]
name = "requests-oauthlib"
version = "1.3.0"
description = "OAuthlib authentication support for Requests."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
oauthlib = ">=3.0.0"
requests = ">=2.0.0"
[package.extras]
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
[[package]]
name = "rsa"
version = "4.7.2"
@ -1172,7 +1236,7 @@ python-versions = "*"
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
content-hash = "bfdb4d3d5d69e53f16b315f993b712a703058d3f59e24644681ccc9062cf5143"
content-hash = "73bac73c62e64c90a29816dde9ef1d896e8ca0b4271e67cde6ca8cc56bd87efd"
[metadata.files]
aiofiles = [
@ -1187,6 +1251,10 @@ appdirs = [
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
]
apprise = [
{file = "apprise-0.9.2-py2.py3-none-any.whl", hash = "sha256:e3f34fcf7cd717704f04b194f6fd62719e563b7d338415467e0cd0fbd7fc5e61"},
{file = "apprise-0.9.2.tar.gz", hash = "sha256:263bd6ed81cb33f3d24c9353506921129890a654965fbe9014f7f562bf2ba9f9"},
]
apscheduler = [
{file = "APScheduler-3.7.0-py2.py3-none-any.whl", hash = "sha256:c06cc796d5bb9eb3c4f77727f6223476eb67749e7eea074d1587550702a7fbe3"},
{file = "APScheduler-3.7.0.tar.gz", hash = "sha256:1cab7f2521e107d07127b042155b632b7a1cd5e02c34be5a28ff62f77c900c6a"},
@ -1329,6 +1397,20 @@ coverage = [
{file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"},
{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"},
]
decorator = [
{file = "decorator-5.0.7-py3-none-any.whl", hash = "sha256:945d84890bb20cc4a2f4a31fc4311c0c473af65ea318617f13a7257c9a58bc98"},
{file = "decorator-5.0.7.tar.gz", hash = "sha256:6f201a6c4dac3d187352661f508b9364ec8091217442c9478f1f83c003a0f060"},
@ -1617,6 +1699,10 @@ nltk = [
{file = "nltk-3.6.2-py3-none-any.whl", hash = "sha256:240e23ab1ab159ef9940777d30c7c72d7e76d91877099218a7585370c11f6b9e"},
{file = "nltk-3.6.2.zip", hash = "sha256:57d556abed621ab9be225cc6d2df1edce17572efb67a3d754630c9f8381503eb"},
]
oauthlib = [
{file = "oauthlib-3.1.0-py2.py3-none-any.whl", hash = "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"},
{file = "oauthlib-3.1.0.tar.gz", hash = "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889"},
]
packaging = [
{file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
{file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
@ -1854,6 +1940,11 @@ requests = [
{file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
{file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
]
requests-oauthlib = [
{file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"},
{file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"},
{file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"},
]
rsa = [
{file = "rsa-4.7.2-py3-none-any.whl", hash = "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2"},
{file = "rsa-4.7.2.tar.gz", hash = "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9"},

View file

@ -1,64 +1,65 @@
[tool.poetry]
name = "mealie"
version = "0.5.0b"
description = "A Recipe Manager"
authors = ["Hayden <hay-kot@pm.me>"]
license = "MIT"
[tool.poetry.scripts]
start = "mealie.app:main"
[tool.poetry.dependencies]
python = "^3.9"
aiofiles = "0.5.0"
aniso8601 = "7.0.0"
appdirs = "1.4.4"
fastapi = "^0.63.0"
uvicorn = {extras = ["standard"], version = "^0.13.0"}
APScheduler = "^3.6.3"
SQLAlchemy = "^1.3.22"
Jinja2 = "^2.11.2"
python-dotenv = "^0.15.0"
python-slugify = "^4.0.1"
requests = "^2.25.1"
PyYAML = "^5.3.1"
extruct = "^0.12.0"
scrape-schema-recipe = "^0.1.3"
python-multipart = "^0.0.5"
fastapi-camelcase = "^1.0.2"
bcrypt = "^3.2.0"
python-jose = "^3.2.0"
passlib = "^1.7.4"
lxml = "4.6.2"
Pillow = "^8.2.0"
pathvalidate = "^2.4.1"
[tool.poetry.dev-dependencies]
pylint = "^2.6.0"
black = "^20.8b1"
pytest = "^6.2.1"
pytest-cov = "^2.11.0"
mkdocs-material = "^7.0.2"
flake8 = "^3.9.0"
coverage = "^5.5"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.black]
line-length = 120
[tool.pytest.ini_options]
minversion = "6.0"
addopts = "-ra -q --cov=mealie"
python_files = 'test_*'
python_classes = '*Tests'
python_functions = 'test_*'
testpaths = [
"tests",
]
[tool.coverage.report]
[tool.poetry]
name = "mealie"
version = "0.5.0b"
description = "A Recipe Manager"
authors = ["Hayden <hay-kot@pm.me>"]
license = "MIT"
[tool.poetry.scripts]
start = "mealie.app:main"
[tool.poetry.dependencies]
python = "^3.9"
aiofiles = "0.5.0"
aniso8601 = "7.0.0"
appdirs = "1.4.4"
fastapi = "^0.63.0"
uvicorn = {extras = ["standard"], version = "^0.13.0"}
APScheduler = "^3.6.3"
SQLAlchemy = "^1.3.22"
Jinja2 = "^2.11.2"
python-dotenv = "^0.15.0"
python-slugify = "^4.0.1"
requests = "^2.25.1"
PyYAML = "^5.3.1"
extruct = "^0.12.0"
scrape-schema-recipe = "^0.1.3"
python-multipart = "^0.0.5"
fastapi-camelcase = "^1.0.2"
bcrypt = "^3.2.0"
python-jose = "^3.2.0"
passlib = "^1.7.4"
lxml = "4.6.2"
Pillow = "^8.2.0"
pathvalidate = "^2.4.1"
apprise = "^0.9.2"
[tool.poetry.dev-dependencies]
pylint = "^2.6.0"
black = "^20.8b1"
pytest = "^6.2.1"
pytest-cov = "^2.11.0"
mkdocs-material = "^7.0.2"
flake8 = "^3.9.0"
coverage = "^5.5"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.black]
line-length = 120
[tool.pytest.ini_options]
minversion = "6.0"
addopts = "-ra -q --cov=mealie"
python_files = 'test_*'
python_classes = '*Tests'
python_functions = 'test_*'
testpaths = [
"tests",
]
[tool.coverage.report]
skip_empty = true