Utiliser en Python les fichiers créés avec Qt Designer
Avant-propos
subprocess.run()
Description
utils.py
dans un package Python nommé functions
, à la racine du site.utils.py
class Utils:
@staticmethod
def ui2py(action=None):
"""
Cette méthode est chargée de compiler tous les fichiers .ui et .qrc du dossier /design du code appelant.
- Le code appelant manipule des fenêtres ou des boîtes de dialogue créées par Qt Designer.
- Le code appelant est dans un dossier : /pc, /affichage, /tests, ... et d'autres à venir.
:param action: False, True ou None
- Si False, ne compile pas.
- Si True, compile systématiquement.
- si None (par défaut), compile si nécessaire (automatique).
|_ automatique = seuls les fichiers-source qui ont été modifiés.
:return: True si réussite, False si échec.
"""
ui2py()
dans le fichier main.py
, AVANT l'import de Ui_MainWindow
:
from functions.utils import Utils
ut = Utils()
if ut.ui2py():
""" Si la compilation a réussi. """
from fen_mere import Ui_MainWindow
else:
""" Si la compilation a échoué. """
raise SystemExit("La compilation des fichiers de Qt Designer a échoué.")
ui2py()
.
Robot/tests/test_utils.py
.
Go To > Test > Create New test
> Tout laisser par défaut et valider.
from functions.utils import Utils
from PyQt5.QtCore import QSettings
import pytest
import shutil
import random
import os
import time
ut = Utils()
# @pytest.mark.skip # Commenter cette ligne pour exécuter les tests.
class TestUi2py:
@pytest.fixture()
def files(self):
"""
:return (yield): Dictionnaire -> chemins + liste des fichiers-cible.
"""
def clean_targets():
for target in l_targets:
if os.path.isfile(tests_path + target):
os.remove(tests_path + target)
def clean_design():
if os.path.isdir(design_path):
shutil.rmtree(design_path)
tests_path = os.getcwd() + os.sep
pc_path = f"{os.path.dirname(os.getcwd())}{os.sep}pc{os.sep}"
design_path = f"{tests_path}design"
""" Copie du dossier 'pc/design' dans tests/design afin de protéger les sources. """
clean_design() # Suppression d'un éventuel ancien dossier.
shutil.copytree(pc_path+'design', design_path)
l_targets = list()
for file_name in os.listdir(design_path):
if file_name.lower().endswith('.ui'):
l_targets.append(f"{file_name[:-2]}py")
if file_name.lower().endswith('.qrc'):
l_targets.append(f"{file_name[:-4]}_rc.py")
if len(l_targets) == 0:
print("\nIl n'y a aucun fichier à compiler")
assert len(l_targets) > 0
""" Nettoyage d'éventuels anciens fichiers compilés. """
clean_targets()
yield {
'tests_path': tests_path,
'pc_path': pc_path,
'design_path': design_path,
# 'list_targets': []
'list_targets': l_targets
}
""" Nettoyage. """
clean_targets()
clean_design()
@staticmethod
# @pytest.mark.skip # Commenter cette ligne pour exécuter le test.
def test_ui2py_no(files):
""" Pas de compilation. La méthode doit retourner True. """
assert ut.ui2py(action=False) is True
@staticmethod
@pytest.mark.skip # Commenter cette ligne pour exécuter le test.
def test_ui2py_yes(files):
"""
Compilation forcée.
Pour chaque fichier_source :
- La 'date-heure' du fichier_cible est égale à {dh_now} (à 500 ms près).
"""
l_targets = files['list_targets']
tests_path = files['tests_path']
""" Compilation inconditionnelle (True = forcée). """
ut.ui2py(action=True)
""" Comparaison des dates-heures des fichiers-cible produits, avec dh actuel (dh_now)
L'écart doit être faible : 500ms max. Disons moins de 2 secondes, par sécurité. """
dh_now = time.time()
for target in l_targets:
ecart = dh_now - os.path.getmtime(tests_path + target)
assert ecart < 2 # Inférieur à 2 secondes.
@staticmethod
@pytest.mark.skip # Commenter cette ligne pour exécuter le test.
def test_ui2py_auto(files):
"""
Compilation conditionnelle.
Pour chaque fichier_source dont la 'date-heure' a changé :
- La 'date-heure' du fichier_cible est égale à {now} (à quelques ms près).
"""
def get_dh_memo():
lt_dh = list() # Liste de tuples.
for file_name in os.listdir(design_path):
if file_name.lower().endswith('.ui') or file_name.lower().endswith('.qrc'):
_file_py = file_name[:-3] if file_name.lower().endswith('.ui') else file_name[:-4]+'_rc'
_file_source = design_path + os.sep + file_name
_file_target = f"{tests_path}{_file_py}.py"
dh_source_memo = settings.value(f"dh_{_file_source}", 0.) # Valeur par défaut = 0.
lt_dh.append((_file_source, _file_target, dh_source_memo))
return lt_dh
tests_path = files['tests_path'] # D:\Robot\tests\ (avec \)
design_path = files['design_path'] # D:\Robot\tests\design (sans \)
settings = QSettings(f"{tests_path}{os.sep}params.conf", QSettings.IniFormat)
""" Test 1 ****************************************************************************************
Les dh ont changé => Les dh des fichiers-cible doivent changer. """
""" - Préparation : Changement artificiel des dates. """
for t_dh in get_dh_memo():
design_file = t_dh[0]
os.utime(design_file)
""" - Lancement de ui2py() en mode automatique. """
ut.ui2py()
""" - Vérification : => Les dh des fichiers-cible doivent être le dh actuel (à 500ms près). """
for t_dh in get_dh_memo():
file_target = t_dh[1]
if os.path.isfile(file_target):
dh = os.path.getmtime(file_target)
delay = time.time() - dh
assert delay < 2 # 2 secondes, par sécurité.
else:
assert os.path.isfile(file_target) is True
""" Test 2 ****************************************************************************************
Les dh-sources n'ont pas changé => Les dh des fichiers-cible ne doivent pas changer. """
""" - Préparation : Égalisation artificielle des dh_source. """
target_time = time.time() - 1000 # Valeur dans le passé
for t_dh in get_dh_memo():
design_file = t_dh[0]
dh_memo = t_dh[2]
os.utime(design_file, (dh_memo, dh_memo))
""" Modification des dh-cibles avant traitement. """
target_file = t_dh[1]
os.utime(target_file, (target_time, target_time))
""" - Lancement de ui2py() en mode automatique. """
ut.ui2py()
""" - Vérification : Les dh des fichiers-cible n'ont pas changé (elles sont égales à {target_time}) """
for t_dh in get_dh_memo():
target_file = t_dh[1]
dh = os.path.getmtime(target_file)
assert dh == target_time
""" Test 3 ****************************************************************************************
Certains dh-sources ont changé => Les dh des fichiers-cible correspondants doivent changer, pas les autres. """
""" Préparation : Mélange de la liste des fichiers, modification du premier, pas des autres. """
l_dh_memo = get_dh_memo()
random.shuffle(l_dh_memo)
os.utime(l_dh_memo[0][0]) # Un fichier, au hasard.
""" - Lancement de ui2py() en mode automatique. """
ut.ui2py()
""" Vérification : Seul le premier fichier de la liste a été compilé. """
now = time.time()
for i, t_dh in enumerate(l_dh_memo):
target_file = t_dh[1]
dh = os.path.getmtime(target_file)
delay = now - dh
if i == 0:
assert delay < 2 # Le premier a été modifié : 2 secondes, par sécurité.
else:
assert delay > 1000 # Les autres n'ont pas été modifiés.
TestUi2py
.
test_
, il y en a 3 : test_ui2py_no
, test_ui2py_yes
et test_ui2py_auto
@pytest.mark.skip
: il permet d'ignorer le test.ui2py(action=False)
:
test_ui2py_no()
est actif (@pytest.mark.skip
supprimé ou commenté) et que les autres sont ignorés.test_ui2py_no
: FAILEDtest_ui2py_yes
: SKIPPEDtest_ui2py_auto
: SKIPPEDui2py()
dans main.py
: if ut.ui2py()
devient if ut.ui2py(action=False)
Run main.py
: Erreur - La compilation des fichiers de Qt Designer a échoué.ui2py()
est None
au lieu de True
.ui2py()
. Voici le code :
if action is False:
return True
main.py
: Pas d'erreur, la fenêtre s'affiche.ui2py(action=True)
:
main.py
→ if ut.ui2py(action=True)
test_utils.py
en retirant le skip
du test test_ui2py_yes
Run main.py
: Erreur - La compilation des fichiers de Qt Designer a échoué.ui2py()
tant que le test ne passe pas.ui2py()
:
auto == None
main.py
et test_utils.py
en conséquence.Snippets
Bonjour les codeurs !