Automatic UI generation for PySide6 settings from Pydantic models
Project description
PySide6 Settings
A powerful and elegant settings management library for PySide6 applications that automatically generates UI forms from Pydantic models.
Features
- 🎨 Automatic UI Generation - Generate beautiful Qt forms directly from Pydantic models
- 🔄 Two-way Data Binding - Automatic synchronization between UI widgets and data models
- 💾 Multiple File Formats - Support for JSON, YAML, TOML, and more
- 🎯 Type-Safe - Full type checking with Pydantic
- 🏗️ Organized Groups - Group related settings with collapsible group boxes
- 🔌 Extensible - Easy to add custom widgets and loaders
- ✅ Validation - Automatic validation with Pydantic constraints
- 📝 Rich Widgets - Support for various input types including file browsers, tag inputs, password fields, and more
Installation
pip install pyside6-settings
Or install from source:
git clone https://github.com/yourusername/pyside6-settings.git
cd pyside6-settings
pip install -e .
Quick Start
Basic Example
from pathlib import Path
from pyside6_settings import BaseSettings, Field
from PySide6.QtWidgets import QApplication, QMainWindow
class AppSettings(BaseSettings):
# Basic fields with automatic widget generation
username: str = Field(default="user", description="Your username")
age: int = Field(default=25, ge=0, le=150, description="Your age")
enabled: bool = Field(default=True, description="Enable feature")
# Field with choices (creates QComboBox)
theme: str = Field(
default="dark",
title="Theme",
choices=["light", "dark", "auto"]
)
# Create Qt application
app = QApplication([])
# Load settings from file (creates if doesn't exist)
settings = AppSettings.load("config.json")
# Create main window with settings form
window = QMainWindow()
window.setCentralWidget(settings.create_form())
window.setWindowTitle("Application Settings")
window.show()
# Changes are automatically saved to config.json
settings.username = "new_user" # Auto-saved!
app.exec()
Grouped Settings
Organize related settings into collapsible groups:
from pydantic import Field
from pyside6_settings import BaseSettings, WidgetMetadata
class MySettings(BaseSettings):
# Account Group
username: str = Field(
default="",
title="Username",
group="Account",
description="Your account username"
)
password: str = Field(
default="",
title="Password",
widget="password",
group="Account"
)
# Appearance Group
font_size: int = Field(
default=12,
ge=8,
le=32,
title="Font Size",
group="Appearance"
)
theme: str = Field(
default="dark",
title="Color Theme",
choices=["light", "dark", "high-contrast"],
group="Appearance"
)
settings = MySettings.load("config.json")
form = settings.create_form() # Creates form with "Account" and "Appearance" groups
Advanced Widget Types
from pathlib import Path
from typing import List
from pydantic import Field
from pyside6_settings import BaseSettings, WidgetMetadata
class AdvancedSettings(BaseSettings):
# File/Directory Browser
project_path: Path = Field(
default=Path("."),
title="Project Directory",
s_mode="directory"
)
config_file: Path = Field(
default=Path("config.ini"),
title="Config File",
fs_mode="file"
)
# Tag/List Input
tags: List[str] = Field(
default_factory=list,
title="Tags",
widget="tags"
)
# Multi-line Text
description: str = Field(
default="",
title="Description",
widget="textarea"
)
# Password Field
api_key: str = Field(
default="",
title="API Key",
widget="password"
)
# Numeric with Constraints
timeout: float = Field(
default=30.0,
ge=1.0,
le=300.0,
title="Timeout (seconds)"
)
settings = AdvancedSettings.load("advanced.json")
Widget Types
The library automatically selects the appropriate widget based on field type and metadata:
| Field Type | Widget | Notes |
|---|---|---|
str |
QLineEdit |
Default text input |
int |
QSpinBox |
With min/max from constraints |
float |
QDoubleSpinBox |
With min/max from constraints |
bool |
QCheckBox |
Checkbox |
List[str] |
TagInputWidget |
Custom tag input |
Path |
PathBrowseWidget |
File/directory browser |
str (with choices) |
QComboBox |
Dropdown selection |
Custom Widget Override
Use widget parameter in WidgetMetadata to force a specific widget type:
description: str = Field(
default="",
widget="textarea"
)
Available widget overrides:
"textarea"- Multi-line text input"password"- Password field (masked input)"tags"- Tag input widget"path"- File/directory browser"checkbox"- Checkbox"spinbox"- Numeric spinner"doublespinbox"- Float spinner"hidden"- Field exists but no widget created
Working with Individual Widgets
Get individual field widgets for custom layouts:
settings = MySettings.load("config.json")
# Get widget without label
username_widget = settings.get_widget("username", with_label=False)
# Get widget with label (in QFormLayout)
username_field = settings.get_widget("username", with_label=True)
# Get entire group as QGroupBox
account_group = settings.get_group("Account")
Programmatic Access
Settings behave like normal Python objects:
settings = MySettings.load("config.json")
# Read values
print(settings.username)
print(settings.theme)
# Modify values (automatically saved)
settings.username = "new_user"
settings.theme = "light"
# Access all fields
for field_name in settings.__pydantic_fields__:
value = getattr(settings, field_name)
print(f"{field_name}: {value}")
File Format Support
Supported configuration file formats:
- JSON (
.json) - YAML (
.yaml,.yml) - TOML (
.toml) - INI (
.ini)
The format is automatically detected from the file extension:
# Use different formats
json_settings = MySettings.load("config.json")
yaml_settings = MySettings.load("config.yaml")
toml_settings = MySettings.load("config.toml")
Custom Loaders
Implement custom loaders for other formats:
from pyside6_settings.loaders import BaseConfigLoader
class CustomLoader(BaseConfigLoader):
def load(self) -> dict:
# Your loading logic
pass
def save(self, data: dict) -> None:
# Your saving logic
pass
# Register the loader
from pyside6_settings import DEFAULT_LOADERS
DEFAULT_LOADERS[".custom"] = CustomLoader
Validation
Leverage Pydantic's powerful validation:
from pydantic import Field, field_validator
class ValidatedSettings(BaseSettings):
email: str = Field(default="")
port: int = Field(default=8080, ge=1, le=65535)
@field_validator("email")
@classmethod
def validate_email(cls, v):
if v and "@" not in v:
raise ValueError("Invalid email address")
return v
settings = ValidatedSettings.load("config.json")
settings.email = "invalid" # Raises ValidationError
Excluding Fields
Exclude fields from UI or serialization:
internal_value: str = Field(default="secret", exclude=True)
# Or hide from UI only
hidden_field: str = Field(
default="value",
widget="hidden"
)
Complete Example
from pathlib import Path
from typing import List, Optional
from pydantic import Field
from pyside6_settings import BaseSettings, Field
from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton
class EditorSettings(BaseSettings):
# General Settings
window_title: str = Field(
default="My Editor",
title="Window Title",
group="General"
)
auto_save: bool = Field(
default=True,
title="Auto Save",
description="Automatically save files",
group="General"
)
# Editor Settings
font_family: str = Field(
default="Monospace",
title="Font Family",
choices=["Monospace", "Arial", "Times New Roman"],
group="Editor"
)
font_size: int = Field(
default=12,
ge=8,
le=32,
title="Font Size",
group="Editor"
)
tab_size: int = Field(
default=4,
ge=1,
le=8,
title="Tab Size",
group="Editor"
)
# Paths
workspace: Path = Field(
default=Path.home(),
title="Workspace Directory",
fs_mode="directory",
group="Paths"
)
recent_files: List[str] = Field(
default_factory=list,
title="Recent Files",
widget="tags",
group="Paths"
)
def main():
app = QApplication([])
# Load settings
settings = EditorSettings.load("editor_config.json")
# Create main window
window = QMainWindow()
window.setWindowTitle("Editor Settings")
# Create central widget with settings form
central = QWidget()
layout = QVBoxLayout(central)
# Add settings form
settings_form = settings.create_form()
layout.addWidget(settings_form)
# Add close button
close_btn = QPushButton("Close")
close_btn.clicked.connect(window.close)
layout.addWidget(close_btn)
window.setCentralWidget(central)
window.resize(600, 500)
window.show()
app.exec()
if __name__ == "__main__":
main()
API Reference
BaseSettings
Class Methods
load(config_file: str | Path) -> Self- Load settings from file
Instance Methods
create_form(parent: QWidget | None = None) -> QWidget- Create complete settings formget_widget(field_name: str, with_label: bool = True) -> QWidget- Get widget for specific fieldget_group(group_name: str) -> QGroupBox- Get group box for specific group
Properties
_config_file: Path- Path to configuration file_config_loader: BaseConfigLoader- Loader instance_bridge: _SettingsBridge- Signal bridge for widget synchronization
WidgetMetadata
Configuration for widget behavior and appearance:
title: str- Display label for the fielddescription: str- Tooltip textgroup: str- Group name (default: "General")widget: str- Force specific widget typechoices: List[str]- Options for dropdown (creates QComboBox)fs_mode: str- File system mode: "file" or "directory"
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Testing
Run the test suite:
pytest tests/ -v
With coverage:
pytest tests/ --cov=pyside6_settings --cov-report=html
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Built with Pydantic for data validation
- Uses PySide6 for Qt bindings
- Inspired by various settings management libraries
Support
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyside6_settings-1.0.2.tar.gz.
File metadata
- Download URL: pyside6_settings-1.0.2.tar.gz
- Upload date:
- Size: 17.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6aba539372d004bcf51f1600b11fb57ae0c8d0dbb11c6fda41d858cd47e96568
|
|
| MD5 |
b2ae156ec9742c25c21a1fd7f8aafc17
|
|
| BLAKE2b-256 |
48bdb52dc46f39fede10fcc1034b9327c0701d917dce2e835255a6c6b014072a
|
Provenance
The following attestation bundles were made for pyside6_settings-1.0.2.tar.gz:
Publisher:
release.yaml on AstralMortem/pyside6-settings
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyside6_settings-1.0.2.tar.gz -
Subject digest:
6aba539372d004bcf51f1600b11fb57ae0c8d0dbb11c6fda41d858cd47e96568 - Sigstore transparency entry: 686230708
- Sigstore integration time:
-
Permalink:
AstralMortem/pyside6-settings@e6efbefc11ffc8c6d854e0d615cbec73235eccf4 -
Branch / Tag:
refs/tags/v1.0.2 - Owner: https://github.com/AstralMortem
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@e6efbefc11ffc8c6d854e0d615cbec73235eccf4 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pyside6_settings-1.0.2-py3-none-any.whl.
File metadata
- Download URL: pyside6_settings-1.0.2-py3-none-any.whl
- Upload date:
- Size: 17.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
16ad66bedcacdb63e723e695b6150d4723dc16cf5da5c80941fe89d2cfd93cbf
|
|
| MD5 |
1905433ece96c13af148c734058db3c7
|
|
| BLAKE2b-256 |
a039ae560423d360e1cfe59dab298a8bbd028ce13260e1b9994808827a4edc11
|
Provenance
The following attestation bundles were made for pyside6_settings-1.0.2-py3-none-any.whl:
Publisher:
release.yaml on AstralMortem/pyside6-settings
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyside6_settings-1.0.2-py3-none-any.whl -
Subject digest:
16ad66bedcacdb63e723e695b6150d4723dc16cf5da5c80941fe89d2cfd93cbf - Sigstore transparency entry: 686230713
- Sigstore integration time:
-
Permalink:
AstralMortem/pyside6-settings@e6efbefc11ffc8c6d854e0d615cbec73235eccf4 -
Branch / Tag:
refs/tags/v1.0.2 - Owner: https://github.com/AstralMortem
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@e6efbefc11ffc8c6d854e0d615cbec73235eccf4 -
Trigger Event:
release
-
Statement type: