Application settings manager with instant reads from a local cache and automatic background persistence to macOS UserDefaults. Provides attribute-style access via dot notation.
Constructor¶
nib.Settings(defaults: dict[str, Any], on_load: Callable[[], None] | None = None)
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
defaults |
dict[str, Any] |
-- | Dictionary mapping setting names to their default values. These defaults are used when no saved value exists in UserDefaults |
on_load |
Callable[[], None] \| None |
None |
Optional callback invoked when settings finish loading persisted values from UserDefaults |
Properties¶
| Property | Type | Description |
|---|---|---|
on_load |
Callable[[], None] \| None |
Get or set the callback invoked when settings finish loading. If settings have already loaded, assigning a new callback invokes it immediately |
<setting_name> |
Any |
Any key defined in defaults is accessible as an attribute. Reading returns the cached value instantly; writing updates the cache and persists in the background |
Methods¶
get(name, default)¶
Get a setting value with an optional fallback.
settings.get(name: str, default: Any = None) -> Any
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
-- | The setting name |
default |
Any |
None |
Value returned if the setting is not found |
set(name, value)¶
Set a setting value. Alternative to attribute assignment.
settings.set(name: str, value: Any) -> None
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
-- | The setting name (must be defined in defaults) |
value |
Any |
-- | The new value |
wait_for_load(timeout)¶
Block until initial settings have been loaded from UserDefaults. Usually not needed since defaults are available immediately.
settings.wait_for_load(timeout: float = 5.0) -> bool
| Parameter | Type | Default | Description |
|---|---|---|---|
timeout |
float |
5.0 |
Maximum wait time in seconds |
Returns: True if loading completed, False if timed out.
load(blocking, timeout)¶
Trigger loading of persisted values from UserDefaults. Called automatically on first attribute access, but can be called explicitly for more control.
settings.load(blocking: bool = False, timeout: float = 2.0) -> None
| Parameter | Type | Default | Description |
|---|---|---|---|
blocking |
bool |
False |
If True, waits for the load to complete before returning |
timeout |
float |
2.0 |
Maximum wait time when blocking=True |
to_dict()¶
Get all current settings as a plain dictionary.
settings.to_dict() -> dict[str, Any]
reset(name)¶
Reset settings to their default values.
settings.reset(name: str | None = None) -> None
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str \| None |
None |
Specific setting to reset. If None, resets all settings to defaults |
How It Works¶
- Cache layer: All reads go to an in-memory dictionary, so
settings.dark_modenever blocks. - Background persistence: Writes update the cache immediately, then fire-and-forget a message to the Swift runtime to persist in UserDefaults.
- Initial load: When registered with
app.register_settings(), saved values are loaded from UserDefaults in a background thread and merged into the cache. Theon_loadcallback fires when this completes. - Attribute validation: Only keys defined in
defaultscan be read or written. Accessing an undefined key raisesAttributeError.
Examples¶
Basic settings with persistence¶
import nib
def main(app: nib.App):
app.title = "Preferences Demo"
app.icon = nib.SFSymbol("gear")
app.width = 350
app.height = 250
settings = nib.Settings({
"dark_mode": False,
"font_size": 14,
"username": "guest",
})
app.register_settings(settings)
status = nib.Text(f"User: {settings.username}, Font: {settings.font_size}pt")
def toggle_dark():
settings.dark_mode = not settings.dark_mode
status.content = f"Dark mode: {settings.dark_mode}"
app.build(
nib.VStack(
controls=[
status,
nib.Button("Toggle Dark Mode", action=toggle_dark),
],
spacing=12,
padding=20,
)
)
nib.run(main)
Using on_load to update UI after persisted values load¶
import nib
def main(app: nib.App):
app.title = "Settings"
app.icon = nib.SFSymbol("slider.horizontal.3")
app.width = 350
app.height = 200
label = nib.Text("Loading...", font=nib.Font.HEADLINE)
settings = nib.Settings(
{"volume": 50, "notifications": True},
on_load=lambda: setattr(label, "content", f"Volume: {settings.volume}%"),
)
app.register_settings(settings)
def update_volume(value):
settings.volume = int(value)
label.content = f"Volume: {settings.volume}%"
app.build(
nib.VStack(
controls=[
label,
nib.Slider(
"Volume",
value=settings.volume,
min_value=0,
max_value=100,
on_change=update_volume,
),
],
spacing=12,
padding=20,
)
)
nib.run(main)
Resetting settings¶
import nib
def main(app: nib.App):
app.title = "Reset Demo"
app.icon = nib.SFSymbol("arrow.counterclockwise")
app.width = 300
app.height = 150
settings = nib.Settings({"theme": "light", "volume": 50})
app.register_settings(settings)
info = nib.Text(f"Theme: {settings.theme}, Volume: {settings.volume}")
def reset_all():
settings.reset()
info.content = f"Theme: {settings.theme}, Volume: {settings.volume}"
def reset_theme():
settings.reset("theme")
info.content = f"Theme: {settings.theme}, Volume: {settings.volume}"
app.build(
nib.VStack(
controls=[
info,
nib.Button("Reset Theme", action=reset_theme),
nib.Button("Reset All", action=reset_all),
],
spacing=8,
padding=16,
)
)
nib.run(main)
Related¶
- App -- Register settings via
app.register_settings() - UserDefaults -- Low-level persistent storage used under the hood
- SettingsPage -- UI for a preferences window