Résolue

Avis programme

# Fichiers # PySide # Scripting

Bonjour à vous,


J'aimerais avoir un avis sur un petit programme que j'ai écris, voir ce que je peux améliorer, les éventuelles erreur etc.


Il s'agit d'un programme qui va scanner un ou plusieurs dossier au choix de l'utilisateur en utilisant le module "Watchdog" et afficher une notification Windows lorsqu'un fichier est crée dans un des dossiers via le module "winotify".


Dans l'état actuel le programme fonctionne comme prévu, la seul chose qui me dérange c'est que j'ai du passer par la fonction "global" dans la méthode "click_start" sur la fonction "Observer()" du module Watchdog. Si je ne le fais pas la surveillance ne s'arrête jamais, que ce soit en passant par la méthode "click_stop" ou même en supprimant les dossiers de la liste des dossiers à surveiller.


Voici mon code et merci d'avance pour vos retours :-) :


import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QFileDialog, QListWidget, QTextEdit
from PySide6.QtGui import QIcon, QPixmap
from pathlib import Path
from winotify import Notification
from watchdog.events import FileSystemEventHandler, FileCreatedEvent
from watchdog.observers import Observer
import os
import json



class Window(QMainWindow):


    def __init__(self):
        super().__init__()
        self.setWindowTitle("Scan dossier")
        self.resize(400, 280)
        icone_application = QIcon("./data/icone_notif.ico")
        self.setWindowIcon(icone_application)
        self.background = QPixmap("./data/4853433.jpg")
        self.label_background = QLabel(self)
        self.label_background.setPixmap(self.background)
        self.label_background.setGeometry(1, 1, 400, 280)
        self.Widgetsprincipaux()
        self.show()


    def Widgetsprincipaux(self):


        button_1 = QPushButton("...", self)
        button_1.setGeometry(20, 130, 35, 25)
        button_1.setStyleSheet("background-color: skyblue; color: black; font-size: 14px")
        button_1.pressed.connect(self.click)
        
        self.dossier_a_scanner = QListWidget(self)
        self.dossier_a_scanner.setGeometry(20, 40, 350, 80)
        self.dossier_a_scanner.setStyleSheet("background-color: lavender")
        for i in liste_dossier_a_surveiller:  #Envoi des données de la variable liste_dossier vers le QListWidget au lancement de l'appli
            self.dossier_a_scanner.addItem(i)
        self.dossier_a_scanner.itemDoubleClicked.connect(self.double_click)


        self.label_liste = QLabel(self)
        self.label_liste.setText("Liste des dossiers à surveiller : ")
        self.label_liste.setGeometry(20, 10, 250, 25)


        self.button_start = QPushButton("Start", self)
        self.button_start.setGeometry(20, 230, 35, 25)
        self.button_start.setCheckable(True)
        if liste_dossier_a_surveiller != []: #bouton start coché si la variable liste_dossier n'est pas vide
            self.button_start.setChecked(True)
        self.button_start.setStyleSheet("background-color: limegreen; color: black; font-size: 14px")
        self.button_start.pressed.connect(self.click_start)


        self.button_stop = QPushButton("Stop", self)
        self.button_stop.setGeometry(70, 230, 35, 25)
        self.button_stop.setCheckable(True)
        self.button_stop.pressed.connect(self.click_stop)
        self.button_stop.setStyleSheet("background-color: red; color: black; font-size: 14px")


        self.label_texte_notif = QLabel(self)
        self.label_texte_notif.setText("Texte notification : ")
        self.label_texte_notif.setGeometry(20, 160, 160, 25)


        self.texte_notif = QTextEdit(self)
        self.texte_notif.setText("Un nouveau rapport est disponible.")
        self.texte_notif.setGeometry(20, 180, 300, 30)
        self.texte_notif.setStyleSheet("background-color: lavender") #border: 2px solid gray; border-radius: 5px; padding: 5px")




    def click(self):
        dossier_a_surveiller = QFileDialog.getExistingDirectory() #Selection des dossiers à surveiller
        liste_dossier_a_surveiller.append(dossier_a_surveiller)
        self.dossier_a_scanner.addItem(dossier_a_surveiller) #Ajout des dossiers selectionnés au QListWidget
       



    
    def double_click(self, item):
        self.dossier_a_scanner.takeItem(self.dossier_a_scanner.row(item)) #Suppression d'un dossier par double clique
        liste_dossier_a_surveiller = [self.dossier_a_scanner.item(i).text() for i in range(self.dossier_a_scanner.count())]
        


    def click_start(self):
        class Handler(FileSystemEventHandler):
            def on_created(self, event:FileCreatedEvent): #évènement = création d'un fichier
                file = Path(event.src_path).name #Nom du fichier crée dans la notif
                dir = Path(event.src_path) #Chemin vers le fichier crée
                dir_name = (dir.parent).name  #Nom du dossier concerné dans la notif
                icone_notif = r".\data\icone_notif.ico"
                path_icone_notif = os.path.abspath(icone_notif)
                notif = Notification(app_id = f"Notification {dir_name}", title = texte_notif_affichage, msg = file, duration="short", icon = path_icone_notif)
                notif.add_actions(label= f"Ouvrir '{dir_name}'", 
                    launch= os.path.dirname(event.src_path))         
                notif.show()
        if self.button_stop.isChecked():
            self.button_stop.setChecked(False)


        texte_notif_affichage = self.texte_notif.toPlainText() #Changement du texte de la notif par l'utilisateur
        global observer
        observer = Observer()
        liste_dossier_a_surveiller = [self.dossier_a_scanner.item(i).text() for i in range(self.dossier_a_scanner.count())] # ajout des dossiers à surveiller à la variable liste_dossier
        #Sauvegarde du contenu de la variable liste_dossier vers le fichier JSON
        dossier_data = "data"
        abs_folder_path = os.path.abspath(dossier_data) #Chemin absolu du dossier data
        data_path = os.path.join(abs_folder_path, "liste_dossier_a_surveiller.json") #chemin vers le fichier JSON
        with open(data_path, 'w') as file:
            json.dump(liste_dossier_a_surveiller, file)
        #Lancement de "l'observer" sur chaque dossier de la variable liste_dossier
        for i in liste_dossier_a_surveiller:
            observer.schedule(Handler(), i)
        observer.start()



    def click_stop(self):
        if self.button_start.isChecked():
            self.button_start.setChecked(False)
        observer.stop()
        observer.join()
        liste_dossier_a_surveiller = [self.dossier_a_scanner.item(i).text() for i in range(self.dossier_a_scanner.count())]



#Chargement du fichier JSON à l'ouverture de l'appli et envoi du contenu dans la variable liste_dossier
dossier_data = "data"
abs_folder_path = os.path.abspath(dossier_data)
data_path = os.path.join(abs_folder_path, "liste_dossier_a_surveiller.json")


try:
    with open(data_path, 'r') as file:
        liste_dossier_a_surveiller = json.load(file)
except FileNotFoundError:
    open(data_path, 'w').close()
    liste_dossier_a_surveiller = []


app = QApplication(sys.argv)
start = Window()


if liste_dossier_a_surveiller != []:  #Démarrage de la surveillance au démarrage si dossiers dans la variable liste_dossier
    start.click_start()


sys.exit(app.exec())



Il faut que tu le mette en attribut d'instance en faisant :

self.observer = Observer()

Bonsoir,


Effectivement comme Chirstian l'évoque il serait bien de changer la global observer en attribut d'instance. En programmation on évite le plus possible d'utiliser des variables globales car ça devient très vite incompréhensible.

Ensuite, je vous conseille de suprimer le try / except qui permet de vérifier si un fichier existe bien. Pour cela, vous pouvez utiliser la méthode exists dans le module os en lui passant directement un chemin.



os.path.exists(path)



Vous pouvez également séparer la méthode WidgetsPrincipaux en sous méthodes car elle vraiment trop grande. L'idée pour avoir un code propre est de se fixer une limite de 20-25 lignes par méthode / fonction. Plus une fonctionnalité est découpée, plus ce sera simple de la maintenir.


Un petit détail mais en Python on aime bien organiser les import dans un ordre précis.


  1. Les modules standard
  2. Les modules externes
  3. Les modules locaux


Dans votre cas cela donnera :


import sys
import os
import json
from pathlib import Path
from PySide6.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, \
    QFileDialog, QListWidget, QTextEdit
from PySide6.QtGui import QIcon, QPixmap
from winotify import Notification
from watchdog.events import FileSystemEventHandler, FileCreatedEvent
from watchdog.observers import Observer



Enfin, essayez d'écrire votre code entièrement en anglais. Ce n'est pas une bonne habitude du français / anglais.


Sinon dans l'idée le projet est plutôt bien réalisé. Il ne faut pas paniquer, les bonnes habitudes viendront avec le temps.


PS : avec le temps vous verrez que les commentaires sont inutiles et qu'il suffit juste de faire des docstring.


N'hésitez pas si certains points ne sont pas clairs pour vous.


Bien cordialement.

Tom de l'équipe Docstring.

Merci à vous deux pour vos retours. J'ai pris en compte vos remarques, voici ce que j'obtiens :


import sys
import os
import json
from pathlib import Path
from PySide6.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QFileDialog, QListWidget, QTextEdit
from PySide6.QtGui import QIcon, QPixmap
from winotify import Notification
from watchdog.events import FileSystemEventHandler, FileCreatedEvent
from watchdog.observers import Observer


class Window(QMainWindow):

    def __init__(self):
        super().__init__()
        self.setWindowTitle("Scan dossier")
        self.resize(400, 280)
        icone_application = QIcon("./data/icone_notif.ico")
        self.setWindowIcon(icone_application)
        self.background = QPixmap("./data/4853433.jpg")
        self.label_background = QLabel(self)
        self.label_background.setPixmap(self.background)
        self.label_background.setGeometry(1, 1, 400, 280)
        self.Widgetsprincipaux()
        self.buttons()
        self.show()


    def Widgetsprincipaux(self):
        
        self.dossier_a_scanner = QListWidget(self)
        self.dossier_a_scanner.setGeometry(20, 40, 350, 80)
        self.dossier_a_scanner.setStyleSheet("background-color: lavender")
        for i in liste_dossier_a_surveiller:  #Envoi des données de la variable liste_dossier vers le QListWidget au lancement de l'appli
            self.dossier_a_scanner.addItem(i)
        self.dossier_a_scanner.itemDoubleClicked.connect(self.double_click)

        self.label_liste = QLabel(self)
        self.label_liste.setText("Liste des dossiers à surveiller : ")
        self.label_liste.setGeometry(20, 10, 250, 25)

        self.label_texte_notif = QLabel(self)
        self.label_texte_notif.setText("Texte notification : ")
        self.label_texte_notif.setGeometry(20, 160, 160, 25)

        self.texte_notif = QTextEdit(self)
        self.texte_notif.setText("Un nouveau rapport est disponible.")
        self.texte_notif.setGeometry(20, 180, 300, 30)
        self.texte_notif.setStyleSheet("background-color: lavender") #border: 2px solid gray; border-radius: 5px; padding: 5px")

    def buttons(self):

        button_1 = QPushButton("...", self)
        button_1.setGeometry(20, 130, 35, 25)
        button_1.setStyleSheet("background-color: skyblue; color: black; font-size: 14px")
        button_1.pressed.connect(self.click)

        self.button_start = QPushButton("Start", self)
        self.button_start.setGeometry(20, 230, 35, 25)
        self.button_start.setCheckable(True)
        if liste_dossier_a_surveiller != []: #bouton start coché si la variable liste_dossier n'est pas vide
            self.button_start.setChecked(True)
        self.button_start.setStyleSheet("background-color: limegreen; color: black; font-size: 14px")
        self.button_start.pressed.connect(self.click_start)

        self.button_stop = QPushButton("Stop", self)
        self.button_stop.setGeometry(70, 230, 35, 25)
        self.button_stop.setCheckable(True)
        self.button_stop.pressed.connect(self.click_stop)
        self.button_stop.setStyleSheet("background-color: red; color: black; font-size: 14px")


    def click(self):
        dossier_a_surveiller = QFileDialog.getExistingDirectory() #Selection des dossiers à surveiller
        liste_dossier_a_surveiller.append(dossier_a_surveiller)
        self.dossier_a_scanner.addItem(dossier_a_surveiller) #Ajout des dossiers selectionnés au QListWidget
       
    
    def double_click(self, item):
        self.dossier_a_scanner.takeItem(self.dossier_a_scanner.row(item)) #Suppression d'un dossier par double clique
        liste_dossier_a_surveiller = [self.dossier_a_scanner.item(i).text() for i in range(self.dossier_a_scanner.count())]
        

    def click_start(self):
        self.observer = Observer()
        class Handler(FileSystemEventHandler):
            def on_created(self, event:FileCreatedEvent): #évènement = création d'un fichier
                file = Path(event.src_path).name #Nom du fichier crée dans la notif
                dir = Path(event.src_path) #Chemin vers le fichier crée
                dir_name = (dir.parent).name  #Nom du dossier concerné dans la notif
                icone_notif = r".\data\icone_notif.ico"
                path_icone_notif = os.path.abspath(icone_notif)
                notif = Notification(app_id = f"Notification {dir_name}", title = texte_notif_affichage, msg = file, duration="short", icon = path_icone_notif)
                notif.add_actions(label= f"Ouvrir '{dir_name}'", 
                    launch= os.path.dirname(event.src_path))         
                notif.show()
        if self.button_stop.isChecked():
            self.button_stop.setChecked(False)

        texte_notif_affichage = self.texte_notif.toPlainText() #Changement du texte de la notif par l'utilisateur
        liste_dossier_a_surveiller = [self.dossier_a_scanner.item(i).text() for i in range(self.dossier_a_scanner.count())] # ajout des dossiers à surveiller à la variable liste_dossier
        #Sauvegarde du contenu de la variable liste_dossier vers le fichier JSON
        dossier_data = "data"
        abs_dossier_data = os.path.abspath(dossier_data) #Chemin absolu du dossier data
        data_path = os.path.join(abs_dossier_data, "liste_dossier_a_surveiller.json") #chemin vers le fichier JSON
        with open(data_path, 'w') as file:
            json.dump(liste_dossier_a_surveiller, file)
        #Lancement de "l'observer" sur chaque dossier de la variable liste_dossier
        for i in liste_dossier_a_surveiller:
            self.observer.schedule(Handler(), i)
        self.observer.start()


    def click_stop(self):
        if self.button_start.isChecked():
            self.button_start.setChecked(False)
        self.observer.stop()
        self.observer.join()
        liste_dossier_a_surveiller = [self.dossier_a_scanner.item(i).text() for i in range(self.dossier_a_scanner.count())]


#Chargement du fichier JSON à l'ouverture de l'appli et envoi du contenu dans la variable liste_dossier
dossier_data = "data"
abs_dossier_data = os.path.abspath(dossier_data)
data_path = os.path.join(abs_dossier_data, "liste_dossier_a_surveiller.json")


if os.path.exists(data_path):
    with open(data_path, "r") as json_file:
        liste_dossier_a_surveiller = json.load(json_file)
else:
    with open(data_path, "w") as json_file:
        liste_dossier_a_surveiller = []
        json.dump(liste_dossier_a_surveiller, json_file)

app = QApplication(sys.argv)
start = Window()

if liste_dossier_a_surveiller != []:  #Démarrage de la surveillance au démarrage si dossiers dans la variable liste_dossier
    start.click_start()

sys.exit(app.exec())

Inscris-toi

(c'est gratuit !)

Inscris-toi

Tu dois créer un compte pour participer aux discussions.

Créer un compte

Rechercher sur le site

Formulaire de contact

Inscris-toi à Docstring

Pour commencer ton apprentissage.

Tu as déjà un compte ? Connecte-toi.