skillZs
★ LIVE SKILL TAGS ★
>>> LIVE SKILLS INDEX <<<
* OPEN SOURCE *
NO LOGIN, NO TRACKING
※ REAL INSTALL DATA ※
← back to all skills
ds-codi/project-memory-mcp95 installs

pyside6-mvc

Use this skill when building Python desktop applications using PySide6 with strict MVC architecture where all UI is defined by .ui files. Covers architecture patterns, controller/model/view separation, signal handling, and .ui file workflows.

How do I install this agent skill?

npx skills add https://github.com/ds-codi/project-memory-mcp --skill pyside6-mvc
view source ↗

Is this agent skill safe to install?

  • Gen Agent Trust Hubpass

    This skill provides a comprehensive architectural framework and code templates for developing desktop applications with PySide6 using a strict Model-View-Controller (MVC) pattern. It focuses on the use of Qt Designer (.ui) files, signal handling, and clean separation of concerns without introducing any security vulnerabilities or malicious behaviors.

  • Socketpass

    No alerts

  • Snykpass

    Risk: LOW · No issues

  • Runlayerpass

    1 file scanned · No issues

What does this agent skill do?

PySide6 MVC Architecture Instructions

Guidelines for building Python desktop applications using PySide6 with strict MVC architecture where all UI is defined by .ui files.

Architecture Overview

┌─────────────────────────────────────────┐
│           View Layer (.ui files)        │
│  Load from Qt Designer, capture input   │
└──────────────────┬──────────────────────┘
                   │ Signals
┌──────────────────▼──────────────────────┐
│           Controller Layer              │
│  Coordinate models & services           │
└──────────────────┬──────────────────────┘
                   │
┌──────────────────▼──────────────────────┐
│           Model Layer                   │
│  Data structures, validation            │
└──────────────────┬──────────────────────┘
                   │
┌──────────────────▼──────────────────────┐
│           Services Layer                │
│  Database, files, network, broker       │
└─────────────────────────────────────────┘

Project Structure

my_app/
├── app.py                    # Bootstrap & DI container
├── __main__.py               # Entry point
├── controllers/
│   ├── base.py               # BaseController
│   └── *_controller.py       # Domain controllers
├── models/
│   ├── base.py               # BaseModel with signals
│   └── *.py                  # Domain models
├── views/
│   ├── base.py               # BaseView
│   └── *.py                  # View classes
├── services/
│   └── *.py                  # External interactions
├── resources/
│   └── ui/                   # .ui files (Qt Designer)
└── utils/
    └── signals.py            # Central signal registry

Core Principles

ComponentResponsibilityDoes NOT
ModelData, validation, serializationTouch UI, call services
ViewLoad .ui files, capture inputContain business logic
ControllerCoordinate models & servicesManipulate UI directly

Base Model Pattern

from PySide6.QtCore import QObject, Signal
from datetime import datetime
from typing import Any

class BaseModel(QObject):
    property_changed = Signal(str, object)  # name, value
    changed = Signal()
    
    def __init__(self, parent=None):
        super().__init__(parent)
        self._updated_at = datetime.now()
    
    def _set_property(self, name: str, old: Any, new: Any) -> bool:
        if old != new:
            self._updated_at = datetime.now()
            self.property_changed.emit(name, new)
            self.changed.emit()
            return True
        return False
    
    def to_dict(self) -> dict:
        raise NotImplementedError
    
    @classmethod
    def from_dict(cls, data: dict):
        raise NotImplementedError

Base View Pattern

from pathlib import Path
from PySide6.QtWidgets import QWidget, QVBoxLayout
from PySide6.QtCore import QFile, Signal
from PySide6.QtUiTools import QUiLoader

class BaseView(QWidget):
    error_occurred = Signal(str, str)  # title, message
    
    def __init__(self, parent=None):
        super().__init__(parent)
        self._controller = None
        self._init_ui()
        self._connect_signals()
    
    def _load_ui(self, ui_filename: str) -> QWidget:
        ui_path = Path(__file__).parent.parent / "resources" / "ui" / ui_filename
        loader = QUiLoader()
        ui_file = QFile(str(ui_path))
        
        if ui_file.open(QFile.ReadOnly):
            ui = loader.load(ui_file, self)
            ui_file.close()
            
            layout = QVBoxLayout(self)
            layout.setContentsMargins(0, 0, 0, 0)
            layout.addWidget(ui)
            return ui
        raise FileNotFoundError(f"Cannot open: {ui_path}")
    
    def _init_ui(self):
        pass  # Override: load .ui file
    
    def _connect_signals(self):
        pass  # Override: connect handlers
    
    def set_controller(self, controller):
        self._controller = controller
    
    def refresh(self):
        pass  # Override: update from model

Base Controller Pattern

from PySide6.QtCore import QObject

class BaseController(QObject):
    def __init__(self, signals, parent=None):
        super().__init__(parent)
        self._signals = signals
        self._views = []
    
    def register_view(self, view):
        if view not in self._views:
            self._views.append(view)
            view.set_controller(self)
    
    def notify_views(self):
        for view in self._views:
            view.refresh()
    
    def initialize(self):
        pass  # Override: setup logic
    
    def cleanup(self):
        self._views.clear()

Central Signal Registry

from PySide6.QtCore import QObject, Signal

class SignalRegistry(QObject):
    # Domain signals
    job_changed = Signal(str)         # job_id
    job_created = Signal(str)         # job_id
    settings_changed = Signal(str, object)  # key, value
    
    # Connection signals
    broker_connected = Signal(bool)   # connected
    
    # UI signals
    error_occurred = Signal(str, str)  # title, message
    app_closing = Signal()

# Singleton
_registry = None

def get_signal_registry():
    global _registry
    if _registry is None:
        _registry = SignalRegistry()
    return _registry

Application Bootstrap (DI Container)

import sys
from PySide6.QtWidgets import QApplication
from my_app.utils.signals import get_signal_registry

class MyApplication:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
    
    def __init__(self):
        self._qt_app = None
        self._services = {}
        self._controllers = {}
        self._signals = get_signal_registry()
    
    def _register_services(self):
        self._services["db"] = DatabaseService()
    
    def _register_controllers(self):
        self._controllers["job"] = JobController(
            signals=self._signals,
            db=self._services["db"],
        )
        for c in self._controllers.values():
            c.initialize()
    
    def run(self) -> int:
        self._qt_app = QApplication(sys.argv)
        self._register_services()
        self._register_controllers()
        
        window = MainWindow(self._signals, self._controllers)
        window.show()
        return self._qt_app.exec()

Widget Naming Conventions (.ui files)

Widget TypePatternExample
Label*_labeljob_number_label
Button*_btnsave_btn
Line Edit*_inputcustomer_input
List*_listjobs_list
Table*_tablepieces_table
Combo*_combostatus_combo

Signal Flow

User Action (View)
       ↓
    View emits signal
       ↓
    Controller handles action
       ↓
    Service performs operation
       ↓
    SignalRegistry.emit()
       ↓
    Views call refresh()

Best Practices

1. Never Create UI Programmatically

❌ Wrong:

layout = QVBoxLayout()
btn = QPushButton("Click")
layout.addWidget(btn)

✅ Correct:

self._ui = self._load_ui("my_widget.ui")
self._btn = self._ui.findChild(QPushButton, "action_btn")

2. Controllers Never Touch UI

❌ Wrong:

def activate_job(self):
    self._view.label.setText(job.name)  # NO!

✅ Correct:

def activate_job(self):
    self._signals.job_changed.emit(job_id)  # Views subscribe

3. Views Subscribe to Signals

def _connect_signals(self):
    self._signals.job_changed.connect(self._on_job_changed)

def _on_job_changed(self, job_id):
    self.refresh()

4. Provide Fallback UI

def _init_ui(self):
    try:
        self._ui = self._load_ui("widget.ui")
    except FileNotFoundError:
        self._create_fallback_ui()

5. Use Services for External Operations

Controllers delegate to services:

  • Database queries → Repository services
  • File operations → File service
  • Network calls → API service
  • IPC → Broker client

Common Imports

# Qt
from PySide6.QtWidgets import QWidget, QMainWindow
from PySide6.QtCore import Signal, Slot, QFile
from PySide6.QtUiTools import QUiLoader

# Project
from my_app.utils.signals import get_signal_registry
from my_app.controllers.base import BaseController
from my_app.views.base import BaseView
from my_app.models.base import BaseModel

References

Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.

<a href="https://skillzs.dev/skills/ds-codi/project-memory-mcp/pyside6-mvc">View pyside6-mvc on skillZs</a>