Journal htag : realiser des UI en python3 sur web, mobile et desktop.

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
21
11
août
2022

Juste une bafouille pour vous présentez ma nouvelle lib python3 : htag

En gros : ça permet de créer des composants qui seront nativement transformés en HTML/JS/CSS, et qui seront réutilisables dans une appli desktop (linux/mac/win), une appli android (apk) ou dans un site web.

En simplifiant : on code ses composants comme on les coderait avec une lib de gui classique (tk, gtk, qt, wx, …), et on les fait tourner à l'aide d'un "runner htag" (pour du desktop: ça utilise pywebview, pour du android : ça utilise kivy/webview, pour du web : starlette ou tornado, … ) … Accessoirement, ça tourne complètement dans PYSCRIPT : https://raw.githack.com/manatlan/htag/main/examples/pyscript_htbulma.html (C'est assez bluffant, juste dans du HTML ;-)

Un "runner spécial pour le dev" permet de développer rapidement et facilement ses composants en lançant le tout dans un onglet du navigateur (où l'ont peu bénéficier du devtools(f12)), et qui réalise le autoreload/autorefresh en live, et affiche clairement les éventuelles erreurs ;-)

Techniquement : c'est une sorte de vuejs côté python(back), les échange front/back sont réalisées en WS ou HTTP, et ça ne redessine que les composants modifiés.

Pratiquement : ça permet de fabriquer simplement de beaux front-ends interactifs (html/js/css), sorte de SPA, tout en restant dans python3. Et de faire des applis graphiques sur mobile, desktop ou web avec le même code source ;-) (Et ça encourage à la ré-utilisation de composants)

Un exemple de code :

    from htag import Tag

    class App(Tag.body):
        """ An example with basic interaction """

        def init(self):
            self.content = Tag.div()

            self <= self.content
            self <= Tag.button("Add content",_onclick=self.more)
            self <= Tag.button("Clear",_onclick=lambda o: self.content.clear())

            # just an example to call a method in javascript side
            self <= Tag.button("alert box",_onclick="alert(42)")

        def more(self, object):
            self.content <= "hello " 

(le <= est juste un shortcut, mais peut être remplacé par self.add( Tag.button(...) ) )

Des démos/tutoriels sont dispos sur https://htag.glitch.me/
(en attendant une doc plus complète, c'est le moyen le plus simple de comprendre comment ça fonctionne)

l'annonce originelle sur reddit

Un tuto simple pour faire un "input field" interactif :
https://manatlan.github.io/htag/tuto_create_an_input_component/
(tous les exemples fonctionnent OOTB localement, après un pip install htag évidement)

Une librairie de composants réutilisables : https://github.com/manatlan/htbulma
(uniquement destiné à mes besoins ;-)

Voilà, c'est assez simple, mais ça fonctionne fichtrement bien (au moins pour moi) …

Il faudrait que je me penche sur une doc plus fournie ;-)

  • # A working example OO ;-)

    Posté par  (site web personnel) . Évalué à 2.

    Le site officiel : https://github.com/manatlan/htag

    Juste un exemple qui fonctionnera OOTB sur votre poste, après un pip install htag !

    Créer le fichier suivant, et lancer le :

        from htag import Tag
    
        class Stars(Tag.div):
            def init(self,nb):
                self["style"] = "background: #"+hex(nb*666)[2:]
                self <= "⭐" * nb
    
        class MyApp(Tag.body):
            def init(self):
    
                self.main = Tag.div()
    
                # create the layout
                self <= Tag.button("3",nb=3,_onclick=self.click )
                self <= Tag.button("4",nb=4,_onclick=self.click )
                self <= Tag.button("5",nb=5,_onclick=self.click )
                self <= Tag.button("clear",_onclick=lambda o: self.main.clear() )
                self <= self.main
    
            def click(self, o):
                self.main <= Stars(o.nb)
    
        #======================================
        from htag.runners import BrowserHTTP as Runner # need htag only !
        #~ from htag.runners import DevApp as Runner # need htag+uvicorn+starlette !
    
        app=Runner( MyApp )
        if __name__=="__main__":
            app.run()
  • # Interfaçage avec autres libs ?

    Posté par  . Évalué à 2.

    Salut,

    Est-ce que c'est possible de l'interfacer avec des libs telles que matplotlib ?

    • [^] # Re: Interfaçage avec autres libs ?

      Posté par  (site web personnel) . Évalué à 3.

      Je ne manip pas du tout matplotlib/numpy, mais il y a aucune raison que ça ne soit pas possible ;-)

      D'après ce que j'ai vu, j'ai l'impression que matplotlib est capable de restituer des ressources de type "Pil.Image" ….

      Et à l'aide d'une méthode python comme celle ci (qui converti une "Pil.Image" en dataurl):

      import io,base64
      
      def img2dataurl(img):
          data = io.BytesIO()
          img.save(data, "PNG")
          data64 = base64.b64encode(data.getvalue())
          return u'data:image/png;base64,'+data64.decode()

      Côté HTAG, tu peux créer une "image" (balise html img) comme ceci :

      self += Tag.img( _src=img2dataurl(my_pil_image) )

      Et le tour est joué ;-)

      • [^] # Re: Interfaçage avec autres libs ?

        Posté par  (site web personnel) . Évalué à 3.

        Matplotlib est aussi capable de générer du SVG et, vu les affinités de SVG avec HTML, il doit y avoir moyen d'injecter ça avec htag.

        Cyberdépendance, cyberharcèlement, pédocriminalité… : Zelbinium, pour que les smartphones soient la solution, pas le problème !

        • [^] # Re: Interfaçage avec autres libs ?

          Posté par  . Évalué à 3.

          Carrément, c'est une super idée, le rendu de Matplotlib pour le svg est très bon, et tant qu'il n'y a pas de vidéo dans un SVG, firefox et webkit s'en sortent super bien !

          • [^] # Re: Interfaçage avec autres libs ?

            Posté par  (site web personnel) . Évalué à 1.

            Voilà une version de matplotlib+htag, avec un composant TagPlot qui va bien

            # -*- coding: utf-8 -*-
            
            from htag import Tag
            import matplotlib.pyplot as plt
            import io,base64,random
            
            class TagPlot(Tag.span):
                def init(self,plt):
                    self["style"]="display:inline-block"
                    with io.StringIO() as fid:
                        plt.savefig(fid,format='svg',bbox_inches='tight')
                        self <= fid.getvalue()
            
            class App(Tag):
                def init(self):
                    self.main = Tag.div()
                    self.liste=[1, 2, 5, 4]
            
                    # create the layout
                    self <= Tag.h3("Matplotlib test" + Tag.button("Add",_onclick=self.add_random))
                    self <= self.main
            
                    self.redraw_svg()
            
                def add_random(self,o):
                    self.liste.append( random.randint(1,6))
                    self.redraw_svg()
            
                def redraw_svg(self):
                    plt.ylabel('some numbers')
                    plt.xlabel('size of the liste')
                    plt.plot(self.liste)
            
                    self.main.clear()
                    self.main <= TagPlot(plt)
            
            if __name__=="__main__":
                from htag.runners import BrowserHTTP
                BrowserHTTP(App).run()
            • [^] # Re: Interfaçage avec autres libs ?

              Posté par  . Évalué à 1.

              Une remarque : il me semble que la déclaration de l'encodage utf8 en début de source est dépréciée en Python3 (il en traine encore beaucoup dans les docs datant de Python2).

              • [^] # Re: Interfaçage avec autres libs ?

                Posté par  (site web personnel, Mastodon) . Évalué à 2.

                Ça mange pas de pain et c'est toujours utile aux éditeurs quand on échange des fichiers.

                “It is seldom that liberty of any kind is lost all at once.” ― David Hume

              • [^] # Re: Interfaçage avec autres libs ?

                Posté par  (site web personnel) . Évalué à 1.

                C'est complètement HS.

                Mais quand tu fais du python sous Windows, qui utilise un encoding particulier par défaut pour ses fichiers textes …
                Je peux garantir que ça a du sens, même sous py3.

                Sous nux, tout est utf8.

                • [^] # Re: Interfaçage avec autres libs ?

                  Posté par  (site web personnel) . Évalué à 3. Dernière modification le 18 août 2022 à 15:51.

                  En fait, tu n'as aucune contrainte sous Linux, même pas sur le système de fichiers (ce qui peut avoir des conséquences cocasses, comme avoir deux fichiers avec exactement le même nom au sens UTF-8).

        • [^] # Re: Interfaçage avec autres libs ?

          Posté par  (site web personnel) . Évalué à 1.

          Si vous avez un bout de code simple (genre 10lignes) qui génère un graphique SVG avec matplotlib … je veux bien rajouter un exemple htag qui afficherait le rendu …

          Histoire de rajouter à mes exemples htag. Car tous l'intérêt de htag, c'est de pouvoir combiner des libs python, et des libs js …

        • [^] # Re: Interfaçage avec autres libs ?

          Posté par  (site web personnel) . Évalué à 3.

          Et la même application htag, qui fonctionne complètement en client-side/html (car htag et matplotlib sont compatible pyscript)

          https://raw.githack.com/manatlan/htag/main/examples/pyscript_matplotlib.html

          C'est bluffant quand même … merci pyscript !

    • [^] # Re: Interfaçage avec autres libs ?

      Posté par  (site web personnel) . Évalué à 1.

      J'avais 5min pour me mettre à matplotlib …. voilà un exemple fonctionnel (necessite htag+matplotlib)

      # -*- coding: utf-8 -*-
      
      from htag import Tag
      import matplotlib.pyplot as plt
      import io,base64,random
      
      class App(Tag):
          def init(self):
              self.img = Tag.img()
              self.liste=[1, 2, 5, 4]
      
              # create the layout
              self <= Tag.h3("Matplotlib test") + self.img
              self <= Tag.button("Add",_onclick=self.add_random)
      
              self.redraw_svg()
      
          def add_random(self,o):
              self.liste.append( random.randint(1,6))
              self.redraw_svg()
      
          def redraw_svg(self):
              plt.plot(self.liste)
              plt.ylabel('some numbers')
      
              data=io.BytesIO()
              plt.savefig(data)
              data.seek(0)
      
              self.img["src"]=u'data:image/svg;base64,'+base64.b64encode(data.read()).decode()
      
      
      if __name__=="__main__":
          from htag.runners import BrowserHTTP
          BrowserHTTP(App).run()
      • [^] # Re: Interfaçage avec autres libs ?

        Posté par  (site web personnel) . Évalué à 3.

        Je ne connais pas Matplotlib plus que ça, mais il me semble que l'image n'est pas exportée au format SVG.

        D’après la documentation de la méthode savefig, comme il n'y a pas de paramètre format et que le format ne peut pas être déduit du paramètre fname, alors c'est le format par défaut qui est utilisé, à savoir png.

        Voici la fonction que j'utilise pour exporter un graphique Matplotlib en SVG :

        def getSVG(plt):
          figfile = StringIO()
          plt.savefig(figfile, format='svg')
        
          svg = figfile.getvalue();
        
          plt.close()
        
          return svg

        Pour voir ce code en action : https://replit.com/@Q37Info/MatplotlibSVG#main.py (ligne 42).

        Cyberdépendance, cyberharcèlement, pédocriminalité… : Zelbinium, pour que les smartphones soient la solution, pas le problème !

        • [^] # Re: Interfaçage avec autres libs ?

          Posté par  (site web personnel) . Évalué à 1.

          Fichtre, tu as raison ;-)
          je débute en matplotlib ;-)

          Voilà une version qui génère un vrai SVG, et provoque le raffraichissement de l'image avec du vrai svg ;-) (plus besoin de dataurl !)

          # -*- coding: utf-8 -*-
          
          from htag import Tag
          import io,base64,random
          import matplotlib.pyplot as plt
          
          class App(Tag):
              def init(self):
                  self.img = Tag.svg(_viewBox="0 0 1000 1500")
                  self.liste=[1, 2, 5, 4]
          
                  # create the layout
                  self <= Tag.h3("Matplotlib test" + Tag.button("Add",_onclick=self.add_random))
                  self <= self.img
          
                  self.redraw_svg()
          
              def add_random(self,o):
                  self.liste.append( random.randint(1,6))
                  self.redraw_svg()
          
              def redraw_svg(self):
                  plt.plot(self.liste)
                  plt.ylabel('some numbers')
                  plt.xlabel('size of the liste')
          
                  with io.StringIO() as fid:
                      plt.savefig(fid,format='svg')
                      self.img.set( fid.getvalue() )
          
          if __name__=="__main__":
              from htag.runners import BrowserHTTP
              BrowserHTTP(App).run()
          • [^] # Re: Interfaçage avec autres libs ?

            Posté par  (site web personnel) . Évalué à 1.

            Et comme, le principal intérêt de htag : c'est de pouvoir créer ses composants réutilisables, voilà une version avec un composant TagPlot, qui auto génère un svg à partir d'un objet matplotlib

            # -*- coding: utf-8 -*-
            
            from htag import Tag
            import io,base64,random
            import matplotlib.pyplot as plt
            
            class TagPlot(Tag.svg):
                def init(self,plt):
                    self["viewBox"]="0 0 1000 1500"
                    with io.StringIO() as fid:
                        plt.savefig(fid,format='svg')
                        self.set( fid.getvalue() )
            
            class App(Tag):
                def init(self):
                    self.main = Tag.div()
                    self.liste=[1, 2, 5, 4]
            
                    # create the layout
                    self <= Tag.h3("Matplotlib test" + Tag.button("Add",_onclick=self.add_random))
                    self <= self.main
            
                    self.redraw_svg()
            
                def add_random(self,o):
                    self.liste.append( random.randint(1,6))
                    self.redraw_svg()
            
                def redraw_svg(self):
                    plt.plot(self.liste)
                    plt.ylabel('some numbers')
                    plt.xlabel('size of the liste')
            
                    self.main.set( TagPlot(plt) )
            
            if __name__=="__main__":
                from htag.runners import BrowserHTTP
                BrowserHTTP(App).run()
  • # Kivy

    Posté par  (Mastodon) . Évalué à 4.

    ça utilise kivy/webview

    Si tu connais Kivy, n'hésite pas à aider Goffi à terminer la dépêche sur le sujet.

    Surtout, ne pas tout prendre au sérieux !

    • [^] # Re: Kivy

      Posté par  (site web personnel) . Évalué à 2.

      Je ne connais pas kivy plus que ça …
      Certes, le runner htag qui se lance sous android est basé sur kivy … mais il n'y utilise que la partie "webview" de kivy (et tout le binz autours de p4a/buildozer pour que ça build proprement) …

      D'ailleurs, j'ai réalisé une appli mobile (de démo, mais l'appli est réellement utile (un clone de tricount), avec htag evidemment, et j'ai tout déposé sur github :
      https://github.com/manatlan/TriApp (sources + apk)

      L'appli (le source python) tourne évidemment sur n'importte quel plateforme. Mais le github utilise une action/workflow pour builder l'APK (via buildozer) de manière automatique et transparente. Du coup, difficile de faire plus simple pour passer d'un code source python à un apk qui fonctionne OOTB ;-)

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.