Change how config works, add missing models
This commit is contained in:
parent
f0a4deaf72
commit
60090951cf
2 changed files with 26 additions and 144 deletions
|
@ -1,142 +0,0 @@
|
||||||
"""Load global config."""
|
|
||||||
import ast
|
|
||||||
from os import environ
|
|
||||||
from lib2to3.pgen2 import token
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, Dict, List, Optional, Type
|
|
||||||
|
|
||||||
import orjson as json
|
|
||||||
import yaml
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from yaml import CLoader as Loader
|
|
||||||
except ImportError:
|
|
||||||
from yaml import Loader
|
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_YAML = Path("config.yaml")
|
|
||||||
DEFAULT_JSON = Path("config.json")
|
|
||||||
|
|
||||||
|
|
||||||
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_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
|
|
||||||
|
|
||||||
if isinstance(filepath, str):
|
|
||||||
filepath = Path(filepath)
|
|
||||||
|
|
||||||
with filepath.open() as f:
|
|
||||||
raw = f.read()
|
|
||||||
|
|
||||||
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."""
|
|
||||||
return cls.__dict__.pop("inst", None) is None
|
|
|
@ -1,6 +1,7 @@
|
||||||
"""JARVIS database models."""
|
"""JARVIS database models."""
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from beanie import Document, Link
|
from beanie import Document, Link
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
@ -28,10 +29,12 @@ __all__ = [
|
||||||
"Purge",
|
"Purge",
|
||||||
"Reminder",
|
"Reminder",
|
||||||
"Rolegiver",
|
"Rolegiver",
|
||||||
|
"Bypass",
|
||||||
"Roleping",
|
"Roleping",
|
||||||
"Setting",
|
"Setting",
|
||||||
"Subreddit",
|
"Subreddit",
|
||||||
"SubredditFollow",
|
"SubredditFollow",
|
||||||
|
"Tag",
|
||||||
"Temprole",
|
"Temprole",
|
||||||
"TwitterAccount",
|
"TwitterAccount",
|
||||||
"TwitterFollow",
|
"TwitterFollow",
|
||||||
|
@ -74,6 +77,14 @@ class Config(Document):
|
||||||
value: str | int | bool
|
value: str | int | bool
|
||||||
|
|
||||||
|
|
||||||
|
class Filter(Document):
|
||||||
|
"""Filter database object."""
|
||||||
|
|
||||||
|
guild: int
|
||||||
|
name: str
|
||||||
|
filters: list[str] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
class Guess(Document):
|
class Guess(Document):
|
||||||
"""Guess database object."""
|
"""Guess database object."""
|
||||||
|
|
||||||
|
@ -167,7 +178,7 @@ class Setting(Document):
|
||||||
|
|
||||||
guild: int
|
guild: int
|
||||||
setting: str
|
setting: str
|
||||||
value: str | int | bool
|
value: str | int | bool | list[int | str]
|
||||||
|
|
||||||
|
|
||||||
class Pinboard(Document):
|
class Pinboard(Document):
|
||||||
|
@ -189,10 +200,22 @@ class Pin(Document):
|
||||||
pinboard: Link[Pinboard]
|
pinboard: Link[Pinboard]
|
||||||
guild: int
|
guild: int
|
||||||
admin: int
|
admin: int
|
||||||
star: int
|
pin: int
|
||||||
created_at: datetime = NowField()
|
created_at: datetime = NowField()
|
||||||
|
|
||||||
|
|
||||||
|
class Tag(Document):
|
||||||
|
"""Tag database object."""
|
||||||
|
|
||||||
|
creator: int
|
||||||
|
name: str
|
||||||
|
content: str
|
||||||
|
guild: int
|
||||||
|
created_at: datetime = NowField()
|
||||||
|
edited_at: Optional[datetime] = None
|
||||||
|
editor: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
class Temprole(Document):
|
class Temprole(Document):
|
||||||
"""Temporary role object."""
|
"""Temporary role object."""
|
||||||
|
|
||||||
|
@ -239,6 +262,7 @@ all_models = [
|
||||||
Setting,
|
Setting,
|
||||||
Subreddit,
|
Subreddit,
|
||||||
SubredditFollow,
|
SubredditFollow,
|
||||||
|
Tag,
|
||||||
Temprole,
|
Temprole,
|
||||||
TwitterAccount,
|
TwitterAccount,
|
||||||
TwitterFollow,
|
TwitterFollow,
|
||||||
|
|
Loading…
Add table
Reference in a new issue