summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorafix.space <laurent@afix.space>2026-07-09 19:34:22 +0200
committerafix.space <laurent@afix.space>2026-07-09 19:34:22 +0200
commit5c369a5d98dbab80903dfcb7c52625c78228e789 (patch)
treee17ac19cc282778dd14aa6cdac3b50481e55e9ea
feat: PySide6 dev environment with runnable examplesHEADmain
Flake devShell (PySide6 6.11 on Python 3.13, designer, uic/rcc from qtbase libexec, offline docs) plus hello_widgets and hello_quick examples. QML_IMPORT_PATH exported — nixpkgs PySide6 finds platform plugins on its own but not QML imports; see README.
-rw-r--r--.gitignore3
-rw-r--r--README.md66
-rw-r--r--examples/Main.qml26
-rw-r--r--examples/hello_quick.py22
-rw-r--r--examples/hello_widgets.py43
-rw-r--r--flake.lock27
-rw-r--r--flake.nix45
7 files changed, 232 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..84c7d25
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+__pycache__/
+result
+.direnv/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..826a6c1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,66 @@
+# Qt for Python development environment
+
+Environnement PySide6 (binding Qt officiel) sur Python 3.13, versions
+alignées avec le C++ de `~/projects/qt-dev` (Qt 6.11).
+
+## Entrer dans l'environnement
+
+```bash
+cd ~/projects/qt-python-dev
+nix develop
+```
+
+## Lancer les exemples
+
+```bash
+python examples/hello_widgets.py # app Widgets (signals/slots)
+python examples/hello_quick.py # app QML/Qt Quick
+```
+
+Pas de compilation : Python + QML chargé à l'exécution. Itération
+instantanée — c'est l'avantage principal sur le C++.
+
+## Outils disponibles
+
+| Commande | Rôle |
+|----------|------|
+| `designer` | éditeur de fichiers .ui |
+| `uic -g python fichier.ui -o ui_fichier.py` | compiler un .ui en module Python |
+| `rcc -g python ressources.qrc -o rc.py` | compiler des ressources |
+| `linguist` | traduction (.ts) |
+| `assistant` | doc offline |
+
+Note : le paquet nixpkgs ne fournit pas les wrappers `pyside6-uic`/
+`pyside6-rcc` de PyPI — mais `uic`/`rcc` de qtbase (sur le PATH du
+shell) génèrent du Python nativement avec `-g python`, c'est le même
+résultat.
+
+Workflow Designer typique : dessiner dans `designer`, générer avec
+`uic -g python`, importer la classe générée dans son code.
+
+## Documentation
+
+- Doc Qt offline dans `$QT_DOCS` (enregistrer dans Assistant :
+ Préférences → Documentation → Ajouter).
+- Doc PySide6 spécifique (API Python) : https://doc.qt.io/qtforpython-6/
+- La doc C++ reste utile : l'API Python est un mapping quasi 1:1.
+
+## PySide6 vs PyQt6
+
+Les deux sont dans nixpkgs (`python3Packages.pyside6` / `.pyqt6`),
+mêmes versions. PySide6 choisi ici : binding **officiel** Qt, licence
+LGPL (PyQt6 est GPL/commercial). API quasi identique — migration
+triviale dans les deux sens (`Signal` vs `pyqtSignal`, etc.).
+
+## Notes NixOS
+
+- Les **plugins de plateforme** sont trouvés automatiquement (chemins
+ compilés dans le Qt de nixpkgs), mais **pas les imports QML** : sans
+ le `QML_IMPORT_PATH` exporté par le shellHook, tout `import QtQuick`
+ échoue avec « module is not installed ».
+- **Ne pas faire `pip install pyside6`** dans un venv sur NixOS : le
+ wheel PyPI embarque ses propres libs Qt liées contre une glibc/ABI
+ générique — segfaults et conflits garantis. Toujours passer par
+ `python3Packages.pyside6`.
+- Session Wayland GNOME : fonctionne nativement ; forcer X11 si besoin
+ avec `QT_QPA_PLATFORM=xcb`.
diff --git a/examples/Main.qml b/examples/Main.qml
new file mode 100644
index 0000000..ed85a81
--- /dev/null
+++ b/examples/Main.qml
@@ -0,0 +1,26 @@
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ visible: true
+ width: 320
+ height: 200
+ title: "Hello PySide6 Quick"
+
+ property int count: 0
+
+ Column {
+ anchors.centerIn: parent
+ spacing: 12
+
+ Text {
+ text: "Clics : " + count
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ Button {
+ text: "Cliquer"
+ onClicked: count++
+ }
+ }
+}
diff --git a/examples/hello_quick.py b/examples/hello_quick.py
new file mode 100644
index 0000000..0ffa9f2
--- /dev/null
+++ b/examples/hello_quick.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+"""Minimal PySide6 Qt Quick app: loads Main.qml next to this script."""
+
+import sys
+from pathlib import Path
+
+from PySide6.QtGui import QGuiApplication
+from PySide6.QtQml import QQmlApplicationEngine
+
+
+def main() -> int:
+ app = QGuiApplication(sys.argv)
+
+ engine = QQmlApplicationEngine()
+ engine.objectCreationFailed.connect(lambda: sys.exit(1))
+ engine.load(Path(__file__).parent / "Main.qml")
+
+ return app.exec()
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/examples/hello_widgets.py b/examples/hello_widgets.py
new file mode 100644
index 0000000..926fff6
--- /dev/null
+++ b/examples/hello_widgets.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+"""Minimal PySide6 Widgets app: a counter demonstrating signals/slots."""
+
+import sys
+
+from PySide6.QtWidgets import (
+ QApplication,
+ QLabel,
+ QPushButton,
+ QVBoxLayout,
+ QWidget,
+)
+
+
+def main() -> int:
+ app = QApplication(sys.argv)
+
+ window = QWidget()
+ window.setWindowTitle("Hello PySide6")
+
+ label = QLabel("Clics : 0")
+ button = QPushButton("Cliquer")
+
+ count = 0
+
+ def on_click() -> None:
+ nonlocal count
+ count += 1
+ label.setText(f"Clics : {count}")
+
+ button.clicked.connect(on_click)
+
+ layout = QVBoxLayout(window)
+ layout.addWidget(label)
+ layout.addWidget(button)
+
+ window.resize(240, 100)
+ window.show()
+ return app.exec()
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..458bca5
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,27 @@
+{
+ "nodes": {
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1783389287,
+ "narHash": "sha256-0xIy4dVLqq47rA+mRy0hXDfjhQd4E5PoIns/RmB7nR4=",
+ "owner": "Nixos",
+ "repo": "nixpkgs",
+ "rev": "0ad6f47ea4fe188f4bc8f0380f93ae8523337c6c",
+ "type": "github"
+ },
+ "original": {
+ "owner": "Nixos",
+ "ref": "nixos-26.05",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..40e09a0
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,45 @@
+{
+ description = "Qt for Python development environment (PySide6, nixos-26.05)";
+
+ inputs = {
+ nixpkgs.url = "github:Nixos/nixpkgs/nixos-26.05";
+ };
+
+ outputs =
+ { self, nixpkgs }:
+ let
+ system = "x86_64-linux";
+ pkgs = nixpkgs.legacyPackages.${system};
+
+ python = pkgs.python3.withPackages (
+ p: with p; [
+ pyside6
+ ]
+ );
+ in
+ {
+ formatter.${system} = pkgs.nixfmt;
+
+ devShells.${system}.default = pkgs.mkShell {
+ packages = [
+ python
+ pkgs.qt6.qttools # designer, linguist, assistant
+ pkgs.qt6.qtdoc # offline documentation for Assistant
+ ];
+
+ # Platform plugins are found automatically (compiled-in store
+ # paths), but QML imports are NOT: without this export, any
+ # "import QtQuick" fails with "module is not installed".
+ shellHook = ''
+ # uic/rcc (with Python codegen via -g python) live in libexec,
+ # not bin, so they are not on PATH by default.
+ export PATH=${pkgs.qt6.qtbase}/libexec:$PATH
+ export QML_IMPORT_PATH=${pkgs.qt6.qtdeclarative}/lib/qt-6/qml
+ export QML2_IMPORT_PATH=$QML_IMPORT_PATH
+ export QT_DOCS=${pkgs.qt6.qtdoc}/share/doc
+ echo "PySide6 $(python -c 'import PySide6; print(PySide6.__version__)') sur Python $(python -V | cut -d' ' -f2)"
+ echo "Exemples dans ./examples — voir README.md"
+ '';
+ };
+ };
+}