Settings
ZwyLib provides a small framework for declaring persistent, per-plugin settings and binding them directly to exteraGram’s settings-screen components (Input, Selector, Switch), so you don’t have to wire storage and UI together by hand.
Getting Started
Declare each setting once as a SettingEntry, then bind it to a settings-screen component when building your plugin’s preferences:
# ... metadata and zwylib import ...
from zwylib import SettingEntry
class MySettings:
greeting = SettingEntry("greeting", "Hello!", icon="msg_edit")
notify = SettingEntry("notify", True, icon="msg_notifications")
class MyPlugin(BasePlugin):
def create_settings(self):
return [
MySettings.greeting.as_input(text="Greeting text"),
MySettings.notify.as_switch(text="Show notifications"),
]Reading and writing a setting from anywhere in your plugin is then just:
current = MySettings.greeting.get()
MySettings.greeting.set("Hi there!")zwylib.SettingEntry
SettingEntry(key: str, default: Any, icon: Optional[str] = None)Describes a single named setting belonging to the calling plugin. The owning plugin ID is inferred automatically from the call stack at construction time, so SettingEntry instances should be created at your plugin’s module level (not inside a function called from elsewhere).
Constructor Arguments
key(str): Unique key identifying this setting within your plugin’s settings.default(Any): Value returned bygetwhen the setting hasn’t been set yet.icon(Optional[str], defaultNone): Drawable resource name used when this entry is bound to a settings-screen component viaas_input,as_selector, oras_switch.
Methods
get
SettingEntry.get() -> AnyReturns the current value of this setting, or default if it hasn’t been set.
safe_get
SettingEntry.safe_get(
factory: (Any) -> _T,
bulletin_text: str = None,
log_text: str = None
) -> _TReads the setting and passes it through factory (e.g. int, float, a custom parser), falling back to factory(default) and logging the error if either the read or the conversion fails.
Parameters
factory((Any) ->_T): Function used to convert the raw stored value into the desired type.bulletin_text(str, optional): If provided, shown as an error bulletin when the conversion fails.log_text(str, optional): Custom message logged when the conversion fails. Defaults to a generic message naming the setting’s key.
Example
timeout = S.autoupdate_timeout.safe_get(int, bulletin_text="Invalid timeout, using default.")set
SettingEntry.set(value: Any) -> NonePersists a new value for this setting and reloads the plugin’s settings screen.
Parameters
value(Any): The new value to store.
Example
S.enable_autoupdates.set(False)as_input
SettingEntry.as_input(**kwargs) -> InputBuilds a ui.settings.Input component bound to this entry — key, default, and icon (if set) are filled in automatically.
Parameters
**kwargs: Any otherInputconstructor arguments (text,subtext,on_change, etc.).
Example
S.autoupdate_timeout.as_input(text="Update check interval (seconds)")as_selector
SettingEntry.as_selector(**kwargs) -> SelectorBuilds a ui.settings.Selector component bound to this entry — key, default, and icon (if set) are filled in automatically.
Parameters
**kwargs: Any otherSelectorconstructor arguments (text,items,on_change, etc.).
Example
S.theme.as_selector(text="Theme", items=["Light", "Dark", "System"])as_switch
SettingEntry.as_switch(**kwargs) -> SwitchBuilds a ui.settings.Switch component bound to this entry — key, default, and icon (if set) are filled in automatically.
Parameters
**kwargs: Any otherSwitchconstructor arguments (text,subtext,on_change, etc.).
Example
S.enable_autoupdates.as_switch(text="Show notifications")zwylib.SettingsManager
class SettingsManagerLower-level static access to plugin settings storage, used internally by SettingEntry and available directly if you’d rather not declare entries (e.g. for dynamic keys).
Methods
get
SettingsManager.get(
plugin_id: str,
item: Union[str, SettingEntry],
default: Optional[_T] = None
) -> Optional[_T]Reads a setting’s value for the given plugin.
Parameters
plugin_id(str): ID of the plugin the setting belongs to.item(Union[str, SettingEntry]): Either a raw key string, or aSettingEntry(whose owndefaultis used unless overridden).default(Optional[_T], defaultNone): Fallback value whenitemis a plain string key. Ignored ifitemis aSettingEntry.
Example
from zwylib import SettingsManager
SettingsManager.get("MyPluginID", "greeting", "Hello!")set
SettingsManager.set(
plugin_id: str,
item: Union[str, SettingEntry],
value: Any,
reload_settings=True
) -> NoneWrites a setting’s value for the given plugin.
Parameters
plugin_id(str): ID of the plugin the setting belongs to.item(Union[str, SettingEntry]): Either a raw key string, or aSettingEntry.value(Any): The new value to store.reload_settings(bool, defaultTrue): Whether to reload the plugin’s settings screen after writing.
Example
SettingsManager.set("MyPluginID", "greeting", "Hi!")reload
SettingsManager.reload(plugin_id: str = None) -> NoneReloads a plugin’s settings from disk, refreshing its settings screen. Called automatically by set unless reload_settings=False.
Parameters
plugin_id(str, optional): ID of the plugin to reload. Inferred automatically from the call stack if omitted.
remove_setting
SettingsManager.remove_setting(key: str, plugin_id: str = None) -> NoneDeletes a single setting key from storage entirely (as opposed to resetting it to a default).
Parameters
key(str): Key of the setting to remove.plugin_id(str, optional): ID of the plugin the setting belongs to. Inferred automatically from the call stack if omitted.
Example
SettingsManager.remove_setting("legacy_flag")Expandable Switches
For a group of related boolean settings — feature toggles, debloat options, and the like — ExpandableSwitchManager builds a single collapsible “select all” switch that expands into individual checkboxes for each option, without you having to manage the collapsed/expanded state yourself.
# ... metadata and zwylib import ...
from zwylib import SettingEntry, ExpandableOption, ExpandableSwitchManager
def on_feature_toggled(feature_id: int, enabled: bool):
print(f"Feature {_features[feature_id]} {'enabled' if enabled else 'disabled'}!")
_features = {
1: "Camera integration",
2: "Cloud sync",
3: "Background updates",
}
_options = [
ExpandableOption(
text=label,
settings_entry=SettingEntry(f"feature_{feature_id}_enabled", True),
on_toggle=lambda enabled, feature_id=feature_id: on_feature_toggled(feature_id, enabled),
)
for feature_id, label in _features.items()
]
class MyPlugin(BasePlugin):
def create_settings(self):
return ExpandableSwitchManager.generate("Extra features", _options)Each ExpandableOption’s on_toggle only ever receives the new bool state — the feature_id=feature_id default-argument trick above is what lets a single shared callback still know which switch triggered it.
zwylib.ExpandableOption
@dataclass
class ExpandableOption:
text: str
settings_entry: SettingEntry
on_toggle: Optional[(bool) -> None] = NoneDescribes one row inside an expandable switch group.
Fields
text(str): Label shown next to the checkbox.settings_entry(SettingEntry): The setting this option reads from and writes to.on_toggle(Optional[(bool) -> None], defaultNone): Called with the new state whenever this option is toggled, either directly or via the group’s “select all” switch.
Methods
toggle
ExpandableOption.toggle() -> NoneFlips this option’s current value and calls on_toggle, if set. This is what the checkbox’s own click handler calls internally — you generally won’t need to call it yourself.
zwylib.ExpandableSwitchManager
class ExpandableSwitchManagerMethods
generate
ExpandableSwitchManager.generate(
main_switch_text: str,
connected_options: List[ExpandableOption]
) -> List[Custom]Builds (or retrieves, if already built) the collapsible switch group for main_switch_text and returns its current list of ui.settings.Custom entries — just splice the result straight into whatever create_settings returns. The group’s collapsed/expanded state and its “select all” checked state are tracked automatically, keyed by main_switch_text, for as long as the plugin stays loaded.
Parameters
main_switch_text(str): Label for the group’s top-level switch, and the key used to look up its state on subsequent calls — keep it stable acrosscreate_settingscalls.connected_options(List[ExpandableOption]): The individual toggles belonging to this group.
Example
class MyPlugin(BasePlugin):
def create_settings(self):
return ExpandableSwitchManager.generate("Extra features", _options)