Migrate to beanie

This commit is contained in:
Zeva Rose 2023-03-23 21:56:54 -06:00
parent 7bb9b25f63
commit eb36cf7413
11 changed files with 970 additions and 737 deletions

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"python.formatting.provider": "black"
}

View file

@ -1,10 +1,14 @@
"""Load global config."""
import ast
from os import environ
from pathlib import Path
from typing import Union
from typing import Any, Dict, List, Optional, Type
from yaml import load
import orjson as json
import yaml
from dotenv import load_dotenv
from pydantic import BaseModel
from jarvis_core.util import Singleton
try:
from yaml import CLoader as Loader
@ -12,15 +16,88 @@ except ImportError:
from yaml import Loader
DEFAULT_PATH = Path("config.yaml")
DEFAULT_YAML = Path("config.yaml")
DEFAULT_JSON = Path("config.json")
class Config(Singleton):
REQUIRED = []
OPTIONAL = {}
class Config:
REQUIRED: Dict[str, Type] = {}
OPTIONAL: Dict[str, Dict[str, Any]] = {}
def __new__(cls, *args: list, **kwargs: dict):
"""Create a new Config object."""
for arg, flags in cls.OPTIONAL.items():
kwargs[arg] = kwargs.get(arg, flags["default"])
inst = super().__new__(cls, *args, **kwargs)
inst._validate()
return inst
def _validate(self) -> None:
for key in self.REQUIRED.keys():
if getattr(self, key, None) is None:
raise ValueError(f"Missing required key: {key}")
@classmethod
def from_yaml(cls, filepath: Union[Path, str] = DEFAULT_PATH) -> "Config":
def from_env(cls) -> "Config":
"""Load the .env config file."""
load_dotenv()
data = {}
for k, t in cls.REQUIRED.items():
value = environ.get(k.upper(), None)
if value and not isinstance(value, t):
if t is dict:
try:
value = ast.literal_eval(value)
except Exception:
raise ValueError(f"{k} is a dict but is not formatted properly")
elif t is bool:
value = value.lower() in ["true", "yes", "t", "y", "1"]
else:
try:
value = t(value)
except Exception:
continue
data[k] = value
for k, flags in cls.OPTIONAL.items():
t = flags["type"]
value = environ.get(k.upper(), flags["default"])
if value and not isinstance(value, t):
if t is dict:
try:
value = ast.literal_eval(value)
except Exception:
raise ValueError(f"{k} is a dict but is not formatted properly")
elif t is bool:
value = value.lower() in ["true", "yes", "t", "y", "1"]
else:
try:
value = t(value)
except Exception:
continue
data[k] = value
return cls(**data)
@classmethod
def from_json(cls, filepath: Path | str = DEFAULT_JSON) -> "Config":
"""Load the json config file."""
if inst := cls.__dict__.get("inst"):
return inst
if isinstance(filepath, str):
filepath = Path(filepath)
with filepath.open() as f:
raw = f.read()
j = json.loads(raw)
return cls(**j)
@classmethod
def from_yaml(cls, filepath: Path | str = DEFAULT_YAML) -> "Config":
"""Load the yaml config file."""
if inst := cls.__dict__.get("inst"):
return inst
@ -31,9 +108,33 @@ class Config(Singleton):
with filepath.open() as f:
raw = f.read()
y = load(raw, Loader=Loader)
y = yaml.load(raw, Loader=Loader)
return cls(**y)
@classmethod
def load(cls, method: Optional[str] = None) -> "Config":
"""
Load the config in a somewhat generic way.
Default load order is: yaml, json, env
Args:
method: Load method, one of: yaml, json, env
"""
methods = ["yaml", "json", "env"]
if method and method in methods:
methods.remove(method)
methods.insert(0, method)
for method in methods:
try:
m = cls.__dict__.get(f"from_{method}", None)
if m:
return m()
except Exception:
continue
raise ValueError("Unable to load configuration, please create one of: config.yaml, config.json, .env")
@classmethod
def reload(cls) -> bool:
"""Reload the config."""

View file

@ -1,20 +1,13 @@
"""JARVIS database models and utilities."""
from bson import ObjectId
from motor.motor_asyncio import AsyncIOMotorClient
from pytz import utc
from umongo.frameworks import MotorAsyncIOInstance
from beanie import init_beanie
from jarvis_core.util import find
CLIENT = None
JARVISDB = None
CTC2DB = None
JARVIS_INST = MotorAsyncIOInstance()
CTC2_INST = MotorAsyncIOInstance()
from jarvis_core.db.models import all_models
def connect(
host: str, username: str, password: str, port: int = 27017, testing: bool = False
async def connect(
host: str, username: str, password: str, port: int = 27017, testing: bool = False, extra_models: list = []
) -> None:
"""
Connect to MongoDB.
@ -24,66 +17,10 @@ def connect(
username: Username
password: Password
port: Port
testing: Whether or not to use jarvis_dev
extra_models: Extra beanie models to register
"""
global CLIENT, JARVISDB, CTC2DB, JARVIS_INST, CTC2_INST
CLIENT = AsyncIOMotorClient(
host=host, username=username, password=password, port=port, tz_aware=True, tzinfo=utc
)
JARVISDB = CLIENT.narvis if testing else CLIENT.jarvis
CTC2DB = CLIENT.ctc2
JARVIS_INST.set_db(JARVISDB)
CTC2_INST.set_db(CTC2DB)
QUERY_OPS = ["ne", "lt", "lte", "gt", "gte", "not", "in", "nin", "mod", "all", "size"]
STRING_OPS = [
"exact",
"iexact",
"contains",
"icontains",
"startswith",
"istartswith",
"endswith",
"iendswith",
"wholeword",
"iwholeword",
"regex",
"iregex" "match",
]
GEO_OPS = [
"get_within",
"geo_within_box",
"geo_within_polygon",
"geo_within_center",
"geo_within_sphere",
"geo_intersects",
"near",
"within_distance",
"within_spherical_distance",
"near_sphere",
"within_box",
"within_polygon",
"max_distance",
"min_distance",
]
ALL_OPS = QUERY_OPS + STRING_OPS + GEO_OPS
def q(**kwargs: dict) -> dict:
"""uMongo query wrapper.""" # noqa: D403
query = {}
for key, value in kwargs.items():
if key == "_id":
value = ObjectId(value)
elif "__" in key:
args = key.split("__")
if not any(x in ALL_OPS for x in args):
key = ".".join(args)
else:
idx = args.index(find(lambda x: x in ALL_OPS, args))
key = ".".join(args[:idx])
value = {f"${args[idx]}": value}
query[key] = value
return query
client = AsyncIOMotorClient(host=host, username=username, password=password, port=port, tz_aware=True, tzinfo=utc)
db = client.jarvis_dev if testing else client.jarvis
await init_beanie(database=db, document_models=all_models + extra_models)

View file

@ -1,205 +1,248 @@
"""JARVIS database models."""
from datetime import datetime, timezone
from typing import Any, List
from functools import partial
import marshmallow as ma
from umongo import Document, EmbeddedDocument, fields
from beanie import Document, Link
from pydantic import BaseModel, Field
from jarvis_core.db import CTC2_INST, JARVIS_INST
from jarvis_core.db.models.actions import Ban, Kick, Mute, Unban, Warning
from jarvis_core.db.models.modlog import Action, Modlog, Note
from jarvis_core.db.models.reddit import Subreddit, SubredditFollow
from jarvis_core.db.models.twitter import TwitterAccount, TwitterFollow
__all__ = [
"Action",
"Autopurge",
"Autoreact",
"Ban",
"Config",
"Guess",
"Kick",
"Lock",
"Lockdown",
"Modlog",
"Mute",
"Note",
"Pin",
"Pinboard",
"Purge",
"Reminder",
"Rolegiver",
"Roleping",
"Setting",
"Subreddit",
"SubredditFollow",
"Temprole",
"TwitterAccount",
"TwitterFollow",
"Unban",
"UserSetting",
"Warning",
"all_models",
]
def get_now() -> datetime:
"""Get proper timestamp."""
return datetime.now(tz=timezone.utc)
class RawField(fields.BaseField, ma.fields.Raw):
pass
NowField = partial(Field, default_factory=get_now)
@JARVIS_INST.register
class Autopurge(Document):
guild: int = fields.IntegerField(required=True)
channel: int = fields.IntegerField(required=True)
delay: int = fields.IntegerField(default=30)
admin: int = fields.IntegerField(required=True)
created_at: datetime = fields.DateTimeField(default=datetime.now)
guild: int
channel: int
delay: int = 30
admin: int
created_at: datetime = NowField()
@JARVIS_INST.register
class Autoreact(Document):
guild: int = fields.IntegerField(required=True)
channel: int = fields.IntegerField(required=True)
reactions: List[str] = fields.ListField(fields.StringField())
admin: int = fields.IntegerField(required=True)
thread: bool = fields.BooleanField(default=True)
created_at: datetime = fields.DateTimeField(default=datetime.now)
guild: int
channel: int
reactions: list[str] = Field(default_factory=list)
admin: int
thread: bool
created_at: datetime = NowField()
@JARVIS_INST.register
class Config(Document):
"""Config database object."""
key: str = fields.StringField(required=True)
value: Any = RawField(required=True)
key: str
value: str | int | bool
@CTC2_INST.register
class Guess(Document):
"""Guess database object."""
correct: bool = fields.BooleanField(default=False)
guess: str = fields.StringField(required=True)
user: int = fields.IntegerField(required=True)
correct: bool
guess: str
user: int
@JARVIS_INST.register
class Permission(EmbeddedDocument):
class Permission(BaseModel):
"""Embedded Permissions document."""
id: int = fields.IntegerField(required=True)
allow: int = fields.IntegerField(default=0)
deny: int = fields.IntegerField(default=0)
id: int
allow: int = 0
deny: int = 0
@JARVIS_INST.register
class Lock(Document):
"""Lock database object."""
active: bool = fields.BooleanField(default=True)
admin: int = fields.IntegerField(required=True)
channel: int = fields.IntegerField(required=True)
duration: int = fields.IntegerField(default=10)
guild: int = fields.IntegerField(required=True)
reason: str = fields.StringField(required=True)
original_perms: Permission = fields.EmbeddedField(Permission, required=False)
created_at: datetime = fields.DateTimeField(default=get_now)
active: bool = True
admin: int
channel: int
duration: int = 10
reason: str
original_perms: Permission
created_at: datetime = NowField()
@JARVIS_INST.register
class Lockdown(Document):
"""Lockdown database object."""
active: bool = fields.BooleanField(default=True)
admin: int = fields.IntegerField(required=True)
duration: int = fields.IntegerField(default=10)
guild: int = fields.IntegerField(required=True)
reason: str = fields.StringField(required=True)
original_perms: int = fields.IntegerField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
active: bool = True
admin: int
duration: int = 10
guild: int
reason: str
original_perms: int
created_at: datetime = NowField()
@JARVIS_INST.register
class Purge(Document):
"""Purge database object."""
admin: int = fields.IntegerField(required=True)
channel: int = fields.IntegerField(required=True)
guild: int = fields.IntegerField(required=True)
count: int = fields.IntegerField(default=10)
created_at: datetime = fields.DateTimeField(default=get_now)
admin: int
channel: int
guild: int
count: int = 10
created_at: datetime = NowField()
@JARVIS_INST.register
class Reminder(Document):
"""Reminder database object."""
active: bool = fields.BooleanField(default=True)
user: int = fields.IntegerField(required=True)
guild: int = fields.IntegerField(required=True)
channel: int = fields.IntegerField(required=True)
message: str = fields.StringField(required=True)
remind_at: datetime = fields.DateTimeField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
private: bool = fields.BooleanField(default=False)
active: bool = True
user: int
guild: int
channel: int
message: str
remind_at: datetime
created_at: datetime = NowField()
private: bool = False
@JARVIS_INST.register
class Rolegiver(Document):
"""Rolegiver database object."""
guild: int = fields.IntegerField(required=True)
roles: List[int] = fields.ListField(fields.IntegerField())
guild: int
roles: list[int]
@JARVIS_INST.register
class Bypass(EmbeddedDocument):
class Bypass(BaseModel):
"""Roleping bypass embedded object."""
users: List[int] = fields.ListField(fields.IntegerField())
roles: List[int] = fields.ListField(fields.IntegerField())
users: list[int]
roles: list[int]
@JARVIS_INST.register
class Roleping(Document):
"""Roleping database object."""
active: bool = fields.BooleanField(default=True)
role: int = fields.IntegerField(required=True)
guild: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
bypass: Bypass = fields.EmbeddedField(Bypass)
created_at: datetime = fields.DateTimeField(default=get_now)
active: bool = True
role: int
guild: int
admin: int
bypass: Bypass
created_at: datetime = NowField()
@JARVIS_INST.register
class Setting(Document):
"""Setting database object."""
guild: int = fields.IntegerField(required=True)
setting: str = fields.StringField(required=True)
value: Any = RawField()
guild: int
setting: str
value: str | int | bool
@JARVIS_INST.register
class Star(Document):
"""Star database object."""
class Pinboard(Document):
"""Pinboard database object."""
active: bool = fields.BooleanField(default=True)
index: int = fields.IntegerField(required=True)
message: int = fields.IntegerField(required=True)
channel: int = fields.IntegerField(required=True)
starboard: int = fields.IntegerField(required=True)
guild: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
star: int = fields.IntegerField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
channel: int
guild: int
admin: int
created_at: datetime = NowField()
@JARVIS_INST.register
class Starboard(Document):
"""Starboard database object."""
class Pin(Document):
"""Pin database object."""
channel: int = fields.IntegerField(required=True)
guild: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
active: bool = True
index: int
message: int
channel: int
pinboard: Link[Pinboard]
guild: int
admin: int
star: int
created_at: datetime = NowField()
@JARVIS_INST.register
class Temprole(Document):
"""Temporary role object."""
guild: int = fields.IntegerField(required=True)
user: int = fields.IntegerField(required=True)
role: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
expires_at: datetime = fields.DateTimeField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
guild: int
user: int
role: int
admin: int
expires_at: datetime
reapply_on_rejoin: bool = True
created_at: datetime = NowField()
@JARVIS_INST.register
class UserSetting(Document):
"""User Setting object."""
user: int = fields.IntegerField(required=True)
type: str = fields.StringField(required=True)
setting: str = fields.StringField(required=True)
value: Any = RawField()
user: int
type: str
settings: str
value: str | int | bool
class Meta:
collection_name = "usersetting"
class Setting:
name = "usersetting"
all_models = [
Action,
Autopurge,
Autoreact,
Ban,
Config,
Guess,
Kick,
Lock,
Lockdown,
Modlog,
Mute,
Note,
Pin,
Pinboard,
Purge,
Reminder,
Rolegiver,
Roleping,
Setting,
Subreddit,
SubredditFollow,
Temprole,
TwitterAccount,
TwitterFollow,
Unban,
UserSetting,
Warning,
]

View file

@ -1,9 +1,9 @@
"""User action models."""
from datetime import datetime, timezone
from functools import partial
from typing import Optional
from umongo import Document, fields
from jarvis_core.db import JARVIS_INST
from beanie import Document, Field
def get_now() -> datetime:
@ -11,66 +11,63 @@ def get_now() -> datetime:
return datetime.now(tz=timezone.utc)
@JARVIS_INST.register
NowField = partial(Field, default_factory=get_now)
class Ban(Document):
active: bool = fields.BooleanField(default=True)
admin: int = fields.IntegerField(required=True)
user: int = fields.IntegerField(required=True)
username: str = fields.StringField(required=True)
discrim: int = fields.IntegerField(required=True)
duration: int = fields.IntegerField(required=False, default=None)
guild: int = fields.IntegerField(required=True)
type: str = fields.StringField(default="perm")
reason: str = fields.StringField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
active: bool = True
admin: int
user: int
username: str
discrim: int
duration: Optional[int]
guild: int
type: str = "perm"
reason: str
created_at: datetime = NowField()
@JARVIS_INST.register
class Kick(Document):
"""Kick database object."""
admin: int = fields.IntegerField(required=True)
guild: int = fields.IntegerField(required=True)
reason: str = fields.StringField(required=True)
user: int = fields.IntegerField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
admin: int
guild: int
reason: str
user: int
created_at: datetime = NowField()
@JARVIS_INST.register
class Mute(Document):
"""Mute database object."""
active: bool = fields.BooleanField(default=True)
user: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
duration: int = fields.IntegerField(default=10)
guild: int = fields.IntegerField(required=True)
reason: str = fields.StringField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
active: bool = True
user: int
admin: int
duration: int = 10
guild: int
reason: str
created_at: datetime = NowField()
@JARVIS_INST.register
class Unban(Document):
"""Unban database object."""
user: int = fields.IntegerField(required=True)
username: str = fields.StringField(required=True)
discrim: int = fields.IntegerField(required=True)
guild: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
reason: str = fields.StringField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
user: int
username: str
discrim: str
guild: int
reason: str
created_at: datetime = NowField()
@JARVIS_INST.register
class Warning(Document):
"""Warning database object."""
active: bool = fields.BooleanField(default=True)
admin: int = fields.IntegerField(required=True)
user: int = fields.IntegerField(required=True)
guild: int = fields.IntegerField(required=True)
duration: int = fields.IntegerField(default=24)
reason: str = fields.StringField(required=True)
expires_at: datetime = fields.DateTimeField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
active: bool = True
admin: int
user: int
guild: int
duration: int = 24
reason: str
expires_at: datetime
created_at: datetime = NowField()

View file

@ -1,12 +1,11 @@
"""Modlog database models."""
from datetime import datetime, timezone
from typing import List
from functools import partial
import nanoid
from bson import ObjectId
from umongo import Document, EmbeddedDocument, fields
from jarvis_core.db import JARVIS_INST
from beanie import Document
from pydantic import BaseModel, Field
NANOID_ALPHA = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
@ -21,33 +20,34 @@ def get_id() -> str:
return nanoid.generate(NANOID_ALPHA, 12)
@JARVIS_INST.register
class Action(EmbeddedDocument):
NanoField = partial(Field, default_factory=get_id)
NowField = partial(Field, default_factory=get_now)
class Action(BaseModel):
"""Modlog embedded action document."""
action_type: str = fields.StringField(required=True)
parent: ObjectId = fields.ObjectIdField(required=True)
orphaned: bool = fields.BoolField(default=False)
action_type: str
parent: ObjectId
orphaned: bool = False
@JARVIS_INST.register
class Note(EmbeddedDocument):
class Note(BaseModel):
"""Modlog embedded note document."""
admin: int = fields.IntegerField(required=True)
content: str = fields.StrField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
admin: int
content: str
created_at: datetime = NowField()
@JARVIS_INST.register
class Modlog(Document):
"""Modlog database object."""
user: int = fields.IntegerField(required=True)
nanoid: str = fields.StringField(default=get_id)
guild: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
actions: List[Action] = fields.ListField(fields.EmbeddedField(Action), factory=list)
open: bool = fields.BoolField(default=True)
created_at: datetime = fields.DateTimeField(default=get_now)
notes: List[Note] = fields.ListField(fields.EmbeddedField(Note), factory=list)
user: int
nanoid: str = NanoField()
guild: int
admin: int
actions: list[Action] = Field(default_factory=list)
notes: list[Note] = Field(default_factory=list)
open: bool = True
created_at: datetime = NowField

View file

@ -1,9 +1,8 @@
"""Reddit databaes models."""
from datetime import datetime, timezone
from functools import partial
from umongo import Document, fields
from jarvis_core.db import JARVIS_INST
from beanie import Document, Field
def get_now() -> datetime:
@ -11,24 +10,25 @@ def get_now() -> datetime:
return datetime.now(tz=timezone.utc)
@JARVIS_INST.register
NowField = partial(Field, default_factory=get_now)
class Subreddit(Document):
"""Subreddit object."""
display_name: str = fields.StringField(required=True)
over18: bool = fields.BooleanField(default=False)
display_name: str
over18: bool = False
@JARVIS_INST.register
class SubredditFollow(Document):
"""Subreddit Follow object."""
active: bool = fields.BooleanField(default=True)
display_name: str = fields.StringField(required=True)
channel: int = fields.IntegerField(required=True)
guild: int = fields.IntegerField(required=True)
admin: int = fields.IntegerField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
active: bool = True
display_name: str
channel: int
guild: int
admin: int
created_at: datetime = NowField()
class Meta:
collection_name = "subredditfollow"
class Setting:
name = "subredditfollow"

View file

@ -1,9 +1,8 @@
"""Twitter database models."""
from datetime import datetime, timezone
from functools import partial
from umongo import Document, fields
from jarvis_core.db import JARVIS_INST
from beanie import Document, Field
def get_now() -> datetime:
@ -11,30 +10,31 @@ def get_now() -> datetime:
return datetime.now(tz=timezone.utc)
@JARVIS_INST.register
NowField = partial(Field, default_factory=get_now)
class TwitterAccount(Document):
"""Twitter Account object."""
handle: str = fields.StringField(required=True)
twitter_id: int = fields.IntegerField(required=True)
last_tweet: int = fields.IntegerField(required=True)
last_sync: datetime = fields.DateTimeField(default=get_now)
handle: str
twitter_id: int
last_tweet: int
last_sync: datetime = NowField()
class Meta:
collection_name = "twitteraccount"
class Setting:
name = "twitteraccount"
@JARVIS_INST.register
class TwitterFollow(Document):
"""Twitter Follow object."""
active: bool = fields.BooleanField(default=True)
twitter_id: int = fields.IntegerField(required=True)
channel: int = fields.IntegerField(required=True)
guild: int = fields.IntegerField(required=True)
retweets: bool = fields.BooleanField(default=True)
admin: int = fields.IntegerField(required=True)
created_at: datetime = fields.DateTimeField(default=get_now)
active: bool = True
twitter_id: int
channel: int
guild: int
retweets: bool = True
admin: int
created_at: datetime = NowField()
class Meta:
collection_name = "twitterfollow"
class Setting:
name = "twitterfollow"

View file

@ -9,38 +9,6 @@ from jarvis_core.filters import url
DEFAULT_BLOCKSIZE = 8 * 1024 * 1024
class Singleton(object):
REQUIRED = []
OPTIONAL = {}
def __new__(cls, *args: list, **kwargs: dict):
"""Create a new singleton."""
inst = cls.__dict__.get("inst")
if inst is not None:
return inst
inst = object.__new__(cls)
inst.init(*args, **kwargs)
inst._validate()
cls.__inst__ = inst
return inst
def _validate(self) -> None:
for key in self.REQUIRED:
if not getattr(self, key, None):
raise ValueError(f"Missing required key: {key}")
def init(self, **kwargs: dict) -> None:
"""Initialize the object."""
for key, value in kwargs.items():
setattr(self, key, value)
for key, value in self.OPTIONAL.items():
if not getattr(self, key, None):
setattr(self, key, value)
async def hash(
data: str, method: Union[Callable, str] = hashlib.sha256, size: int = DEFAULT_BLOCKSIZE
) -> Tuple[str, int, str]:

987
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,18 +6,23 @@ authors = ["Zevaryx <zevaryx@gmail.com>"]
[tool.poetry.dependencies]
python = "^3.10"
orjson = "^3.6.6"
orjson = {version = "^3.6.6"}
motor = "^2.5.1"
umongo = "^3.1.0"
PyYAML = "^6.0"
PyYAML = {version = "^6.0"}
pytz = "^2022.1"
aiohttp = "^3.8.1"
rich = "^12.3.0"
nanoid = "^2.0.0"
python-dotenv = {version = "^0.20.0"}
beanie = "^1.17.0"
pydantic = "^1.10.7"
[tool.poetry.dev-dependencies]
pytest = "^7.1"
[tool.poetry.group.dev.dependencies]
black = "^23.1.0"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"