vendredi 12 août 2016

Python Logging & Logger - CE QU'IL NE FAUT PAS RATER

Petit détour sur les méthodes de logging Python et une erreur assez répandue (je pense).

Dans le blog victorlin, j'ai trouvé l'exemple suivant à propos du logging, exemple qui recèle une erreur dans la façon d'utiliser le logger :

import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

logger.info('Start reading database')
# read database here

records = {'john': 55, 'tom': 66}
logger.debug('Records: %s', records)
logger.info('Updating records ...')
# update records here

logger.info('Finish updating records')

Il y a comme un bug!
Si ce code est parfaitement fonctionnel, il y a quelque-chose qui me choque.
  1. Il faut créer un "logger" pour pouvoir logger
  2. Ou passer le logger d'objet en objet si on ne veut pas en créer à tour de bras.
Si vous avez une grosse application, vous allez vous retrouver avec des logger dans tous les coins (classe, fichier, etc) pour enregistrer des messages... alors que tout ce qui vous intéresse c'est faire du logging d'information (info ou degug) pour les utiliser quand cela sera utile.

Découpler logging et logger
Le plus approprié, il me semble, est de faire du logging agnostique, sans se préoccuper de la question "si cela intéresse un logger ou non"! En gros, découpler le logger et les opérations de logging dans le code.
Cela se fait très simplement en écrivant les traces directement sur "logging".

import logging

logging.debug(' %r _fire_select(): %r ' % (self, selection) ) 
logging.debug('%r _fire_cancel()' % self )

Utiliser un logger pour garder une trace
Ensuite, au besoin, il suffit de créer un logger pour garder une trace des messages qui passent comme ceci:

logger = logging.getLogger()
handler = logging.StreamHandler() # Logging vers console
formatter = logging.Formatter(
    '%(asctime)s [%(levelname)s] -> %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO) # ou DEBUG 

Et toutes les opérations loggin.info, logging.debug, etc tomberons dans logger :-)
Cela va singulièrement simplifier la codage des opérations de logging dans vis classes :-)

PythonMegaWidget (Pmw) - Ressources utiles

Voici une liste de ressources collectées durant mes recherches et apprentissage sur TkInter et PythonMegaWidget.
Cette liste est destinée à être régulièrement agrémentée.
  • Comment construire des Pmw MegaWidget (pmw.sourceforge.net, anglais)
    Détaille et commente la création d'un MegaWidget. Incontournable dès que l'on veut spécialiser un composant.
  • Origine de la MultiListBox, une sorte de TableView (mypythonadventure.com, anglais)
  • SimpleTable (stackoverflow.com, anglais)
    Créer une table avec des libellés et un Grid geometry manager. Une approche simple qui peut se révéler utile.
  • Calculating the pixel size of a string with Python (stackoverflow.com, anglais)
    Ressource très intéressante pour estimer la hauteur d'un texte dans un Widget
  • Listbox.Autowidth (http://stackoverflow.com, anglais)
    Permet d'évaluer la largeur en Pixel des entrées d'une Listbox et d'ajuster la largeur du composant en conséquence. Font et taille de texte. Excellente ressource!
  • x
  • DataQ/DataTools.py (github.com/pythonadventurer, anglais)
    Un ensemble d'outils pour ouvrir et manipuler les données d'un fichier csv
  • x
  • Creating a simple Dialog (effbot.org, anglais)
    Quelques lignes de code... mais également l'utilité de destroy() et root.wait_window().
    Simple et très pertinent.
  • Tk Named Color (wiki.tcl.tk)
    Toutes les couleurs dispo dans Tk (et TkInter). Un Must!.
  • xxx

vendredi 5 août 2016

PyhtonMegaWidget (Pmw) StrictComboBox

Dans l'article "PythonMegaWidget (Pmw) ComboBox restreind à une liste de valeur" j'expliquais comment avoir configuré une combobox pour n'accepter qu'une liste finie de valeur.

Ce hack est maintenant transformé en classe StrictComboBox .

In the previous article "PythonMegaWidget (Pmw) ComboBox restreind à une liste de valeur" I did explain how to restrict the selected value to a given list of values.
That hack has been converted to the StrictComboBox  class.

class StrictComboBox( Pmw.ComboBox ):
    """ Pmw.ComboBox that strictly restrict the selection to the list of items stored in the "items" keyword
        
        Thank to RL_ScrolledText class for their wonderfull example
        http://www.reportlab.com/examples/frml/old/rlextra/src/rlextra/graphics/guiedit/guidialogs.py                
    """
    _items = None # List of combobox items

    def __init__( self, *args, **kw ):
        # Take a local copy of some property
        for a in ['items']:
            print( a )
            if a in list(kw.keys()):
                print( ' --removed')
                v=kw[a]
                del kw[a]
            else:
                v=None
            setattr(self,a,v)

        # add the items to combobox + validator
        kw['scrolledlist_items'] = self.items
        kw['entryfield_extravalidators'] = { 'strictselect' : (self.strictselect_validate, self.strictselect_stringtovalue) }
        kw['entryfield_validate'] = { 'validator' : 'strictselect' }

        Pmw.ComboBox.__init__( *(self,)+args, **kw )
        w = self._entryfield = self.component('entryfield')

        self.initialiseoptions(StrictComboBox)
    
    def destroy(self):
        Pmw.ComboBox.destroy( self )
        

    def strictselect_validate(self, avalue):
        """ called by ComboBox entry field to validate the keyIn value """
        print( 'validate: %s' % avalue )
        if avalue in self.items:
            return Pmw.OK
        elif any( [avalue in v for v in self.items] ):
            return Pmw.PARTIAL
        else:
            return Pmw.ERROR

    def strictselect_stringtovalue(self, avalue):
        """ Not called since we did not defined min and max values """
        return None

Qui s'utilise comme ceci:
And you can use it like that:

    # StrictComboBox
    f = Frame( fbody  )
    l = Label( f, text="Prefered Parent", anchor=W, width=15 ).pack( side=LEFT, expand=N )
    combobox4 = StrictComboBox( f, items=['Dady','Mom','Sister','Grandpa'],
                        labelpos='wn', listbox_width=24, dropdown=1, # selectioncommand=choseEntry,
                        entry_state = 'disabled', entry_bg='white', entry_fg='black' )
    combobox4.pack( side=LEFT, expand=N )
    fields.append( (combobox4, None) ) # the Field and the string variable
    f.pack( side=TOP, expand=N, fill=X )

PythonMegaWidget (Pmw) ComboBox restreind à une liste de valeur

Bonjour a tous, j'essaye de restreindre la sélection de valeur d'un Pmw.ComboBox aux seules valeurs disponibles dans la liste. Voici comment j'ai utilisé extravalidators pour y arriver.
Code en beta mais fonctionnant correctement

Hello there, I'm trying to restrict the selected value of a Pmw.Combox to the only possible values in the list. Here how I did use the extravalidators to reach that goal.
Roughly beta code but worked fine

from tkinter import *
import Pmw

root = Tk()
root.option_readfile('optionDB')

...
...

    combovalues = ['v1','v2','v3'] 
    f = Frame( root  )
    l = Label( f, text="Combo", anchor=W, width=15 ).pack( side=LEFT, expand=N )
    def strictselect_validate(avalue):
        print( 'validate: %s' % avalue )
        if avalue in combovalues:
            return Pmw.OK
        elif any( [avalue in v for v in combovalues] ):
            return Pmw.PARTIAL
        else:
            return Pmw.ERROR
    def strictselect_stringtovalue(avalue): # Not used in this case
        print( 'stringtovalue: %s' % avalue )
        return None
    combobox2 = Pmw.ComboBox( f, labelpos='wn', listbox_width=24, dropdown=1, 
                        entry_bg='white', entry_fg='black',
                        scrolledlist_items=combovalues,
                        entryfield_extravalidators = { 'strictselect' : (strictselect_validate, strictselect_stringtovalue) }, 
                        entryfield_validate = { 'validator' : 'strictselect' } )
    combobox2.pack( side=LEFT, expand=N )
    fields.append( (combobox2, None) ) # the Field and the string variable
    f.pack( side=TOP, expand=N, fill=X )


Le truc: le Pmw.ComboBox utilise un Pmw.EntryField, il est donc possible d'utiliser les fonctionnalités de Pmw.EntryField

The Tip: the Pmw.ComboBox use a Pmw.EntryField, it is then possible to use the features of Pmw.EntryField

jeudi 4 août 2016

Python: faire un breakpoint en mode interactif pour faire du débogage

Dans les IDE évolués nous disposons généralement d'une fonctionnalité "Breakpoint" pour arrêter le programme et pouvoir inspecter le contenu des variables locales.
Il est possible de réaliser quelque-chose de similaire en mode console (python interactif) qui fonctionne aussi bien sous Windows que sous Linux. Très utile si l'on se trouve sur un environnement sans facilité.

Je bosse sur un projet TkInter et j'aurais trouvé intéressant de pouvoir inspecter le contenu d'un événement en mode interactif... bref, sauter dans un débogueur mais "sans débogueur" à disposition.

J'ai trouvé ce petit bout de code qui fonctionne à merveille et qui lance un python interactif là où j'en ai besoin.
La réponse se trouve dans code.interact()
 
>>> import code
>>> def foo():
...   a = 10
...   code.interact(local=locals())
...   return a
...
>>> foo()
Python 2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> a
10

  • Presser Ctrl+Z pour terminer la session interactive. 
  • Il n'est pas possible de modifier les variables locales (cela n'a pas d'implication sur le code appelant). 
  • Vous pouvez lister inspecter les variables locales en utilisant dir() 
Voici un autre exemple tel qu'utilisé depuis un code tkInter.
N'oubliez pas de démarrez votre programme avec python.exe et non pythonw.exe pour disposer d'une console ;-)

import code
...
...
def focus_next_window(event):
    """ Move focus to next item, Bind it for  """
    print( type(event.widget) )
    code.interact(local=locals())
    event.widget.tk_focusNext().focus()
    return("break")

Souce: http://stackoverflow.com/questions/13432717/enter-interactive-mode-in-python