(core): Add app-global setting storage.

New `AppConfig` key-value table for arbitrary app config.
New `KeyValueData` setting data mixin.
New `AppSettings` settings group.
Attached `AppSettings` as `client.settings`.
Migrated sponsor settings to `AppSettings`.
This commit is contained in:
2022-03-19 15:54:34 +02:00
parent bccbf38310
commit ace84c9388
9 changed files with 120 additions and 48 deletions

View File

@@ -11,6 +11,9 @@ meta = RowTable(
attach_as='meta',
)
# TODO: Consider converting to RowTable for per-shard config caching
app_config = Table('AppConfig')
user_config = RowTable(
'user_config',

View File

@@ -4,6 +4,9 @@ from data import tables
import core # noqa
# Note: This MUST be imported after core, due to table definition orders
from settings import AppSettings
import modules # noqa
# Load and attach app specific data
@@ -15,6 +18,8 @@ client.appdata = core.data.meta.fetch_or_create(appname)
client.data = tables
client.settings = AppSettings(conf.bot['data_appid'])
# Initialise all modules
client.initialise_modules()

View File

@@ -1,7 +1,6 @@
from cmdClient.checks import is_owner
from .module import module
from .config import settings
@module.cmd(
@@ -18,10 +17,10 @@ async def cmd_sponsors(ctx, flags):
if await is_owner.run(ctx) and any(flags.values()):
if flags['edit']:
# Run edit setting command
await settings.sponsor_message.command(ctx, 0)
await ctx.client.settings.sponsor_message.command(ctx, ctx.client.conf.bot['data_appid'])
elif flags['prompt']:
# Run prompt setting command
await settings.sponsor_prompt.command(ctx, 0)
await ctx.client.settings.sponsor_prompt.command(ctx, ctx.client.conf.bot['data_appid'])
else:
# Display message
await ctx.reply(**settings.sponsor_message.args(ctx))
await ctx.reply(**ctx.client.settings.sponsor_message.args(ctx))

View File

@@ -1,23 +1,16 @@
from cmdClient.checks import is_owner
from settings.base import Setting, ColumnData, ObjectSettings
from settings import AppSettings, Setting, KeyValueData, ListData
from settings.setting_types import Message, String
from meta import client
from utils.lib import DotDict
from .data import sponsor_text
from core.data import app_config
class SponsorSettings(ObjectSettings):
settings = DotDict()
pass
@SponsorSettings.attach_setting
class sponsor_prompt(String, ColumnData, Setting):
@AppSettings.attach_setting
class sponsor_prompt(String, KeyValueData, Setting):
attr_name = 'sponsor_prompt'
_default = "Type {prefix}sponsors to check our wonderful partners!"
_default = None
write_ward = is_owner
@@ -30,11 +23,11 @@ class sponsor_prompt(String, ColumnData, Setting):
_quote = False
_data_column = 'prompt_text'
_table_interface = sponsor_text
_id_column = 'ID'
_upsert = True
_create_row = True
_table_interface = app_config
_id_column = 'appid'
_key_column = 'key'
_value_column = 'value'
_key = 'sponsor_prompt'
@classmethod
def _data_to_value(cls, id, data, **kwargs):
@@ -44,8 +37,8 @@ class sponsor_prompt(String, ColumnData, Setting):
return None
@SponsorSettings.attach_setting
class sponsor_message(Message, ColumnData, Setting):
@AppSettings.attach_setting
class sponsor_message(Message, KeyValueData, Setting):
attr_name = 'sponsor_message'
_default = '{"content": "Coming Soon!"}'
@@ -58,13 +51,10 @@ class sponsor_message(Message, ColumnData, Setting):
"Message to reply with when a user runs the `sponsors` command."
)
_data_column = 'command_response'
_table_interface = sponsor_text
_id_column = 'ID'
_upsert = True
_create_row = True
_table_interface = app_config
_id_column = 'appid'
_key_column = 'key'
_value_column = 'value'
_key = 'sponsor_message'
_cmd_str = "{prefix}sponsors --edit"
settings = SponsorSettings(0)

View File

@@ -5,8 +5,6 @@ from LionContext import LionContext
from meta import client
from .config import settings
module = LionModule("Sponsor")
@@ -18,7 +16,7 @@ sponsored_commands = {'profile', 'stats', 'weekly', 'monthly'}
async def sponsor_reply_wrapper(func, ctx: LionContext, *args, **kwargs):
if ctx.cmd and ctx.cmd.name in sponsored_commands:
sponsor_hint = discord.Embed(
description=settings.sponsor_prompt.value,
description=ctx.client.settings.sponsor_prompt.value,
colour=discord.Colour.dark_theme()
)
if 'embed' not in kwargs:

View File

@@ -0,0 +1,5 @@
import settings
from utils.lib import DotDict
class AppSettings(settings.ObjectSettings):
settings = DotDict()

View File

@@ -1,3 +1,4 @@
import json
import discord
from cmdClient.cmdClient import cmdClient
from cmdClient.lib import SafeCancellation
@@ -459,5 +460,55 @@ class ListData:
cls._cache[id] = data
class KeyValueData:
"""
Mixin for settings implemented in a Key-Value table.
The underlying table should have a Unique constraint on the `(_id_column, _key_column)` pair.
"""
_table_interface: Table = None
_id_column: str = None
_key_column: str = None
_value_column: str = None
_key: str = None
@classmethod
def _reader(cls, id: ..., **kwargs):
params = {
"select_columns": (cls._value_column, ),
cls._id_column: id,
cls._key_column: cls._key
}
row = cls._table_interface.select_one_where(**params)
data = row[cls._value_column] if row else None
if data is not None:
data = json.loads(data)
return data
@classmethod
def _writer(cls, id: ..., data: ..., **kwargs):
params = {
cls._id_column: id,
cls._key_column: cls._key
}
if data is not None:
values = {
cls._value_column: json.dumps(data)
}
cls._table_interface.upsert(
constraint=f"{cls._id_column}, {cls._key_column}",
**params,
**values
)
else:
cls._table_interface.delete_where(**params)
class UserInputError(SafeCancellation):
pass

View File

@@ -1,8 +1,22 @@
-- App Config Data {{{
CREATE TABLE AppConfig(
appid TEXT,
key TEXT,
value TEXT,
PRIMARY KEY(appid, key)
);
-- }}}
-- Sponsor Data {{{
CREATE TABLE sponsor_text(
ID INTEGER PRIMARY KEY DEFAULT 0,
prompt_text TEXT,
command_response TEXT
CREATE TABLE sponsor_guild_whitelist(
guildid INTEGER PRIMARY KEY
);
-- }}}
-- Topgg Data {{{
CREATE TABLE topgg_guild_whitelist(
guildid INTEGER PRIMARY KEY
);
-- }}}

View File

@@ -22,6 +22,13 @@ CREATE TABLE AppData(
last_study_badge_scan TIMESTAMP
);
CREATE TABLE AppConfig(
appid TEXT,
key TEXT,
value TEXT,
PRIMARY KEY(appid, key)
);
CREATE TABLE global_user_blacklist(
userid BIGINT PRIMARY KEY,
ownerid BIGINT NOT NULL,
@@ -37,16 +44,6 @@ CREATE TABLE global_guild_blacklist(
);
-- }}}
-- Sponsor Data {{{
CREATE TABLE sponsor_text(
ID INTEGER PRIMARY KEY DEFAULT 0,
prompt_text TEXT,
command_response TEXT
);
-- }}}
-- User configuration data {{{
CREATE TABLE user_config(
userid BIGINT PRIMARY KEY,
@@ -808,6 +805,16 @@ create TABLE topgg(
boostedTimestamp TIMESTAMPTZ NOT NULL
);
CREATE INDEX topgg_userid_timestamp ON topgg (userid, boostedTimestamp);
CREATE TABLE topgg_guild_whitelist(
guildid INTEGER PRIMARY KEY
);
-- }}}
-- Sponsor Data {{{
CREATE TABLE sponsor_guild_whitelist(
guildid INTEGER PRIMARY KEY
);
-- }}}
-- vim: set fdm=marker: