social/barkshark_social/database/config.py
2024-04-18 22:36:06 -04:00

235 lines
5.4 KiB
Python

from blib import JsonBase, convert_to_boolean
from bsql import Row
from collections.abc import Callable, Iterator, Sequence
from typing import Any, Generic, Self, TypeVar
from .. import TRANS
T = TypeVar("T")
CONVERTERS: dict[type, tuple[Callable[[str], Any], Callable[[Any], str]]] = {
bool: (convert_to_boolean, str),
int: (int, str),
dict: (JsonBase.parse, lambda value: JsonBase(value).to_json()),
str: (str, str)
}
class Value(Generic[T]):
__slots__: tuple[str, ...] = ("default", "type", "description", "name")
def __init__(self, default: T, description: str) -> None:
self.default: T = default
self.type: type[Any] = type(default)
self.description: str = description
self.name: str = ""
def __set_name__(self, owner: Any, name: str) -> None:
self.name = name.replace("_", "-")
def __get__(self, obj: dict[str, str] | None, cls: type[dict[str, str]] | None) -> Self | T:
if obj is None:
return self
try:
return self.deserialize(obj[self.name])
except KeyError:
return self.default
def __set__(self, obj: dict[str, str], value: T) -> None:
if not isinstance(value, self.type):
raise TypeError(f"Value '{value}' is not type '{self.type.__name__}'")
obj.__setitem__(self.name, self.serialize(value), parse_value = False) # type: ignore
def __delete__(self, obj: dict[str, str]) -> None:
self.__set__(obj, self.default)
def serialize(self, value: T) -> str:
if self.type is str and isinstance(value, str):
return value
try:
return CONVERTERS[self.type][1](value)
except KeyError:
raise TypeError(TRANS.fetch("error", "cannot-convert", type = self.type.__name__))
def deserialize(self, value: str) -> T:
if isinstance(value, self.type):
return value # type: ignore[no-any-return]
try:
return CONVERTERS[self.type][0](value) # type: ignore[no-any-return]
except KeyError:
raise TypeError(TRANS.fetch("error", "cannot-convert", type = type(value).__name__))
class ConfigData(dict[str, str]):
__slots__: tuple[str, ...] = tuple([])
version: Value[int] = Value(
20240409, "Version of the database schema"
)
description: Value[str] = Value(
"Lightweight AP server", "Description for the instance"
)
description_short: Value[str] = Value(
"", "Short version of the description for the instance"
)
email: Value[str] = Value(
"", "E-mail address used for contact"
)
invites_enabled: Value[bool] = Value(
True, "Allow users to invite new users"
)
max_attachments: Value[int] = Value(
4, "Maximum number of media attachments allowed in a post"
)
max_audio_size: Value[int] = Value(
10, "Maximum size of uploaded audio files in mebibytes"
)
max_emoji_size: Value[int] = Value(
512, "Maximum size of emojis in kibibytes"
)
max_featured_tags: Value[int] = Value(
8, "Maximum number of tags to display on a user's profile"
)
max_image_size: Value[int] = Value(
2, "Maximum size of uploaded image files in mebibytes"
)
max_name_chars: Value[int] = Value(
64, "Maximum number of characters allowed in a display name or handle"
)
max_pinned_posts: Value[int] = Value(
8, "Maximum number of posts that can be pinned on a profile"
)
max_poll_expiration: Value[int] = Value(
60 * 24 * 7, "Max time in minutes for a poll to be open"
)
max_poll_option_chars: Value[int] = Value(
200, "Maximum characters allowed in a poll option"
)
max_poll_options: Value[int] = Value(
10, "Maxiumum number of poll options in a post"
)
max_post_chars: Value[int] = Value(
4096, "Maximum number of characters allowed in a post or bio"
)
max_profile_fields: Value[int] = Value(
8, "Maximum number of key/value fields allowed in a profile"
)
max_video_size: Value[int] = Value(
100, "Maximum size of uploaded video files in mebibytes"
)
min_poll_expiration: Value[int] = Value(
5, "Minimum time in minutes for a poll to be open"
)
name: Value[str] = Value(
"Barkshark Social", "Name of the instance"
)
registration_open: Value[bool] = Value(
True, "Allow new users to sign up on the instance"
)
require_approval: Value[bool] = Value(
False, "Require admins/mods to manually accept new users"
)
def __init__(self, _data: dict[str, Any] | None = None, **kwargs: Any) -> None:
dict.__init__(self)
if _data is not None:
for key, value in _data.items():
self.set(key, value)
for key, value in kwargs.items():
self.set(key, value)
def __getitem__(self, raw_key: str) -> str:
key = raw_key.replace("_", "-")
if key not in self:
self.__delitem__(raw_key)
return dict.__getitem__(self, key)
def __setitem__(self, key: str, value: str, parse_value: bool = True) -> None:
if parse_value:
try:
self.set(key, value)
return
except AttributeError:
raise KeyError(key) from None
dict.__setitem__(self, key.replace("_", "-"), value)
def __delitem__(self, key: str) -> None:
delattr(self, key.replace("-", "_"))
@classmethod
def from_rows(cls: type[Self], rows: Iterator[Row] | Sequence[Row]) -> Self:
cfg = cls()
for row in rows:
cfg.set_from_row(row)
return cfg
def get(self, key: str) -> Any: # type: ignore[override]
return getattr(self, key.replace("-", "_"))
def set(self, key: str, value: Any) -> None:
setattr(self, key, value)
def set_from_row(self, row: Row | None) -> None:
if row is None:
return
self[row["key"]] = row["value"]
def update(self, data: dict[str, Any]) -> None: # type: ignore[override]
for key, value in data.items():
self.set(key, value)