Miamondo

Python

  • Kronik

    Sommaire : Présentation de l'application Le script start.py le script kronik.py Epoch Les itérateurs  Quelques réflexions sur la notion d'infinité

    Bonjour

    Aujourd'hui, j'ai envie de vous présenter une application codée par mes soins. Je l'ai baptisée Kronik. Il s'agit d'un planificateur de tâches comparable à Cron, mais dotée d'une interface graphique. J'ai commencé à écrire les premières lignes en Bash, mais je trouvais le résultat peu chatoyant. J'ai donc décidé d'emballer tout ça dans du Python et du Tkinter. Vous trouverez une vidéo à la fin de l'article.

    Le projet est perfectible, notamment au niveau du redimensionnement des fenêtres, mais je suis tout de même très satisfait du résultat, car il m'a fallu relever plusieurs défis:

    • Des calculs mathématiques et des ajustements temporels à la limite de mes capacités mentales.
    • L'utilisation d'un itérateur qui permet de ne pas s'engouffrer dans une boucle infinie ayant pour irrémédiable conséquence un crash de l'application.

    Alors, comment ça fonctionne? Eh bien, grâce à deux scripts Python:

    • start.py lance le programme proprement dit, lequel permet à l'interface chaise/clavier, de sélectionner les applications à planifier.
    • kronik.py est lancé par le fichier openbox/autostart, à chaque démarrage et se charge de gérer les tâches précédemment planifiées.

    Voici donc à quoi ressemble la page d'accueil:

     

    Kronik

     

    On trouve le titre enchâssé dans un widget LabelFrame, avec en dessous, un second cadre étiqueté qui permet de sélectionner une commande ou de renseigner directement le chemin absolu d'un fichier à exécuter (.py, .c, etc...). Le menu déroulant (Listbox) répertorie les commandes de toutes les applications installées sur votre ordinateur. Pour générer cette liste, ce n'est pas bien compliqué. Il suffit de boucler sur le répertoire /usr/share/applications. Ensuite, il faut ouvrir chaque fichier *.desktop pour en extraire la ligne qui commence par exec

    Sous ce menu déroulant, on trouve un champ d'entrée qui permet de renseigner le chemin absolu d'une application qui n'est pas présente dans /usr/share/applications. Enfin, on trouve un bouton Applications planifiées. Il ouvre une fenêtre Toplevel qui répertorie toutes les tâches planifiées et permet de supprimer chacune d'entre elles. Je souhaite améliorer cette fonctionnalité, en y incluant la fréquence (tous les jours, toutes les deux heures etc...)

     

    Fenêtre surgissante

     

    Continuons... Je sélectionne gnome-disks et je valide. Une fenêtre surgissante apparaît et me demande de confirmer en cliquant dessus. Je peux encore changer d'avis mais je décide de confirmer.

     

    kronik2

     

    J'atterris sur une fenêtre remplie de widgets Scale. Il est à noter que je me suis arrangé pour qu'il ne soit pas possible de déplacer deux curseurs simultanément. Si vous bougez un curseur, les autres sont désactivés ou reviennent à la position 0. Il y a également un widget Entry. Ce dernier permet à l'usager de renseigner une tâche qui va se lancer une seule fois. Pour une précision optimale, il faut que je rajoute les minutes et les secondes. Lorsque vous avez fait votre choix et que vous l'avez confirmé, la planification est lancée.

     

    Kronik

     

    2020 07 29 204650 788x622 scrot

     

    Ça, c'est la partie émergée de l'iceberg. Le travailleur de l'ombre, celui qui gère la planification depuis son repaire situé dans les profondeurs de la chaîne volcanique balafrant les Terres de Volgor, c'est kronik.py. Et on peut dire que sa conception m'a donné du fil à retordre, car elle m'a obligé à manipuler des dates transformées en secondes et paramétrées en prenant pour référence Epoch, c'est-à-dire le 1er Janvier 1970. Je ne suis lancé dans de savants calculs qui ont failli me rendre fou, d'autant plus que je voulais trouver une combine pour prendre en compte le temps écoulé lorsque l'ordinateur n'est plus sous tension. C'est-à dire que lorsque je rallume mon ordinateur après une bonne nuit de sommeil, les paramètres temporels ne sont plus les mêmes qu'au moment de l'extinction. Ils ont été mis à jour grâce à l'intervention d'Epoch. Voici donc le fichier généré par Kronik le bossu. Il s'appelle scheduledApps:

    • /home/miamondo/.config/kronik/start.py:86400:47872:1596000773:1596039301
    • gnome-disks:86400:83624:1596039457:1596042233
    • /home/miamondo/.config/kronik/system_update/system_update.py:86400:86308:1596042147:1596042239

    Les paramètres sont séparés par deux petits points. Actuellement, on trouve trois tâches planifiées. Prenons la dernière d'entre elles. Il s'agit d'une petite application que j'ai inclus dans le programme, pour mettre à jour ma distribution Archlinux. Je l'ai codée il y a deux ans.

    1. Chemin de l'application: system_update.py
    2. Fréquence d'exécution du programme: 86400 secondes soit 24 heures
    3. Temps restant avant exécution du programme: 86308 secondes
    4. Précédente exécution du programme (ou premier lancement): 1596042147 secondes après Epoch (01/01/1970)
    5. Tems écoulé depuis Epoch: 1596042239 secondes. C'est l'instant présent. 

    Si vous êtes perspicaces, vous aurez remarqué qu'entre le second et le troisième paramètre, l'écart est de 92 secondes. C'est exactement le même nombre qui sépare le quatrième et le cinquième paramètre... Et normalement, si j'éteins mon ordinateur et que je le redémarre  dans deux heures, les écarts seront identiques (évidemment pas forcément 92 secondes!). Je ne vous raconte pas la prise de tête pour configurer tout ça!

     

    system_update

     

    Deuxième gros défi, il faut que le temps et le programme s'écoulent sans interruption. Il n'est pas possible de créer une boucle infinie du type while 1: go on!. Au bout de 998 tours, elle va se crasher, et le programme avec. Il faut écrire du code itératif ou récursif, mais en prenant soin de définir une condition qui permette théoriquement de sortir de la boucle. J'utilise donc un itérateur, qui tourne tant que la variable d' incrémentation n'a pas atteint Époch, ce qu'elle ne fera jamais... mais elle ne le sait pas... Et il ne faut surtout pas lui dire! Voici le bout de code :

            for i in range(0, int(datetime.datetime.now().timestamp())):
                self
    .go_on()

        def go_on(self): ... suite du code.

    Ce code de deux lignes, plus philosophique qu'informatique, nous interroge sur la notion d'infinité. Il s'agit bien d'une boucle infinie puisque la variable i ne rattrapera jamais Epoch, condition sine qua non pour arrêter le programme. Epoch est lui-même un itérateur. Et pourtant, si on change la valeur de la variable i en lui affectant Epoch, alors la boucle s'arrête... Et ça, ce n'est pas possible avec une boucle du type while 1 < 2. Celle-ci est réellement une prison infinie, un châtiment mathématique perpétuel que l'on ne peut arrêter puisque 1 sera pour toujours inférieur à 2.    

    Voilà donc pour cette petite application qui m'a demandé du temps à coder, mais qui m'a aussi apporté beaucoup de satisfaction. Elle est perfectible, certes, mais elle est déjà opérationnelle. N'hésitez pas à me faire part de vos remarques, notamment au niveau du code itératif. Il n'a pas encore planté mais sait-on jamais... Peut-être y-a-t-il une faiblesse qui m'échappe. 

    J'essaierai, dans un prochain billet, d'expliquer la différence entre un itérateur et une fonction récursive mais la vulgarisation de ces théories abstraites n'est pas chose aisée. 

  • À la découverte de Flask

    Bonjour, 

    Il y a quelques semaines, j'ai découvert Flask, une infrastructure de développement implémentée en Python et combinée à un serveur local. Son programme de base compte en tout et pour tout, sept malheureuses lignes de code!

    Scrot 5 000

    Une fois exécuté, il vous permet d'afficher Hello world, à l'adresse 127.0.0.1:5000, (c'est-à-dire localhost, port 5000). Pour pouvoir utiliser cet outil fort intéressant, il faut au préalable l'installer. Avec Archlinux, c'est un jeu d'enfant. mettez d'abord vos paquets à jour avec pacman -Syu puis entrez cette commande:

    sudo pacman -S python-flask

    Avec Debian et ses dérivés, c'est un peu plus compliqué mais vous trouverez la procédure à cette adresse.  

    Alors, comment est-ce que j'en suis venu à m'intéresser à Flask? Eh bien, c'est parce que je me suis mis en tête de créer mon propre site web pour l'héberger sur une raspberry pi. C'est un projet qui va m'occuper pendant plusieurs mois mais je suis bien avancé puisque le code HTML/CSS3 est déjà opérationnel. Certes, le design s'inspire de celui qui habille mon site actuel hébergé chez un SGB, mais je vous assure que je n'ai pas pompé une seule ligne de code! Sinon c'est pas du jeu. Voici donc à quoi ressemble le site autohébergé Miamondo 3.0 version alpha:

    Scrot 1

    Je suis relativement satisfait du résultat. Cela dit, je souhaite avoir un site qui soit un minimum interactif. J'ai donc créé un onglet de recherche qui, si je ne dis pas de bêtises, a besoin d'un peu de code javascript et d'une base de données pour fonctionner. Mais comme j'aime bien expérimenter des trucs (même si parfois ça part en cacahouète), je me suis dit que je pourrais peut-être essayer de trouver une solution alternative avec le langage Python. Et ça tombe bien puisque Flask est un outil qui marie joyeusement Python et HTML.

    Dans un environnement Flask expérimental, il est tout à fait possible de renvoyer à un visiteur, le résultat d'une recherche et ceci, en utilisant simplement du Python. Je le sais puisque je viens justement de le faire. Par contre, je ne sais pas si ce procédé fonctionnera une fois que le site sera en "production", hébergé dans sa raspberry Pi. J'ai des doutes car lorsque je démarre le serveur, voici le message qui s'affiche dans la console:
    WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
    Je ne sais pas ce que c'est qu'un WSGI mais tout me laisse à penser que cette mise en garde en rouge n'augure rien de bon et qu'il faudra que je me mette en quête d'une autre solution...

    On verra bien. En attendant, voici comment j'ai procédé (d'abord le code suivi de quelques explications sommaires):

    Code Python

    Scrot 6

     

    Code HTML

    Scrot 7

    • Il s'agit bien évidemment de la portion de code HTML qui nous intéresse, c'est-à-dire celle qui correspond au champ de recherche "liée" au code Python. Pour le reste du code, c'est ici que ça se passe.
    • Dans mon répertoire utilisateur, j'ai d'abord créé un sous-répertoire intitulé webapp et j'y ai placé le script primaire de Flask (celui qui est tout en haut de ce billet). Il s'intitule app.py.
    • Je l'ai complété en important render_template (nécessaire pour afficher la page index.html) et request (nécessaire pour récupérer la recherche client dans une variable).
    • La fonction index() affiche la page d'accueil index.html
    • Cette page HTML doit être placée dans un sous-dossier de webapp intitulé templates.
    • En ce qui concerne le code CSS, les images du site et autres documents PDF, ils doivent être placés dans un sous-répertoire de webapp intitulé static. Au final, voici l'architecture de votre site:

    Scrot 8 000 

    • La fonction sniff() cherche dans la page index.html, les occurrences correspondant à la recherche du visiteur. Ce dernier n'est pas obligé de respecter la casse car le processus sniff met tout en minuscules. Vous noterez que les deux fonctions renvoient un résultat. Si ce n'était pas le cas, le programme planterait.

    On continue:

    • Je créé une liste vide et je récupère la totalité des lignes du fichier index.html que je joins avec la méthode "".join(). Ce n'est donc plus une liste mais un string. 
    • Je redécoupe cette gigantesque chaine de caractères en éléments séparés par "\n\n". Ce procédé m'oblige par contre à respecter des règles rigoureuses dans l'agencement de mon code html. Chaque section doit être séparée par "\n\n". Je me demande si je ne ferais pas mieux de créer un dictionnaire contenant tous les mots du site et qui, contrairement à une liste, n'aurait pas besoin d'être parcouru puisqu'il s'agit d'une table de hachage. À voir... Continuons:
    • Une fois les occurrences trouvées, la liste found se charge d'afficher le menu principal, la bannière, les articles qui contiennent l'occurrence recherchée et enfin, le menu latéral avec l'onglet de recherche (found.append(lines[-1]).
    • Si sniff() ne trouve rien, il affiche un message adéquat:

    Scrot 4

    • Si j'entre le mot Révolution en mélangeant volontairement majuscules et minuscules, sniff va m'afficher le premier article qui, de fait, contient bien ce mot.

    Scrot 3

     

    • Si j'entre le mot Massoud, sniff va m'afficher l'introduction du site et effacer le reste.

    Scrot 2

    Voilà... Tout ça pour dire que ça ne m'étonne pas que Flask soit devenu si populaire compte tenu de sa simplicité de mise en oeuvre et des possibilités qu'il offre. Je vais continuer à utiliser cet outil et à voir ce que je peux faire sur mon site 3.0 alpha, en utilisant simplement Python.
    @+

  • Mon jardin numérique (première partie)

    Bonjour à toutes et à tous,

    Nous sommes au mois d'Avril et malgré le confinage, cela fait plusieurs semaines que j'ai recommencé à jardiner. C'est une activité qui me détend et au moins, les légumes ne risquent pas de me refiler le corolibrius. Un radis, ça ne postillonne pas!

    Même si on jardine pour son plaisir, il est important de planifier et de bien noter toutes les tâches que l'on effectue: 

    • les dates des semis,
    • l'emplacement de chaque légume pour ne pas replanter la même chose au même endroit l'année suivante,
    • les quantités récoltées,
    • etc.

    Pour ce faire, on peut, si on a un minimum de bon sens et si on est un adepte de la simplicité volontaire, acheter un cahier à spirale et un stylo quatre couleurs. C'est un investissement qui ne dépasse pas les cinq euros. Si vous avez un petit peu d'argent de côté, vous pouvez même compléter cet équipement avec une règle en bois.

    Cahier 1

    Quand on maîtrise LibreOffice Calc, on peut se lancer dans la création d'un tableau qui fera tout aussi bien l'affaire et qui vous additionnera les quantités récoltées automatiquement.

    Mais lorsque comme moi, on veut se compliquer l'existence, rien de mieux que de se lancer dans la programmation d'une application pour le jardin. Lorsque celle-ci sera opérationnelle, je réaliserai sans doute qu'elle aura accaparé tout mon temps, jusqu'à en oublier de mettre les pieds dans le jardin. Je constaterai avec effroi que ce dernier sera envahi de plantes adventices. Cela dit, notez que beaucoup de ces plantes qu'on appelle à tort, des mauvaises herbes, sont parfaitement comestibles.

    Bref, j'ai commencé à programmer l'ambitieuse application en utilisant le langage Python et la bibliothèque Tkinter. Vous trouverez le code ici sur mon dépôt git. Veuillez noter qu'il est impératif d'installer le logiciel xrandr sur votre ordinateur. Voici donc à quoi ressemble la page d'accueil:

    Garden

    Sobre et élégante, il s'agit en fait d'un gros bouton. qui donne accès à l'application proprement dite. En-dessous, on trouve un lien vers mon site. Cliquons sur l'image et découvrons la page suivante:

    Garden2

    Quatre boutons qui correspondent aux catégories que l'on peut trouver dans un jardin digne de ce nom, à savoir Légumes, Fruits, Herbes et Fleurs.

    Cliquons maintenant sur l'un des quatre boutons, au hasard Légumes. Une fenêtre s'ouvre. Elle nous permet d'enregistrer notre première espèce, par exemple tomate. Je clique donc sur ajouter un légume. Vous pouvez cliquer sur les images pour les agrandir. Une fenêtre surgissante s'ouvre m'invitant à renseigner le nom du légume dans un champ d'entrée. Puis une autre fenêtre surgissante s'ouvre et me demande si je veux illustrer le nom du légume avec une image explicite. Je réponds oui et je télécharge une image. 

    Garden3

    Garden4

    Garden5

    Garden6

    Je clique de nouveau sur légumes et je constate que la page de l'espèce tomate a bien été créée. Je clique sur celle-ci et j'atterris sur la page tomate constituée de panneaux coulissants (paned windows) qui permettent, bien évidemment, de redimensionner l'espace. Sachez que vous pouvez cliquer sur le nom du légume. Il vous conduira directement à la page Wikipedia qui lui est consacrée!

    Garden7

    Il me reste à meubler cette page de panneaux coulissants avec différents widgets qui me permettront d'enregistrer tout un tas d'informations bien utiles telles que les dates de semis, les dates et le poids des récoltes, que sais-je encore? Peut-être que je vais inclure une photothèque et aussi un éditeur de texte pour prendre des notes. Ce sera l'objet de la deuxième partie.

    Garden8

  • Python: La programmation parallèle avec le module threading

    Bonjour, 

    Aujourd'hui, j'ai envie de vous parler du module threading. Ce dernier gère la programmation parallèle dans le langage Python et il est fort utile. Je l'ai utilisé pour créer un bouton dont la finalité est d'afficher l'heure à la seconde près, et d'ouvrir un calendrier. Ce bouton a donc deux fonctions et pour remplir sa mission, il a besoin de deux boucles:

     

     Clockbutton2

             

    • une boucle qui surveille les événements de l'utilisateur. C'est la boucle mainloop() de l'environnement graphique tkinter. Par exemple, Si le pointeur de la souris survole le widget Button, la couleur de ce dernier change.
    • une boucle infinie de type while, qui tourne toutes les secondes et qui permet d'afficher l'heure exacte sur le bouton.

    Si nous ne lançons pas ces deux boucles en parallèle, alors soit le code affichera le défilement des secondes mais le bouton sera inactif, soit le bouton sera actif mais les secondes seront figées dans l'éternité... Enfin, jusqu'à l'arrêt forcé du programme.

    Voici un exemple de code qui ne fonctionne pas. Il y a dans ce code deux boucles : while 1 qui est une boucle infinie et mainloop() réceptionnaire d'événements du GUI tkinter. Lorsque le programme pénètre dans la boucle infinie, il n'a plus aucun moyen d'en sortir. Certes, il affichera le défilement des secondes jusqu'à la fin des temps, mais comme la boucle mainloop() se trouve en dehors, nous n'avons plus accès aux événements du bouton. Celui-ci est complètement pétrifié. Comme je l'explique plus bas dans la vidéo en lien avec ce billet, il n'est même pas possible de cliquer sur la croix pour fermer le programme.

    Si nous déplaçons la toute dernière instruction (button.mainloop) à l'intérieur de la boucle while, une seule seconde va défiler puis le programme va "sauter" dans la boucle mainloop() et quitter la boucle infinie. Nous aurons accès aux fonctionnalités du bouton mais l'heure restera figée.

    La solution

    Elle est fort simple. Voici le code qui fonctionne. Il suffit de créer deux objets de la classe threading et de les démarrer en parallèle presque simultanément grâce à la méthode start(). Celle-ci va ensuite passer le relais à la méthode self.run(). Les noms de ces deux méthodes sont génériques. Ils ne peuvent pas être remplacés par d'autres termes. Les deux processus ne démarrent pas exactement au même moment. Il y a une différence de 200 millièmes de secondes. Dans ce cas précis, cet intervalle est nécessaire si on ne veut pas que le programme se crashe lamentablement juste après le décollage.

    Une fois lancé, chaque "thread" vit sa vie. Ils ont toutefois la possibilité d'accéder à des instructions communes comme par exemple les variables de classe. C'est le cas dans mon programme. La boucle infinie a besoin de la variable qui stocke l'objet fenêtre principale. Mais pour éviter qu'ils se mélangent les pinceaux et qu'ils modifient au même moment des données vitales pour la stabilité du programme, nous importons la classe Lock() du module threading.

    Lock() signifie verrou. Je déclare donc une variable (lock = Lock()) en tout début de programme, et je verrouille le premier thread grâce à l'instruction with lock. Avec cet outil, le second thread ne ne démarrera que lorsque le premier aura terminé son travail et lui aura passé la main. En clair, le premier thread fait ce qu'il a à faire, tandis que le second qui n'est pas verrouillé, reste sur le seuil de la porte. Lorsque le premier est prêt, il ouvre la porte et laisse entrer le second thread. Si le premier doit reprendre son travail pour modifier je ne sais quel bouton, il prie le second de bien vouloir sortir de la pièce. C'est très pratique! Bien évidemment, comme le second thread est une boucle infinie, il n'est pas possible de le verrouiller. Il ne passerait jamais la main à son collègue. Voilà donc pour le module threading que je vous invite à découvrir. Si vous avez des remarques ou des interrogations, n'hésitez pas. Ma porte est ouverte.

     

  • Un environnement de bureau codé en Python

    1ère partie : la barre des tâches

     

    Taskbar3

    Bonjour,

    Il y a un peu plus d'un an, je m'étais lancé dans un projet ambitieux, en l'occurrence la programmation d'un environnement de bureau en langage Python. Le résultat n'avait pas été à la hauteur de mes espérances. Si j'ai réussi à coder le menu des applications, je n'ai jamais réussi à coder proprement la barre des tâches et le fond d'écran.

    Les boutons de la barre des tâches tremblotaient ou disparaissaient inopinément ce qui n'était pas un signe de stabilité. Mais ça, c'était avant. Aujourd'hui, je peux annoncer que j'ai réussi à coder une barre des tâches stable et fonctionnelle. Voici une petite vidéo pour illustrer mes propos:

     

     

    Vous trouverez le code sur mon dépôt git, à cette adresse.Le fichier qui lance l'application, se nomme miamondo.py. Il est mis à jour quotidiennement.

    Points à corriger ou à améliorer

    • Le placement des fenêtres. Lorsque l'une d'entre elles est maximisée et couvre les autres, je dois iconifier et désiconifier ces dernières pour les faire apparaitre au-dessus. Ce n'est pas satisfaisant.
    • Les icônes des boutons. J'ai prévu une icône par défaut, pour éviter de faire planter l'application. Mais cette icône apparait bien trop souvent à mon goût. Elle signifie que je n'ai pas réussi à automatiser mon système d'icônes de manière satisfaisante. La raison en est que je ne maîtrise pas suffisamment les expressions régulières (regex). Je dois donc travailler ce point en priorité. Les icônes sont toutes présentes dans mon système de fichiers Archlinux, soit sous /usr/share/icons ou bien sous /usr/share/pixmaps. Les applications sont toutes répertoriées sous /usr/share/applications. J'ai tout ce qu'il faut pour bien faire. Le problème vient donc de mes lacunes en regex.

    Points de satisfaction

    • Stabilité du code. Dans la barre des tâches, les boutons des applications ouvertes ne scintillent pas ou pire encore, ne disparaissent pas de manière inopinée. À l'ouverture d'une fenêtre, le bouton se matérialise immédiatement. À la fermeture de cette même fenêtre, le bouton qui lui est lié, disparait immédiatement de la barre des tâches. Cela prouve que les boucles fonctionnent. 
    • Découverte de la programmation parallèle avec le module threading.
    • Découverte et prise en compte de l'importance du module regex.
    • Le nombre de processus zombies ne dépasse pas 2, ce qui est le signe que le programme est plutôt bien codé, me semble-t-il.

    À bientôt pour la deuxième partie qui vous présentera le menu principal.