pyside6-qml-architecture

PySide6 QML MVC Architecture

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "pyside6-qml-architecture" with this command: npx skills add ds-codi/project-memory-mcp/ds-codi-project-memory-mcp-pyside6-qml-architecture

PySide6 QML MVC Architecture

Desktop GUI applications in this workspace use Python + PySide6 with QML files for the view layer, following a strict Model-View-Controller (MVC) architecture. This skill documents the canonical project structure, bootstrap pattern, and layer responsibilities derived from the ds_pas/ application.

Architecture Overview

┌─────────────────────────────────────────┐ │ View Layer (QML files) │ │ Declarative UI, data binding, signals │ └──────────────────┬──────────────────────┘ │ Properties, Signals, Slots ┌──────────────────▼──────────────────────┐ │ Python-QML Bridge │ │ QObject subclasses exposed to QML │ └──────────────────┬──────────────────────┘ │ ┌──────────────────▼──────────────────────┐ │ Controller Layer │ │ Coordinate models & services │ └──────────────────┬──────────────────────┘ │ ┌──────────────────▼──────────────────────┐ │ Model Layer │ │ Data structures, validation, signals │ └──────────────────┬──────────────────────┘ │ ┌──────────────────▼──────────────────────┐ │ Services Layer │ │ Database, files, network, broker │ └─────────────────────────────────────────┘

Project Structure

my_app/ ├── app.py # Bootstrap & DI container (singleton) ├── init.py # Package init ├── main.py # Entry point: python -m my_app ├── controllers/ │ ├── init.py │ ├── base.py # BaseController with view registry │ └── *_controller.py # Domain controllers (job, settings, etc.) ├── models/ │ ├── init.py │ ├── base.py # BaseModel with property_changed signal │ ├── state.py # ApplicationState singleton │ └── *.py # Domain models (job, piece, customer, etc.) ├── views/ │ ├── init.py │ ├── bridge.py # QObject bridge classes exposed to QML │ ├── main_window.py # Main window setup (QQmlApplicationEngine) │ └── components/ # Reusable Python view helpers ├── services/ │ ├── init.py │ ├── database/ # Repository pattern for data access │ └── *.py # External interaction services ├── resources/ │ ├── qml/ │ │ ├── main.qml # Root QML component │ │ ├── components/ # Reusable QML components │ │ ├── pages/ # Page-level QML views │ │ └── styles/ # Theme and style definitions │ ├── icons/ # SVG/PNG icons │ └── qml.qrc # Qt resource file (optional) ├── utils/ │ ├── init.py │ ├── signals.py # Central SignalRegistry │ ├── types.py # Type aliases, protocols, ServiceLocator │ └── paths.py # Path resolution helpers └── tests/ └── *.py

Layer Responsibilities

Component Responsibility MUST NOT

Model Data structures, validation, serialization, property_changed signals Touch UI, call services, reference QML

View (QML) Declarative UI layout, data binding to bridge properties, user input capture Contain business logic, call services directly

Bridge QObject subclasses that expose model data and controller actions to QML Contain business logic, directly manipulate QML

Controller Coordinate models & services, handle actions, emit signals Manipulate UI directly, import QML types

Service Database queries, file I/O, network calls, IPC Reference models, views, or controllers

Application Bootstrap (DI Container)

The application class is a singleton that wires all layers together:

"""app.py — Bootstrap & DI container.""" import sys import logging from typing import Any from pathlib import Path

from PySide6.QtWidgets import QApplication from PySide6.QtQml import QQmlApplicationEngine from PySide6.QtCore import QObject, QUrl

from my_app.utils.signals import SignalRegistry, get_signal_registry from my_app.utils.types import ServiceLocator

logger = logging.getLogger(name)

class MyApplication: """Singleton application with DI container."""

_instance: "MyApplication | None" = None

def __new__(cls, dev_mode: bool = False) -> "MyApplication":
    if cls._instance is None:
        cls._instance = super().__new__(cls)
        cls._instance._initialized = False
    return cls._instance

def __init__(self, dev_mode: bool = False) -> None:
    if self._initialized:
        return
    self._initialized = True
    self._dev_mode = dev_mode
    self._qt_app: QApplication | None = None
    self._engine: QQmlApplicationEngine | None = None
    self._services: dict[str, Any] = {}
    self._controllers: dict[str, Any] = {}
    self._signals = get_signal_registry()
    self._service_locator = ServiceLocator()

def _register_services(self) -> None:
    """Create and register all service instances."""
    # self._services["db"] = DatabaseService(...)
    pass

def _register_controllers(self) -> None:
    """Create controllers with injected dependencies."""
    # self._controllers["job"] = JobController(
    #     signals=self._signals,
    #     repository=self._services["db"],
    # )
    pass

def _register_qml_types(self) -> None:
    """Register Python bridge objects as QML context properties."""
    ctx = self._engine.rootContext()
    # ctx.setContextProperty("jobBridge", self._bridges["job"])
    pass

def run(self) -> int:
    self._qt_app = QApplication(sys.argv)
    self._register_services()
    self._register_controllers()

    self._engine = QQmlApplicationEngine()
    self._register_qml_types()

    qml_path = Path(__file__).parent / "resources" / "qml" / "main.qml"
    self._engine.load(QUrl.fromLocalFile(str(qml_path)))

    if not self._engine.rootObjects():
        return -1

    return self._qt_app.exec()

Entry Point

"""main.py""" import sys from my_app.app import MyApplication

def main(): app = MyApplication(dev_mode="--dev" in sys.argv) sys.exit(app.run())

if name == "main": main()

ServiceLocator Pattern

"""utils/types.py — Type aliases, protocols, and DI container.""" from typing import Any, Protocol, TypeVar, runtime_checkable

T = TypeVar("T")

@runtime_checkable class IController(Protocol): def initialize(self) -> None: ... def cleanup(self) -> None: ...

@runtime_checkable class IService(Protocol): def initialize(self) -> None: ... def shutdown(self) -> None: ...

class ServiceLocator: """Lightweight DI container for service instances.""" _instance: "ServiceLocator | None" = None

def __new__(cls) -> "ServiceLocator":
    if cls._instance is None:
        cls._instance = super().__new__(cls)
        cls._instance._services = {}
    return cls._instance

def register(self, name: str, service: Any) -> None:
    self._services[name] = service

def get(self, name: str) -> Any:
    if name not in self._services:
        raise KeyError(f"Service '{name}' not registered")
    return self._services[name]

def has(self, name: str) -> bool:
    return name in self._services

Signal Flow

User Action (QML) ↓ QML emits signal / calls slot on Bridge ↓ Bridge delegates to Controller ↓ Controller calls Service ↓ Service returns result ↓ Controller updates Model ↓ Model emits property_changed ↓ Bridge property notifies QML (via NOTIFY) ↓ QML binding automatically updates UI

Key Design Rules

  • QML files are declarative only — no JavaScript business logic, no direct service calls

  • Python bridge objects are the sole interface between QML and the Python backend

  • Controllers never import QML types — they operate through bridge signals/properties

  • Models are pure data — no UI imports, no service calls

  • Services are stateless workers — no model references, no UI knowledge

  • All cross-layer communication flows through the SignalRegistry or Qt property bindings

  • Singletons (Application , SignalRegistry , ServiceLocator , ApplicationState ) use the new pattern for thread-safe reuse

File Size Guidelines

  • Keep files focused on a single responsibility

  • Split files exceeding ~300-400 lines into submodules

  • Extract reusable QML components into resources/qml/components/

  • Group related controllers/services by domain (e.g., controllers/job_controller.py )

References

  • PySide6 QML Integration

  • Qt QML Documentation

  • PySide6 Documentation

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

pyside6-mvc

No summary provided by upstream source.

Repository SourceNeeds Review
General

pyside6-qml-views

No summary provided by upstream source.

Repository SourceNeeds Review
General

bugfix

No summary provided by upstream source.

Repository SourceNeeds Review
General

pyside6-qml-bridge

No summary provided by upstream source.

Repository SourceNeeds Review