Skip to main content

A simple settings manager for PySide6

Project description

pyside-settings-manager

Code Coverage GitHub Actions CI Status PyPI

Recursively save and restore states of PySide6 widgets using a handler-based system, providing a 'touched' state indicator and the ability to check for unsaved changes.

Features

  • Recursive Traversal: Automatically finds and manages state for supported widgets within a QMainWindow or other container widgets like QTabWidget and QGroupBox.
  • Handler-Based: Provides a flexible system where the logic for saving, loading, comparing, and monitoring specific widget types is encapsulated in dedicated handler classes. Default handlers are provided for common PySide6 widgets.
  • Extensible: Easily register custom handlers for your own widget types or to override default behavior.
  • Touched State: Tracks whether any managed widget or custom data has been changed since the last save or load call.
  • Unsaved Changes Check: Provides a method to explicitly check if the current state of the managed widgets differs from a saved state (in the default settings or a specific file).
  • Custom Data: Save and load arbitrary pickleable Python objects alongside widget states.
  • Skipping: Explicitly exclude specific widgets from management.
  • File-Based Settings: Save and load states to/from .ini files using QSettings, including options to save/load to specific files.

Supported Widgets (with default handlers)

  • QMainWindow (geometry and state)
  • QCheckBox
  • QLineEdit
  • QPushButton (if checkable)
  • QComboBox (index and text if editable)
  • QSpinBox
  • QDoubleSpinBox
  • QRadioButton
  • QTextEdit
  • QTabWidget (current index if managed)
  • QSlider

(Note: Container widgets like QWidget, QGroupBox and QTabWidget are traversed to find managed children, but don't have their own state saved by default unless a specific handler is registered for them).

Installation

Via PyPI:

pip install pyside-settings-manager

Via GitHub, e.g. using uv:

uv add "git+https://github.com/danicc097/pyside-settings-manager.git@vX.Y.Z"

Usage

  1. Set the SETTINGS_PROPERTY property: Add the property entry to widgets that you want to be managed. The property value should be a unique string identifier for that widget's state within the settings file scope. Widgets without this property will be ignored by default.

  2. Create a QSettings instance: Decide where you want your settings to be stored (e.g., application-specific user settings, a project file).

  3. Create the SettingsManager: Instantiate the manager with your QSettings object.

  4. Load State: Call manager.load() usually after your UI is fully set up. This will restore the saved states of your managed widgets from the default QSettings.

  5. Save State: Call manager.save() when you want to persist the current state of your managed widgets to the default QSettings (e.g., on application close, via a save button/shortcut).

  6. Monitor Touched State: Connect to the touched_changed signal or check the is_touched property to update UI elements (like enabling a "Save" button or adding an asterisk to the window title).

    def on_touched_changed(touched):
        save_button.setEnabled(touched)
        window.setWindowTitle(f"My App {'*' if touched else ''}")
    
    manager.touched_changed.connect(on_touched_changed)
    
  7. Check for Unsaved Changes: Use manager.has_unsaved_changes() to determine if the current UI state differs from the last saved state, especially before closing or performing actions that would discard unsaved changes.

Check out the examples folder for self-contained demo apps:

uv run .\examples\basic.py

Custom Handlers

You can register custom handlers for specific widget types or your own custom widgets by creating a class that implements the pyside_settings_manager.settings::SettingsHandler protocol:

from typing import List, Any
from PySide6.QtCore import QSettings, SignalInstance, QWidget
from PySide6.QtWidgets import QLineEdit
from pyside_settings_manager.settings import SettingsHandler, SETTINGS_PROPERTY

class CustomUppercaseLineEditHandler(SettingsHandler):
    def save(self, widget: QLineEdit, settings: QSettings) -> None:
        settings.setValue(widget.property(SETTINGS_PROPERTY), widget.text().upper())

    def load(self, widget: QLineEdit, settings: QSettings) -> None:
        value = settings.value(widget.property(SETTINGS_PROPERTY), type=str)
        if value is not None:
            widget.setText(str(value))

    def compare(self, widget: QLineEdit, settings: QSettings) -> bool:
         """Returns True if current state != saved state"""
         key = widget.property(SETTINGS_PROPERTY)
         current_value: str = widget.text()
         # Load the value saved by this handler's save method (uppercase)
         saved_value = settings.value(key, type=str)
         if saved_value is None:
             return bool(current_value)

         return current_value.upper() != str(saved_value)


    def get_signals_to_monitor(self, widget: QLineEdit) -> List[SignalInstance]:
        # Return a list of signals that indicate a change requiring the 'touched' state
        return [widget.textChanged]

manager.register_handler(QLineEdit, CustomUppercaseLineEditHandler())
# Now, any QLineEdit with the ``SETTINGS_PROPERTY`` property will use your custom handler
# instead of the default one.

Custom Data

You can save and load arbitrary pickleable data using save_custom_data and load_custom_data:

manager.save_custom_data("user_preferences", {"theme": "dark", "font_size": 12})

prefs = manager.load_custom_data("user_preferences", dict) # {"theme": "dark", "font_size": 12}

Custom data is stored under a specific group based on CUSTOM_DATA_GROUP (e.g., [customData]). When using save_to_file and load_from_file, this custom data is also transferred. Note that load_from_file will overwrite any existing custom data in the manager's default QSettings with the custom data from the loaded file.

Skipping Widgets

To prevent a specific widget instance from being saved, loaded, compared, or monitored for the touched state, use skip_widget():

manager.skip_widget(window.my_checkbox)

# Changes to window.my_checkbox will NOT mark the state as touched,
# and its state won't be saved, loaded, or compared.

# To re-enable management:
manager.unskip_widget(window.my_checkbox)

Checking for Unsaved Changes

The has_unsaved_changes() method allows you to check if the current UI state for all managed (not skipped, has property, has handler) widgets differs from a saved state.

# Check against the default settings file used by the manager
manager.has_unsaved_changes()
# Check against a specific settings file
manager.has_unsaved_changes(source="backup_settings.ini")
# Check against a specific QSettings object
alt_settings = QSettings("temp.ini", QSettings.Format.IniFormat)
# ...
manager.has_unsaved_changes(source=alt_settings)

This method traverses the managed widgets and calls their respective handlers' compare methods. It returns True as soon as the first difference is detected. It does not modify the UI or the manager's is_touched state. Changes in custom data saved via save_custom_data are not considered by has_unsaved_changes; these are immediately synced.

Development

See the src/tests directory for detailed examples covering various widgets and handler behaviors.

To run tests using uv:

uv run pytest

Contribution

Contributions are welcome! Please open an issue or submit a pull request.

License

This project is licensed under the Apache 2.0 License - see the LICENSE file for details.

However, it relies on PySide6, which is licensed under the LGPL v3. While the code specific to pyside-settings-manager is provided under the Apache 2.0 license, any usage of this library inherently involves PySide6. Users must ensure they comply with the terms of the LGPL v3 license regarding their use and distribution.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyside_settings_manager-0.0.21.tar.gz (67.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyside_settings_manager-0.0.21-py3-none-any.whl (17.8 kB view details)

Uploaded Python 3

File details

Details for the file pyside_settings_manager-0.0.21.tar.gz.

File metadata

  • Download URL: pyside_settings_manager-0.0.21.tar.gz
  • Upload date:
  • Size: 67.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for pyside_settings_manager-0.0.21.tar.gz
Algorithm Hash digest
SHA256 596e206caf7d566cbb5842d251f26aa093065bbcc1287185c2c15ea6f07d00f9
MD5 56674eab8aa95663340e1a6638556f79
BLAKE2b-256 83398443daf862a561de27a70ae9298cf260acdf83d1fd59b8e8b98b567bc500

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyside_settings_manager-0.0.21.tar.gz:

Publisher: publish-pypi.yaml on danicc097/pyside-settings-manager

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pyside_settings_manager-0.0.21-py3-none-any.whl.

File metadata

File hashes

Hashes for pyside_settings_manager-0.0.21-py3-none-any.whl
Algorithm Hash digest
SHA256 1178f958b8a8dc8c5c06fa9767b9a2fbb75575175c695e3e14287ad5170e12d4
MD5 f8e8200b4f1b9a92a6cfd33c9fa198bd
BLAKE2b-256 9848b1aa562f8a46002a7b387b2c6b7bf345e9f5b92daa18e9543f97f79fe571

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyside_settings_manager-0.0.21-py3-none-any.whl:

Publisher: publish-pypi.yaml on danicc097/pyside-settings-manager

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page