dimanche 20 décembre 2015

Python: PrettyTable pour afficher une table en mode texte

Petit outil à se garder sous le coude pour se simplifier l'écriture d'outil en mode texte (dans la console).
Il n'est pas toujours utile de se prendre la tête...



from prettytable import PrettyTable

x = PrettyTable(["City name", "Area", "Population", "Annual Rainfall"])
x.align["City name"] = "l" # Left align city names
x.padding_width = 1 # One space between column edges and contents (default)
x.add_row(["Adelaide",1295, 1158259, 600.5])
x.add_row(["Brisbane",5905, 1857594, 1146.4])
x.add_row(["Darwin", 112, 120900, 1714.7])
x.add_row(["Hobart", 1357, 205556, 619.5])
x.add_row(["Sydney", 2058, 4336374, 1214.8])
x.add_row(["Melbourne", 1566, 3806092, 646.9])
x.add_row(["Perth", 5386, 1554769, 869.4])
print x

Ressources

lundi 14 décembre 2015

RpnCalc: Install failure + Doc failure = projet d'exception qui tombe dans l'oubli

Pré-préambule:
Coup de gueule! Il faut développer les idées jusqu'au bout! Installation (ou doc d'installation) comprise!

Préambule
Une calculatrice RPN en Python, est-ce possible?

J'ai une calculatrice HP48 que j'utilise énormément, et de façon plus ou moins avancé, au jour le jour. Ce qui m'ennuie, c'est de passer mon temps à la sortie et la rentrer dans mon tiroir (je manque cruellement de place sur mon bureau).

Du coup, je me suis dit: mais pourquoi ne pas garder une calculatrice sur ton bureau Linux Mint? J'ai deux moniteurs... ce serait certainement très pratique.

Je fouille donc sur les dépôts et trouve un outil offrant des fonctionnalités plutôt avancé sous la forme de CalcRpnPy et d'un dépôt Python rpncalc 2.7.

La doc est détaillée, cela fonctionne depuis un interpréteur Python. bref, de la balle pour qui préfère utiliser un clavier pour des questions de performance humaine!
Faut dire que je suis addict à Linux Mint (donc de la chaîne de parenté Mint - Ubuntu - Debian).

Mon premier reflexe est: Génial, je vais pouvoir l'installer avec un pip install ;-)

Sauf que...
rpncalc s'appuie sur clnum (CLN ou encore Class Library for Number) et que l'installation part totalement en couille.
L'installation de clnum en python 2.7 (pip install clnum) inclus un problème d'encoding ASCII!!!

Et en python3, je me retrouve avec une belle erreur de compilation GCC.

En fouillant, j'arrive jusqu'au support et night-build des paquet Debian. 

Conclusion
Je voulais gagner du temps avec un outil sympa et je me retrouve à zoner dans de la doc, des problème d'install & compilation et même des paquets Debian!

Comme je suis à la bourre, je vais ressortir ma calculette Hp48 et rpncalc à toutes les chances de tomber dans l'oubli.

Vous ne trouvez pas cela dommage? Il manque presque rien pour l'exploiter... mais ce "presque rien" est le frein final qui va stopper net la découverte du projet rpncalc.

Moralité
Ne vous arrêtez pas à un mètre de l'arrivé.
C'est trop bête de ne pas faire profiter les autres de votre victoire!

fwbackups - le programme de backup equivalent à "Cobian Backup" pour Linux

Cela fait maintenant des années que j'utilise Cobian Backups sous Windows (Windows, c'est pour travail).

Mais je suis plutôt addict Linux à la maison. Cela faisait longtemps que je cherchais un système de backup plutôt proche des fonctionnalités de "Cobian Backup", simple et efficace.
Je l'ai trouvé, il s'appelle fwbackups et fonctionne en python (léger et très largement supporté par les plateforme Linux).

fwbackups de Diffingo

Utilisée depuis plusieurs mois maintenant, je me prépare à en faire le déploiement. Pourquoi ne pas faire la publicité d'une bonne solution.


Pour démarrer le programme de backup, il suffit de saisir la commande suivante sur une ligne de commande:

fwbackups

Téléchargement:
Installation:

Note:
Par le passé, j'avais trouvé "Areca Backup" vraiment génial mais ce dernier nécessitait l'installation de Java... pas trop a mon goût pour être tout à fait honnête. Entre Java et Python mon coeur balance franchement vers Python.
Cela ne signifie pas qu'une solution Java ne soit pas une bonne solution, loin de là, c'est juste une question de préférence personnel.


Voir aussi:

mercredi 11 novembre 2015

Electrabel - Une démarche spontanée par e-mail potentiellement dangereuse pour l'identité du client

Bonjour à tous et toutes,

Un blog n'est généralement pas le meilleur endroit pour se laisser aller à des réactions sur le vif... mais ce jour je suis choqué car j'ai vu des informations sensible être communiqué par e-mail (certes qui m'est adressée) par la société Electrabel elle-même.

Nous le savons tous, malgré de nombreux efforts de la part des prestataires, les boîtes e-mails sont régulièrement l'objet de tentative de piratage... ainsi il est préférable de ne pas y laisser trainer d'information trop personnelle. Mon beau-père s'est déjà fait avoir une fois où deux (je l'ai su car j'ai reçu des é-mails bizarres de sa part)... nous avons tous connu cela... cela pourrait aussi nous arriver.

Perso, je ne laisse pas d'information sensible, il serait bien que mes prestataires de service --dont Electrabel fait l'objet-- ne se permette pas d'en publier à ma place sur mon propre e-mail!

La quantité d'information que j'y ai trouvé dans deux e-mails est suffisant pour élaborer une tentative d'usurpation d'identité (j'y reviens plus loin). Eventuellement se faire envoyer une copie de facture (voire même savoir où l'on peut la récuperer, l'info est dans l'émail!) pour ensuite l'utiliser afin de s'ouvrir l'accès à d'autres services chez d'autres prestataires et facturé chez vous (bien entendu, sinon ce ne serait pas marrant).
PS: Il n'est pas rare qu'il soit demandé une copie de facture d'électricité et/ou de gaz pour prouver votre identité et le lieu de résidence. Ce qui rend encore plus grave la récente démarche e-mail opéré par Electrabel.

Heureusement il n'y a ni mon numero national, ni ma date de naissance (ouf!)... sinon tout le reste se trouve dans deux e-mail envoyé en moins de 30 jours.

L'adresse pub
Il faut savoir que nous avons plusieurs adresses e-mail... dont une qui se termine par ".pub@gmail.com".
Cette adresse est destinée aux sites avec un faible niveau de confiance ou à toute société se montrant un peu entreprenante en vue d'obtenir une adresse e-mail.
Inutile de dire que cette boite est remplie de spam et messages non désirés.
Seul les prestataires de confiance (n'envoyant pas d'émail non sollicité) reçoivent notre vraie adresse e-mail au terme d'une période de validation relativement longue.

Electrabel dispose de notre adresse ".pub@gmail.com". Electrabel était encore en période de validation et que l'email à été communiquée suite à démarche de type "commerciale". Ont ne sais jamais avec qui l'adresse pourrait être partagée et ce qu'il pourraient en faire. Cela doit remonter à un temps certains... nous ne nous en souvenions plus.

Un premier email d'Electrabel
Il y a un moment, nous avons reçu un e-mail réputé d'Electrabel mentionnant "Important - vos prochaines factures via e-mail". Cet e-mail nous demande de nous connecter sur le compte en ligne pour confirmer nos informations en vue de facturation électronique.
Ce mail contient un numéro de client et un code d'activation.
N'AYANT:
1) RIEN DEMANDé auprès d'Electrabel et
2) le lien proposé RENVOYANT VERS LE SITE P5TRC.EMV2.COM

Notre premier réflexe est de penser à une tentative d’hameçonnage (phishing) en vue d'une collecte d'information pour usurpation d'identité.
Il ne s'agissait visiblement pas "POUR SURE" d'Electrabel... le mail est casé dans les SPAM et déclaré comme tel. En cas de SPAM, la première chose à faire est "de ne rien faire" et ne surtout pas signaler notre intérêt/existence au spammeur.

Il y a au moins 1 belge sur 2 raccordé chez Electrabel. Une tentative de Physhing/Hameçonnage à donc toutes les chance d'aboutir avec un certain succès.

Un deuxième E-Mail d'Electrabel
Ce jour, nous recevons un deuxième e-mail "Votre facture intermédiaire du 10 Novembre 2015".
Les liens pointes toujours vers P5TRC.EMV2.COM (donc toujours pas vers Electrabel.be!!! ni de connexion sécurisé via https).
Ni une, ni deux nous déclarons egalement ce nouvel émail comme SPAM!
Par contre nous restons interpellé par la présence de l'adresse de notre demeure dans l'émail.

Cette fois, nous trouvons:
* Notre numéro client
* Le montant d'une facture
* Une date d émission de facture
* notre adresse complète (reste plus qu'a faire appel à un annuaire pour connaitre le nom correspondant à l'adresse ou être perspicace avec l'adresse e-mail nom.prenom@blabla.com!)

Après vérification, nous avons constaté que toutes les informations communiquées sont "correctes" et communiquées par l'intermédiaire d'un média très discutable.
Les liens ne redirige pas vers le nom de domaine d'Electrabel (cela pourrait aussi bien être un site Chinois, vous n'imaginez pas le nombre d'email de ce genre reçu chaque jour) et les liens ne sont toujours pas sous HTTPS.
Il n'y a là aucun des indices permettant d'augmenter le niveau de confiance.

Electrabel - transmission non sécurisée de mes informations
Je ne doute pas un seul instant que des informaticiens consciencieux de chez Electrabel aient posés les bonnes questions concernant la communication d'informations sensibles via e-mail.
D'autant que tous les intermédiaires informatiques peuvent en prendre librement connaissance a notre insu (super pour les campagnes marketing, ou le réseau échelon).

Cher Electrabel:
  • Il faut être fou d'avoir communiqué un numéro de client et le code d'activation dans un seul e-mail!
    Quiconque peuvant accéder à cet information pourrait avoir des informations utiles pour usurper votre identité et/ou pour manipuler le helpdesk d'Electrabel! Se faire craquer sa boite e-mail n'est pas exceptionnel, les personnes plus agées restant les plus influencables/manipulables.
  • Vous demandez de vous connecter sur des liens pour y traiter des informations "à caractère financier" (mes factures) sans passer par le nom de domaine "Electrabel.be"!!!
  • Ont ne propose JAMAIS DE LIEN à suivre s'il y a un enjeux financier!!!! Faites comme les banques les gars!!!
  • Ne jamais inclure d'information sensible dans un e-mail!!!
    Mon numéro de client, mon code d'activation, mon adresse, mon montant facturé, la date d'émission de facture (et la communication qui va avec) par e-mail!!!
    Faut arrêter de rigoler les gars.
  • Sans compter que tout cette procédure est entamée sans mon consentement explicite... je suis sous le coup d'un accord par défaut d'opposition sur un premier e-mail ne présentant AUCUN indice de confiance! 
  • Vous m'excuserez mais P5TRC.EMV2.COM ce n'est pas Electrabel... sur ce plan c'est vraiment de l’amateurisme (je suis du métier).
    Si un email vous envoyait un lien pour vous connecter sur P5XCC.INV3.COM au lieu de GMAIL.com, vous n'iriez probablement pas faire une telle bêtise.... et bien, cela devrait être pareil pour Electrabel. 

C'est quoi le danger?
Les chevaux de Troie sont légion! Avec eux, personne ne peut savoir si quelqu'un d'autre à pris connaissance de ces mêmes infos sur votre e-mail (ou votre smartphone) à votre insu.
Au moins, avec une facture papier cette dernière est "soit ouverte, soit disparue"... il y a une trace palpable du délit!

Dans le cas de ce billet, et si la sécurité de votre email est compromise, il y a suffisamment d'informations pouvant aider un tiers à se faire passer pour vous... et probablement de pouvoir permettre d'abuser le Helpdesk Electrabel (je laisse ce jeu là a des journalistes, mais reste convaincu par le bien fondé de mon propos).
Avec Internet, une identité est devenue précieuse et fragile. Une fois utilisée par quelqu'un de malveillant, cela peut vous causer énormément de tords et de tracas (souvent financier).
Vous devez rester vigilent et ne pas autoriser qui que ce soit à communiquer par moyen électronique (l'email n'étant pas le plus sécurisé) des informations à votre propos que vous ne communiqueriez pas vous même!

Réagissez
Je reste atterré par un tel égarement alors qu'il suffit de "juste un peu de bon sens" pour voir ce qui saute littéralement aux yeux.

Moi je réagis et je marque ces e-mail comme "SPAM" auprès de GMail.
Rien ne me permet de savoir, pour sur, que l'émail que je recevrais demain sera effectivement émanant d' Electrabel ou quelqu'un qui voulant se fait passer pour cette société.

Soyez prudent et protégez le Net en marquant activement toute communication qui pourrait conduire à une imprudence future! (comme le cas présent)

Chers Electrabel, j'espère que vous ferez rapidement le nécessaire pour redresser la barre.
Vous me faites peur là!

jeudi 29 octobre 2015

Liste des processus - commande ps

la commande ps -ef permet de lister facilement les processus.... mais ce qui serait parfois utile de savoir, c'est l'état du processus.
Pour cela, nous avons la commande ps -aux  qui fourni un résultat similaire à ceci.

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   4464  2512 ?        Ss   sep17   0:02 /sbin/init
root         2  0.0  0.0      0     0 ?        S    sep17   0:00 [kthreadd]
root         3  0.0  0.0      0     0 ?        S    sep17   4:49 [ksoftirqd/0]

yyy
La colonne STAT nous indique le statut du processus dont voici les principaux
  • D - Uninterruptible sleep - En sommeil et ne pouvant pas être arrêté (généralement en attente d' IO)
  • R - Running - Exécuté ou processus exécutable (dans la queue d'exécution)
  • S - Sleep - en sommeil et pouvant être interrompu (en attente d'événement)
  • T - Arrêté, soit suite à u signal de contrôle de job/processus ou parce qu'il est en cours de débogage (traced).
  • X - processus mort (dead). Nous ne devrions jamais voir ce statut
  • Z - Zombie, processus terminé et non repris en charge (collecté/libéré) par son processus parent.

dimanche 11 octobre 2015

Activer Google comme moteur de recherche par Defaut dans Firefox

Nous sommes quand même nombreux à vouloir utiliser Google comme moteur de recherche dans Firefox.
Comme c'est un moteur commercial, il n'est pas installé par défaut... et la recherche sur le net est un peu fastidieuse.

Voici donc comment faire:
1) Télécharger le greffons/plug-in "Google Search" sur le site de mozilla.

Cliquer sur le lien suivant et installer le plug-in

https://addons.mozilla.org/en-US/firefox/addon/google-default/

2) Ensuite, allez cliquer sur l'icone en forme de loupe dans la barre de recherche.

Cela affiche un menu et tout en bas, vous pouvez voir "paramètres de recherche".
Cliquez sur le point de menu "Paramètres de recherche"

3) Dans la fenêtre des paramètres de recherche, sélectionnez simplement le moteur de votre choix.... dans notre cas "Google"


Voili, voilou,

mardi 6 octobre 2015

Inférence entre dictionnaire et paramètres nommés

Cas pratique
J'ai un cas pratique où je veux recharger des données depuis un fichier json (une fois chargé, j'ai une liste de dictionnaire) PUIS les injecter dans une DB à l'aide de sqlAlchemy.
In [57]: d[0]
Out[57]: 
{'actif': 'Y',
 'libe': 'Le carbone lorraine',
 'name': '0200',
 'pnet': 50.0,
 'sync_id': 1.0}

In [58]: d[1]
Out[58]: 
{'actif': 'Y',
 'libe': 'BUSELURES',
 'name': '0201',
 'pnet': 50.0,
 'sync_id': 2.0}

Dans sqlAlchemy, j'ai définit un modèle pour mes tables.
En utilisant ce modèle (ici family_table), l'insertion est relativement simple:

q = family_table.insert()
q.execute( actif='Y', libe= 'Le carbone lorraine', name= '0200', pnet= 50.0, sync_id= 1.0 )

Mais ce qui serait vraiment cool, c'est appeler execute() directement avec le dictionnaire d[0], d[1], ...et se passer de la tache fastidieuse décrite ci-dessus. Comme sqlAlchemy utilise **kwargs, nous pouvons faire de l'inférence :-)

q = family_table.insert()
dic = d[0] # récuperer un dictionnaire
# La ligne suivante dit que dic doit être passé directement
# en tant que paramètre **kwargs de la fonction 
q.execute( **dic ) 

**KWargs - comment ça marche?
Définissons la fonction printkw() pour afficher les valeurs disponibles dans le paramètre **kwargs.
En fait, kwargs est un dictionnaire qui contient les paramètres nommés sous forme clé = valeur. Comme kwargs n'est pas un mot clé (mais une convention de nommage), on utilise le signe ** pour identifier kwargs (et ses propriétés extraordinaires) dans la déclaration de la fonction printkw()
Vous pourriez fort bien appeler ce paramètre **dico_des_parametre_nommes , mais il est préférable de respecter les conventions.

def printkw( **kwargs ):
   for k,v in kwargs.items():
      print( '%s = %r' % (k,v) )

Maintenant, si nous appelons la fonction printkw() avec des paramètres nommés, nous avons:

>>>printkw( name='hello', value=12, value2='test me' )
value2 = 'test me'
name = 'hello'
value = 12

Dans notre fonction printkw() nous pourrions facilement récupérer une valeur donnée avec l'instruction le_nom = kwargs['name']
Cela nous fournirait la valeur du paramètre "name".

Mais le plus intéressant, selon moi, réside au passage des paramètres par l'intermédiaire d'un dictionnaire. Il devient alors très facile de:
  • Passer de nombreuses valeurs d'une fonction à l'autre
  • De présenter une interface très flexible (surtout avec des DB) pour transmettre de nombreuses données
  • De faire une persistance des paramètres nommés dans une DB,Fichier, Json, service en ligne, etc
>>> d = { 'name':'hello', 'value':12, 'value2':'test me' }
>>> printkw( **d )
value2 = 'test me'
name = 'hello'
value = 12

La première ligne crée un dictionnaire avec les paramètre nommés (qui auraient pu provenir d'un fichier json comme dans l'intro de l'article).
La deuxième ligne appel printkw() en passant le dictionnaire d en tant que paramètres nommés (c'est à cela que sert ** )

Recharger un fichier json
Juste pour le fun, voici comment j'ai rechargé le fichier json dans la liste d.

import json
# retourne le nom de fichier pour les données famille
#    'c:\\python\\catflask-alpha\\toolbox\\family.json'
filename = json_filename( 'family' )
with open( filename ) as json_data:
    d = json.load( json_data )
    json_data.close()

vendredi 18 septembre 2015

Inspection des propriétés - petite technique pratique

Voici une petite technique intéressante que vous pouvez utiliser dans un python Interactif (comme IEP par exemple).

Cela fonctionne avec tous les objets... mais dans mon cas, c'est avec un module que j'ai utilisé cette technique. Dans l'article précédent, je voulais recharger le module model.py ... mais je voulais aussi m'assurer que mes objets user_table, group_table, permission_table, etc étaient maintenant disponible!

Les petits trucs à retenir de cet article seront
  [ attr for attr in dir(m) if '_table' in attr]
  [ '%s : %s' % (attr.ljust(25), getattr(m,attr)) for attr in dir(m) if '_table' in attr ]

Mais pour les plus curieux, je vous propose de lire les différentes étapes.

1) Récupérer la référence vers module
import sys
m = sys.modules['model']

2) Faire un dir() --> inefficace
Faire un dir() sur un module (ou un objet) peut retourner un contenu fastidieux à parcourir suivant la taille de l'objet. Dans le cas ci-présent, il n'y a pas moins de 193 entrées! Gloups!

dir( m )
Out[67]: 
['AliasOption',
 'AttributeExtension',
 'BIGINT',
 'BINARY',
 'BLOB',
 ...
 'with_parent',
 'with_polymorphic']

In [68]: len( dir(m) )
Out[68]: 193

3) Utilisons une compréhension list
Avec une simple compréhension liste basé sur dir(), nous allons pouvoir filtrer le contenu retourné à l'aide de l'instruction if.
Comme dir() liste les attribut, il suffit de tester si la chaine de caractère '_table' apparaît dans le nom de l'attribut.
C'est magique :-)

[ attr for attr in dir(m) if '_table' in attr]
Out[69]: 
['group_permission_table',
 'group_table',
 'permission_table',
 'user_group_table',
 'user_table']

4) Avec les valeurs svp
Avoir les noms des attributs c'est bien, avec leur valeur c'est encore mieux!
Cette fois-ci, nous utilisons getattr() pour obtenir la valeur et nous justifions avec ljust() pour le confort de lecture.

In [72]: [ '%s : %s' % (attr.ljust(25), getattr(m,attr)) for attr in dir(m) if '_table' in attr ]
Out[72]: 
['group_permission_table    : tf_group_permission',
 'group_table               : tf_group',
 'permission_table          : tf_permission',
 'user_group_table          : tf_user_group',
 'user_table                : tf_user']

Attention, c'est uniquement pour faire de l'investigation en live!!!

5) Avec le représentation des valeurs
Si vous jouez avec python depuis un moment, vous avez certainement déjà rencontré la fonction __repr()__. Cette dernière est utilisée par l'interpréteur pour afficher plus de détails  sur les objets (et pas uniquement leur adresse).
Si cela est pratique, cela peut aussi fournir du contenu très... touffu selon les circonstances.

Cette version du script remplace uniquement un des %s par %r (pour demander l'affichage de la "représentation").

In [73]: [ '%s : %r' % (attr.ljust(25), getattr(m,attr)) for attr in dir(m) if '_table' in attr ]
Out[73]: 
["group_permission_table    : Table('tf_group_permission', MetaData(bind=Engine(postgresql://dom:***@hp-minty/dom)), Column('id_permission', Integer(), ForeignKey('tf_permission.id'), table=), Column('id_group', Integer(), ForeignKey('tf_group.id'), table=), schema=None)",
 "group_table               : Table('tf_group', MetaData(bind=Engine(postgresql://dom:***@hp-minty/dom)), Column('id', Integer(), table=, primary_key=True, nullable=False), Column('group_name', Unicode(length=16), table=, nullable=False), schema=None)",
 "permission_table          : Table('tf_permission', MetaData(bind=Engine(postgresql://dom:***@hp-minty/dom)), Column('id', Integer(), table=, primary_key=True, nullable=False), Column('permission_name', Unicode(length=16), table=, nullable=False), schema=None)",
 "user_group_table          : Table('tf_user_group', MetaData(bind=Engine(postgresql://dom:***@hp-minty/dom)), Column('user_id', Integer(), ForeignKey('tf_user.id'), table=, primary_key=True, nullable=False), Column('group_id', Integer(), ForeignKey('tf_group.id'), table=, primary_key=True, nullable=False), schema=None)",
 "user_table                : Table('tf_user', MetaData(bind=Engine(postgresql://dom:***@hp-minty/dom)), Column('id', Integer(), table=, primary_key=True, nullable=False), Column('user_name', Unicode(length=16), table=, nullable=False), Column('password', Unicode(length=40), table=, nullable=False), Column('display_name', Unicode(length=255), table=, default=ColumnDefault('')), Column('created', DateTime(), table=, default=ColumnDefault(. at 0x04560B70>)), schema=None)"]
Comme vous pouvez le constaté, c'est nettement plus chargé... et avec le retour à la ligne, c'est franchement illisible)
Comme quoi "le mieux est parfois l'ennemi du bien".

Voila, Happy Python Hacking ;-)

Recharger un module Python à la volée

Il y a peu, je vous parlais d'IEP dans ce billet "IEP: un éditeur Python interactif convivial (Windows, Mac, Linux)".

Environnement bien pratique pour faire des tests et je découvre justement SqlAlchemy.
J'étais justement entrain de coder un petit module de test model.py lorsque je me suis demandé comment recharger mon module!
IEP - édition d'un module "model.py"

J'avais déjà fait un import model ... mais ayant modifié le code, je voulais savoir comment le recharger.
Voici donc comment faire...

Option 1 - à la dure
import sys
del( sys.modules['model'] ) # virer le module "model.py"
from model import *         # ré-importer le module "model.py"

Option 2 - importlib.reload()
import importlib
import sys
a = sys.modules['model'] # Référence vers le module
importlib.reload( a )    # Demander à Python de recharge le module

jeudi 17 septembre 2015

Entête pour fichier Python

Voici une proposition d'entête de fichier Python à partir d'information collectées ici et là.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Foobar.py: Description of what foobar does."""

__author__ = "Rob xxxx, Gavin xxxx, and Peter xxxx"
__copyright__ = "Copyright 2007, The xxxx Project"
__credits__ = ["Rob xxx", "Peter xxx", "Gavin xxxx",
                    "Matthew xxxx"]
__license__ = "GPL"
__version__ = "1.0.1"
__maintainer__ = "Rob xxx"
__email__ = "rob@spot.xxx.edu"
__status__ = "Production"


from sqlalchemy import *
from sqlalchemy.orm import *
from datetime import datetime
....

vendredi 4 septembre 2015

IEP: un éditeur Python interactif convivial (Windows, Mac, Linux)

IEP est un outil vraiment très pratique et agréable permettant de faire du prototypage en Python dans un milieu ergonomique.
Nous ne parlons pas de développement (qui est un autre domaine) mais de Python Interactif (et même IPython).
IEP - Un python interactif vraiment convivial.
J'aime employer Python en mode interactif... c'est vraiment pratique pour faire du prototypage ou essayer des nouvelles fonctions. Il y a aussi son grand frère IPython que je ne connais pas encore très bien.

Limite de Python Interactif en mode console
Par contre, si ces environnements consoles sont plus que pratique du fait de leur mode console, ils deviennent également pénibles lorsqu'il faut faire un "fix" dans une fonction mal définie.
Il faut recommencer la saisie ligne a ligne... ce n'est pas gênant si c'est une fois mais autrement plus ennuyant en usage intensif.

Je voulais charger dynamiquement le contenu d'une table postgresql dans un objet SqlAlchemy (via l'ORM)... puis sauver les objets dans un fichier JSON. Le but est d'exporter les données sur un serveur FTP pour rechargement dans une DB en ligne. Alors oui, vu le nombre de manipulation, j'étais un peu à l'étroit dans la version console.

IEP... KASEKO?!?

J'ai donc cherché un environnement Python Interactif plus convivial - aussi sous Windows - et je suis tombé sur Interactive Editor for Python (IEP)

C'est toujours un Python interactif (IPython) auquel est associé une gestion de fichier, quelques outils, un logger et des fonctionnalités vraiment efficaces.
Une combinaison idéale pour une utilisation en mode Interactif:
  • Exécution du texte sélectionné à la volée (dans l'interpréteur)
  • Exécution du script ou du contenu du fichier
  • Explorateur de fichier
  • Explorateur de classe
  • Auto complétion
  • Python Interactif fonctionnant dans un thread séparé
    Autorise donc la poursuite du coding pendant une phase d'éxécution.
  • Excellent support de raccourcis clavier!
    Capital pour allez vite.
  • etc
L'installer Windows ne fait que 10Mb... tout à fait raisonnable pour les services rendus.

Ressources

samedi 29 août 2015

Pillow - le fork de PIL

PIL Python Image Library est une puissante bibliothèque de traitement d'image.

PIL est mondialement reconnu pour sa puissance... mais aussi tristement connu pour être parfois vraiment pénible à installer.

En travaillant sur la documentation du Sense Hat pour Raspberry-Pi, j'ai découvert que la fondation Pi proposait d'installation Pillow en même temps que la bibliothèque du Sense-Hat.
Piqué à vif, j'ai jeté un petit coup d'oeil sur Pillow.

Pillow? Kaseko?!?!
Pillow est un Fork de PIL, créé par Alex Clark and Contributeurs. PIL est la bibliothèque Python Imaging Library par Fredrik Lundh et Contributeurs.


Pillow Logo
source: python-pillow.github.io

Si la fondation Pi propose d'installer Pillow plutôt de Pil c'est probablement parce que Pillow peut s'installer via l'utilitaire pip et que la bibliotèque est suffisamment légère que pour pouvoir tourner sur un Pi.
Cela n'enlève pourtant rien à l'intérêt de Pillow (cfr la doc).

De la doc
Autre point crucial, c'est la documentation... une belle doc comme je les aiment bien.
Installer Pillow

sudo pip-3.2 install pillow

Plus d'information

mercredi 26 août 2015

Bibliothèque & outils pour Python

Python-Editor
En m'intéressant à Alembic et SqlAlchemy, je suis tombé sur un Package portant le nom de Pyhton-Editor.

Et bien, que voila un petit outil intéressant... Python-Editor ouvre un éditeur (par programmation) et se débrouille pour capturer le résultat.
Outil simple mais efficace, il permet surtout de s'affranchir des problèmes liée à la plateforme utilisée (Windows, Mac, Linux) :-)

https://pypi.python.org/pypi/python-editor/0.3

jeudi 6 août 2015

Faire le backup d'une seule table en PostgreSql

Préambule
je sais... il est toujours mieux de faire un backup de la DB dans son entièreté. C'est d'ailleurs ce que je recommande en général, cependant, il y a toujours des cas particulier.

Introduction
Voila, j'ai fait plusieurs update foireux dans la table des  fournisseurs sur mon environnement de développement. J'aurais bien besoin de restaurer les données de cette table depuis le serveur de production... mais je ne voudrais pas restaurer toute la DB maintenant.

La solution
  • Faire un backup de la table depuis le serveur avec pg_dump --> fichier de backup sql
  • Renommer la table sur le serveur local (vaut mieux pas l'effacer trop vite)
  • Restaurer le fichier de backup sur la db locale avec l'outil psql
Backup de la table

pg_dump.exe --host adresse_du_serveur --port 5432 --username nom_utilisateur --format plain --ignore-version --verbose --file "nom_du_fichier_de_backup_sql" --table public._nom_de_la_table base_de_donnee

Exemple pratique 
pg_dump.exe --host 192.168.1.205 --port 5432 --username postgres --format plain --ignore-version --verbose --file "C:\temp\supplier.backup" --table public.file0020 stockprod

Restaurer les  données
N'oubliez pas renommer la table (vous pouvez le faire avec pgAdmin III , via les propriétés de la table)

psql -h adresse_serveur_local -p 5432 -U nom_utilisateur -d base_de_donnée -a -f nom_du_fichier_de_backup_sql

Exemple pratique

cd c:\temp\
psql -h localhost -p 5432 -U postgres -d stockdev -a -f suppliers.bakup

dimanche 2 août 2015

Dans quels répertoires se trouvent mes modules Python?

Lorsque vous faites un "pip install ..." vous téléchargez et installez des modules python.

Mais où sont-ils donc installés?

Et bien, la réponse se trouve dans Python lui-même :-)
Saisissez les deux lignes de code suivantes dans un Python Interactif et vous aurez votre réponse. Génial, la réponse est indépendante de l'OS!

>>> import site
>>> site.getsitepackages()

Ce qui dans mon cas produit la réponse suivante:

['/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages']

vendredi 31 juillet 2015

NPyScreen: interface en mode texte (console) pour Python

Dans l'article "urwid: interface en mode texte (console) pour Python" je regrettais surtout la lourdeur du Framework et la difficulté d'utilisation de celui-ci.

Je me penche donc sur une autre alternative "NPyScreen" en espérant que celle-ci ne soit pas tout aussi inapprochable qu'Urwid.

Basé sur nCurse, nPyScreen permet de réaliser des interfaces au look rudimentaires mais efficace.
NB: Depuis, je sais qu'il y a des thèmes et j'ai vraiment apprécié le temps (et investissement) très court pour la prise en main. I <3 nPyScreen.


Il faut avouer que le code correspondant est assez explicite.
Pour ma part, je préfère un code un peu plus long mais plus explicite.
La lisibilité (et le sens des instruction) est limpide, c'est déjà une bonne choses.

import npyscreen
class TestApp(npyscreen.NPSApp):
    def main(self):
        # These lines create the form and populate it with widgets.
        # A fairly complex screen in only 8 or so lines of code - a line for each control.
        F  = npyscreen.Form(name = "Welcome to Npyscreen",)
        t  = F.add(npyscreen.TitleText, name = "Text:",)
        fn = F.add(npyscreen.TitleFilename, name = "Filename:")
        fn2 = F.add(npyscreen.TitleFilenameCombo, name="Filename2:")
        dt = F.add(npyscreen.TitleDateCombo, name = "Date:")
        s  = F.add(npyscreen.TitleSlider, out_of=12, name = "Slider")
        ml = F.add(npyscreen.MultiLineEdit,
               value = """try typing here!\nMutiline text, press ^R to reformat.\n""",
               max_height=5, rely=9)
        ms = F.add(npyscreen.TitleSelectOne, max_height=4, value = [1,], name="Pick One",
                values = ["Option1","Option2","Option3"], scroll_exit=True)
        ms2= F.add(npyscreen.TitleMultiSelect, max_height =-2, value = [1,], name="Pick Several",
                values = ["Option1","Option2","Option3"], scroll_exit=True)

        # This lets the user interact with the Form.
        F.edit()

        print(ms.get_selected_objects())

if __name__ == "__main__":
    App = TestApp()
    App.run()

Voyez également la vidéo suivante présentant les différentes fonctionnalités de NPyScreen.

_curses.error: must call initscr() first

L'exemple le plus simple de npyscreen est celui-ci
MyForm = Form()

usrn_box = MyForm.add_widget(TitleText, name="Your name:")
internet = MyForm.add_widget(TitleText, name="Your favourite internet page:")

MyForm.edit()

et débouche inévitablement sur l'erreur:

_curses.error: must call initscr() first

La raison est simple, npyscreen se base sur la bibliothèque curse. Comme l'exemple n'initialise pas un objet "PyScreen Application" qui lui initialise Curse, nous devrons donc faire l'initialisation de Curse nous même.

Dans ce cas bien précis, il faudra faire l'initialisation de Curse nous même (voyez l'autre version de l'exemple.

Autre chose: une fois que curse est initialisé, il prend le contrôle totale de l'écran et de tout ce qui s'affiche.
Par conséquent, hors de question de taper le code de l'exemple dans un Python Interactif... dès que vous aurez initialisé Curse, vous ne verrez plus rien s'afficher à l'écran.
Il faut donc taper votre code dans un script Python puis exécuter le script Python.

#!/usr/bin/python
# encoding: utf8

import curses
from npyscreen import *

# Initialisation de curse
#   Non nécessaire si vous employez l'objet "PyScreen Application" pour
#   gérer l'application
curses.initscr()

# Exemple nPyScreen
MyForm = Form()

usrn_box = MyForm.add_widget( TitleText, name="Votre Nom" )
internet = MyForm.add_widget( TitleText, name="Your favorit internet page:" )

MyForm.edit()

# Remettre le terminal dans son état d'origine
curses.nocbreak()
curses.echo()
curses.curs_set( 1 )
curses.endwin()

# Imprimer les données collectées
print( 'Votre nom est %s' % usrn_box.value )
print( 'L adresse URI est %s' % internet.value )
 

Voici donc l'exemple complet et fonctionnel que nous avons placé dans le fichier 00_form.py que vous pouvez exécuter.

NPyScreen vs Urwid
Pour moi, il n'y a pas photo. 2H pour mon premier écran NPyScreen (avec le message d'erreur) contre 8H pour un résultat non satisfaisant (tellement il a été compliqué d'y arriver).

Pour mes petits outils de gestion journalier ce sera NPyScreen.

Ressources

mercredi 29 juillet 2015

ZPL - Impression d'image sur l'étiquette avec une Zebra LP2824 Plus

Erratum
L'article mentionne à plusieurs reprises que l'instruction Field Origin ^FO ne fonctionne pas avec l'affichage des images.
Ne tenez pas compte de cette remarque erronée. Il ne faut pas confondre la lettre O avec le chiffre zéro. En fonction de la font utilisée, cette distinction est parfois impossible et l'erreur de frappe si vite arrivée :-/ ! Voila pourquoi j'aime fonts avec le zéro barré ;-)

Introduction

Cette article est relativement technique mais passe en revue un certain nombre d'informations nécessaires permettant d'imprimer des images sur une étiquette Zebra.
J'y suis arrivé, vous pourrez donc le faire aussi.

Pour ma part, je dois réaliser cela à partir d'un programme en Clipper (Harbour Project) et n'ai donc pas d'autres choix que de me taper les spécifications.

Vous pouvez également vous référer à ces quelques articles d'introduction:
L'exemple selon Zebra à revoir!
Pour imprimer une image, il faut utiliser la commande ~DG pour définir l'image et stocker l'image en RAM.
Ensuite, l'image est imprimée à l'aide de la commande ^XG

L'exemple Zebra se base sur un petit damier codé assez simplement.
Cet exemple à deux défauts:
  • L'image est petite. Si vous utilisez une étiquette de 1", il est fort probable que vous ne voyez jamais rien. L'image est simplement imprimée hors de l'étiquette!
  • L'image est toujours imprimée depuis la coordonnée 0,0.
    Lu ailleurs dans la doc technique de 500 pages, la coordonnée par défaut de l'image est 0,0.
    Malgré les commandes de positionnement ^FO (field origin), l'origine d'impression sur ma Zebra est toujours 0,0!!!
    Il y a même l'exemple ^XG prétendant imprimer une image a différentes positions sur l'étiquette Zebra LP2824... cet exemple ne fonctionne simplement pas. L'origine est et reste 0,0 et rien d'autre. Il faudra donc prévoir son image en fonction de cette caractéristique.
Pour contourner les défauts de cet exemple, j'ai utilisé un "magnification factor" (agrandissement) de 3 sur une étiquette Zebra de 2" de large.

~DGR:SAMPLE.GRF,00080,010,
FFFFFFFFFFFFFFFFFFFF
8000FFFF0000FFFF0001
8000FFFF0000FFFF0001
8000FFFF0000FFFF0001
FFFF0000FFFF0000FFFF
FFFF0000FFFF0000FFFF
FFFF0000FFFF0000FFFF
FFFFFFFFFFFFFFFFFFFF
^XA
^F020,20^XGR:SAMPLE.GRF,3,3^FS
^XZ

Vous pouvez clairement voir que sur notre Zebra LP2824 la partie supérieure gauche du damier est imprimé "hors" de l'étiquette (depuis le point d'origine 0,0).

Il est donc très important de tenir compte de cette caractéristique lors de la création de notre image.

Encodage de l'image et notes de calculs

Encodage Hexadécimal
Ce point est probablement l'un des plus simple (cfr la doc).
Chaque point/pixel est représenté par un bit dans un octet/byte (il y a 8 bits dans un octet).

Et chaque octet est codé en Hexadécimal pour le définition de l'image.
 
FF en Hexadécimal --> 255 en décimal --> 11111111 en binaire --> donc 8 points nous d'affilés
80 en Hexadécimal --> 128 en décimal --> 10000000 en binaire --> un point noir + 7 point blancs.

Une ligne contenant FF FF FF représente donc une ligne d'une image avec 24 points noir. 

Nous avons donc un ligne ZPL par ligne dans l'image ET chaque ligne doit contenir un multiple de 8 points.

Si l'image fait 203 pixels (lignes) de haut, le bloc ZPL de l'image fera 203 lignes.
Si l'image fait 120 pixels de large, il faudra 120 / 8bits = 15 octets pour coder chaque ligne.

Comme la représentation d'un octet se fait avec 2 caractères Hexadécimal (FF = 255 en décimal), chaque ligne du bloc ZPL définissant l'image contiendra 30 caractères alphanumériques.

Question de point de vue
Là aussi je pense que la doc Zebra est un peu confuse.
Je ne sais pas pour vous mais moi je ne pars pas d'une image en mm pour ensuite savoir comment la plaquer sur une étiquette.
Je pars plutôt de l'espace disponible sur mon étiquette... puis détermine la taille de l'image à y placer... et je forge l'image en PIXELS en fonction de ce que j'ai besoin!
Pas besoin de se prendre la tête avec le nombre de dpi de l'imprimante et les mm disponibles.

La définition du bloc ~DG ci-dessous reprend donc les mensuration et caclul à partir de la taille en pixels.

Définition de l'image

Le bloc ~DG est défini comme suit:

~DGd:o.x,t,w,data

  • d = l'endroit où l'on stocke l'image... c'est en R (la ram)
  • o.x = Nom de l'image et son extension. Nom court, sans fioriture, moins de 8 caractères. optez pour un truc come LOGO.GRF
  • t = Taille de l'image en byte/octet. hauteur_en_pixels * largeur_en_pixels / 8.
    N'oubliez pas de prendre une image dont la largeur est un multiple de 8.
    Pour une image 120 x 203 --> 120 x 203 / 8 = 3045 octets
  • w = Nombre de bytes/octets par ligne. Dépend uniquement de la largeur de l'image.
    Largeur_en_Pixel / 8 --> 120 / 8 = 15 octets sur chaque ligne.
  • data = donnée de l'image au format hexadécimal (comme décrit ci-dessus).
Voici donc l'entête (et une partie du corps) ZPL de mon étiquette contenant une image (120 x 203 pixels) en ZPL.
Cette étiquette étant destiné à une imprimante Zebra LP2824 Plus avec une étiquette Z-Select 2000D de 50.8 x 25.4mm  (2" x 1").

~DGR:GGLOGO.GRF,03045,015,
800000000000000000000000000000
000000000000000000000000000000
...
...
000000000000000000000000000000
000000000000000000000000000000
^XA
^F00,0^XGR:GGLOGO.GRF,1,1^FS
^FX--FAIRE ETIQUETTE LARGE--^FS
^FX--Print Qty--^FS
^PQ@@QTY@@,0,0,Y
^FX--FR LABEL--^FS
^FO20,25^AR,40,30^FB405,1,0,C,0^FD@@LIBEEN@@^FS
^FX--PRODUCT CODE--^FS
^FO120,75^AT,40,30^FB300,1,0,C,0^FD@@ARTI@@^FS
^FX--BOX LOGO--^FS
^FO20,65^GB100,130,2^FS
^FX--EAN - CODE128--^FS
^FO230,130^BY1^AD,18,10
^BCN,40,Y,N,
^FD@@EAN@@^FS
^XZ

Au final, nous obtenons ceci.

Cas pratique
Voici comment faire pour créer l'image à afficher sur une étiquette.
Pour commencer, j'ai essayer de produire le code ZPL de l'étiquette avec un cadre à l'emplacement où je voulais placer l'image.
Cela permet:
  1. De connaître les coordonnées utiles.
  2. d'avoir la dimension de l'image à produire.
Je me suis donc efforcé d'écrire un tel fichier ZPL (voyez nos autres articles repris dans l'intro pour l'impression).
Au final, j'ai produit l'étiquette suivante.

^XA
^FX--FAIRE ETIQUETTE LARGE--^FS
^FX--Print Qty--^FS
^PQ1,0,0,Y
^FX--FR LABEL--^FS
^FO20,25^AR,40,30^FB405,1,0,C,0^FD@@LIBEEN@@^FS
^FX--PRODUCT CODE--^FS
^FO120,75^AT,40,30^FB300,1,0,C,0^FD@@ARTI@@^FS
^FX--BOX LOGO--^FS
^FO20,65^GB100,130,2^FS
^FX--EAN - CODE128--^FS
^FO230,130^BY1^AD,18,10
^BCN,40,Y,N,
^FD@@EAN@@^FS
^XZ

Les valeurs avec @@code@@ sont destinés à être remplacés au vol par un programme.
Ce qui nous intéresse, c'est la BOX pour la future position du logo.

^FO20,65^GB100,130,2^FS

Cette boite se trouve à l'origine 20,65 (gauche,top) avec une largeur de 100 et hauteur de 130 points.
Mais souvenez vous, une image est toujours affichée depuis l'origine 0,0.
Pour remplir le cadre, il faudra une image de:
  • 20 + 100 = 120 point de large.
    On veillera à ne pas placer de graphique sur une bande de 20 pixels sur la gauche de l'image).
    La largeur est aussi un multiple de 8 (pour le codage Hexadécimal) --> 15 octets par ligne.
  • 65 + 103 = 195 Pixels de haut.
    On veillera à ne pas placer de graphique sur la bande supérieure de 65 pixels (afin de ne dessiner que dans le cadre).
Nous avons donc la dimension idéale de notre image 120 x 195 pixels (avec une partie de celle-ci sur laquelle il ne faut pas dessiner).

Créer l'image
J'ai utiliser la méthode décrite dans le précédent article Transformer une image en BMP 2 couleurs pour une impression PCL pour créer l'image ci-dessous.

 Bon, j'ai fait un petit excès de zèle sur la hauteur de l'image, elle fait 203 pixels de haut (au lieu des 195 prévu).
La taille en octet de mon image sera donc de Hauteur_en_pixel x Largeur_en_Pixel / 8 = 3045 octets!
Le nombre d'octet par ligne pour l'image est de largeur_en_pixel / 8 = 15 octets!

Voici l'image pour ceux qui voudraient répéter l'opération (l'image doit être en 2 couleurs, 120x203 pixels)


Blogger n'aime pas les BMP 2 couleurs
du coup c'est un PNG qu'il vous faudra certainement
la retransformé en BMP 2 couleurs

Comme indiqué à la fin de l'article Transformer une image en BMP 2 couleurs pour une impression PCL , j'ai sauvé ce fichier en format SUNRAS 1 bit (im1).

Cela permet d'obtenir un fichier brute qui peut être rechargé en Python (sous format binaire).

Il faut sauter les 32 premiers octets de l'entête et chaque ligne est codée sur 16 octets de large au lieu de 15 octets (il faut donc se débarrasser du dernier octet de chaque ligne qui est toujours à 00).

Voici un petit script python (iPython) sans prétention qui permet de faire les manipulations nécessaires à la création du bloc de donnée de l'image en HexaDécimal.
file = open( 'GGLOGO.im1', 'rb' ) # Read BINARY file
fileContent = file.read()
file.close()
withoutHeader = fileContent[32:]

# Get the first byte as Hex
# "{:02x}".format( withoutHeader[0] )

# Image Size in Pixels (or dots). Look for Image property in Gimp
#    Width must be a multiple of 8
image_width = 120 
image_height = 203

byte_per_line = int( image_width / 8 )
byte_to_read  = int( (image_width * image_height)/8 )

# Test the size of array of data with image size
len( withoutHeader ) # 3248 (inclus le 16ieme octet 00 à virer sur chaque ligne!)
byte_to_read         # 3045 (n'inclus pas le CR dans le calcul)

# Result = List with one line per image line.
#          Each image line is a list of 15 byte under HexaDecimal
#            representation (we throw the 16th byte)
#
# result = [ [ '00', '00', '00', '00', '00', '00', '00' , '00' , '00' , '00' , '00' , '00', '00', '00' ,'00' ],
#            [ '00', '00', '00', '00', '00', '00', '00' , '00' , '00' , '00' , '00' , '00', '00', '00' ,'00' ],
#            ...
#          ] 
iCount = 0
result = []
sublist = []

for c in withoutHeader:
    iCount += 1
    # Garder que les 15 premiers bytes 
    if iCount < 16:
        sublist.append( "{:02x}".format( c ) )
    # Tous les 16 bytes c est une nouvelle ligne
    if iCount == 16:
        # skip the 16th byte
        # Start a new sublist
        result.append( sublist )
        sublist = []
        iCount = 0
if len( sublist )>0:
    result.append( sublist )

# View the result of the resultLines transformation done here under
#for l in result
#  print( ''.join(l) )
  
# Joindre les représentations Hexa Decimal en ligne (une string par ligne de l'image) )
#
# resultLines = [ '000000000000000000000000000000' ,
#                 '000000000000000000000000000000' ,
#                 ...
#               ] 
resultLines = [ ''.join(l) for l in result ]

outFile = open( 'GGLOGO.GRF' , 'w', encoding='utf8' )
for l in resultLines:
    outFile.write( l+'\n' )
outfile.close()

Je vous laisse décortiquer le script qui génère un fichier 'GGLOGO.GRF'. C'est un peu brute de décoffrage mais vous devriez pouvoir reproduire toutes ces étapes par vous même.

Le fichier GGLogo.GRF contient uniquement les données Hexadécimales de l'image à placer dans le bloc data de la commande ZPL ~DG .
Comme déjà calculé précédement, le paramètre T faut 3045 octets et le paramètre w vaut 15 octets.
Au final, nous avons une belle étiquette en ZPL dont voici le contenu complet.

~DGR:GGLOGO.GRF,03045,015,
800000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000020
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000017fffff8000020
0000000000000000e0000007000010
000000000000000300000000400010
000000000000000c00000000300020
00000000000000101e0fff400c0010
0000000000000060e230003f030020
000000000000004304200000e08010
000000000000010c002000001c4010
000000000000023004200000039010
00000000000004400420000000d820
000000000000088004400000003410
000000000000130004400000000b10
000000000000220004400000000490
000000000000cc0004400000000360
000000000001900004600000000130
000000000006600004400000000110
000000000018800004400000000120
00000003c0e3000000400000000190
00000007f90600000a640000005e20
0000001ffe01f017f8017ffffd0010
0000003ffe000de800000000000020
0000007fde000003002000e0000010
000000fc0300fc0f007fc1e000f020
000000fc0103ff0f503ff1ff0fff10
000001f80807ff8ffc7ff1ff9fffe0
000001f80fe7878ffffff9ffffeff0
000001f807ff1f0fe3f1f9f87f01f0
000001f003feff0f878079f07c0070
000001f003ee000f078039e07c0070
000001f0e3ce001e07003de0781830
000001f1b3ce001f0f003de0783c38
000001f363cf000e07001de0786438
000001f3e3cf01df07803de0f83c38
000001f9c7cf83df07c07de07c183c
000001fc07cfffdf03fffde0fc007c
000000fc0fc7ff9f03fffdf07e00fe
000000ffffc1ff3f00ffbff07f01fe
000000ffffc0700b002f3ff87ffffe
0000007fffc00000000004007fffff
0000003fff800000000000003fffff
0000000fff000000000000001fff9c
00000003fc000000000000000fff00
000000103c0000000000000003fc00
000000103c00000000000000000000
000000103c00000000000000000000
000000103800000000000000000000
000000103c00000000000000000000
000000187c00000000000000000000
0000001cfc00000000000000000000
0000001ffe00000000000000000000
0000000ff000000000000000000000
00000007e000000000000000000000
000000010400000000000000000000
000000001c00000000000000000000
000000301c00000000000000000000
000000383c00000000000000000000
000000381c00000000000000000000
000000383c00000000000000000000
000000383c00000000000000000000
0000003c3c00000000000000000000
0000003cfc00000000000000000000
0000001ffc00000000000000000000
0000001ffc00000000000000000000
0000001ffc00000000000000000000
0000000ffc00000000000000000000
000000073c00000000000000000000
000000003c00000000000000000000
000000003c00000000000000000000
000000603c00000000000000000000
000000203c00000000000000000000
000000307c00000000000000000000
000000707c00000000000000000000
00000039f800000000000000000000
0000003ff800000000000000000000
0000001ff000000000000000000000
0000001fe000000000000000000000
00000007c000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
^XA
^F00,0^XGR:GGLOGO.GRF,1,1^FS
^FX--FAIRE ETIQUETTE LARGE--^FS
^FX--Print Qty--^FS
^PQ1,0,0,Y
^FX--FR LABEL--^FS
^FO20,25^AR,40,30^FB405,1,0,C,0^FD@@LIBEEN@@^FS
^FX--PRODUCT CODE--^FS
^FO120,75^AT,40,30^FB300,1,0,C,0^FD@@ARTI@@^FS
^FX--BOX LOGO--^FS
^FO20,65^GB100,130,2^FS
^FX--EAN - CODE128--^FS
^FO230,130^BY1^AD,18,10
^BCN,40,Y,N,
^FD@@EAN@@^FS
^XZ
N'oubliez pas de remplacer les @@code@@ par des valeurs pertinentes... et surtout de sauver votre fichier en UTF8. En espérant que cela puisse vous être utile.

dimanche 26 juillet 2015

urwid: interface en mode texte (console) pour Python

Tkinter + megawidget offre de belles possibilités d'interfaces graphique d'autant que le projet pygubu (designer Tkinter pour python) semble encore très actifs.
Il n'empêche que de telles interfaces consommes pas mal d'énergie pour une première mise en oeuvre.

Nos petits projets n'ont pas toujours besoin d'autant de raffinement et dans de nombreux cas, une petite interface en mode texte (console) serait bien suffisante à notre confort.

NCusrse
NCurse l'outil le plus connu pour dessiner des interfaces en mode texte sous Python. Il est assez facile de trouver de la documentation sur le Net.
C'est pas mal mais un peu barbare. La aussi il y a un certain investissement avant d'être productif.

Snack
J'ai déniché snack en m'intéressant à Grub. Il s'agit d'une interface en mode texte pour Python dont le but est de pouvoir facilement réaliser des outils de configuration système. Il va de soi, pas question de compter sur XWindows lorsque l'on fait de l'administration a distance en ssh. Snack peut donc faciliter la vie dans certains cas.
Snack vaut peut être la peine de s'y pencher, voici quelques liens pour référence:
urwid
Urwid semble être un projet très intéressant car il permet la réalisation d'interface texte de façon "Pythonique".
Ce fut une découverte un peu fortuite sur une vidéo YouTube traitant des interfaces en mode texte.

Le code est agréable à lire et assez facile à comprendre (voir exemple dans le vidéo ci-dessus). Cela en fait un outil de choix à étudier d'autant qu'il ne manque pas d'exemples sur le site urwid.org.

Note complémentaire:
Mon but étant de construire rapidement une petite interface de gestion avec un interface acceptable. Je me suis heurté à la complexité du Framework de Urwid. Il m'aura fallut 8 heures pour réaliser un menu et un premier écran (dont je ne suis vraiment pas satisfait).

En fin de compte, la lourdeur du Framework d'Urwid (composant de type Block et les autres de type Flow ainsi que le mix Block + Flow) aura raison de ma patiente. 8 Heures plus tard je suis toujours au point de départ (ou presque).
Résultat: J'abandonne Urwid.

Cela ne signifie pas qu'Urwid ne conviennent pas pour des projets de plus grandes envergures mais quitte à faire un tel investissement d'apprentissage alors je préfère le faire dans Tkinter pour Python.


L'installation passe par un simple:

apt get install python-urwid


Voici quelques ressources complémentaires:
S'y retrouver dans urwid ne coule pas de source (surtout à cause des "décorations" widget). Le liens suivant permet de fixer les idées plus facilement après les premiers tuto.
A voir aussi

vendredi 10 juillet 2015

Zebra + ZPL sous Clipper Windows - Harbour for beginner, une reference documentaire Clipper sour Windows

Déjà évoqué plusieurs fois, Harbour-Project permet de compiler des logiciels Clipper sous Win32 et ainsi de profiter pleinement des avantages de la plateforme Windows a partir d'un vieux logiciel (appel COM, impression, Mutex, WinSock, etc).

Harbour for Beginner
Je viens de trouver un chouette document "Harbour for Beginner" d'alexender Kresin (anglais) où il aborde des points très intéressantes:
  • Some language extensions
  • Classes and objects
  • Lang API (the language of the program)
  • Codepage API
  • Working with hrb - files
  • The built-in C
  • Hash arrays
  • Regular expressions
  • INET subsystem
  • Multithreading
  • Miscellaneous functions
Le point  "Lang API" et sa fonction hb_StrToUtf8() tombe bien, je dois justement créer des fichiers en UTF-8 pour envoyer un flux ZPL à ma petite zébra ... voyez l'article "Imprimante Zebra USB, ZPL et Spooler Windows - Sacré Windows!!!" .

C'est que la Zebra n'apprécie pas l'ANSI, elle fait sauter tous les accentués si le flux est ANSI mais en UTF-8 c'est impeccable.


Création d'un fichier ZPL et envoi Sur Zebra
Voici un petit programme clipper compilé avec Harbour Project qui crée un fichier ZPL puis l'envoi sur l'imprimante Zebra LP 2824 nommée ZEBRA_RAW (voyez l'article "Imprimante Zebra USB, ZPL et Spooler Windows - Sacré Windows!!!").
Pas le code le plus pure... mais le vilain proto marche très bien :-)

&& #include "mediator.ch"  Mediator - PostgreSql middleware
#include "fileio.ch"

LOCAL hDst := NIL 
LOCAL sOutFilename := 'demo.zpl'
LOCAL sPrinterName := 'ZEBRA_RAW' 

&& Mediator - PostgreSql middleware
&& request medntx
 
REQUEST HB_CODEPAGE_FRWIN
SET( _SET_CODEPAGE, "FRWIN" ) 

&& Mediator - PostgreSql middleware
&& RDDSETDEFAULT("MEDNTX")  && set default data source to MEDNTX RDD driver


* -- initialisation
set date british
set delete on
set confirm on
set escape on
set wrap on
set epoch to 1920

? 'Create a '+sOutFilename+' file encoded with UTF-8'

&& Open Destination Filename 
hDst := hb_FCreate( sOutFilename, FC_NORMAL, FO_READWRITE + FO_SHARED )
IF hDst == F_ERROR 
     sMsg = "Impossible de recréer FAReprint.txt!" 
     ALERT( HB_AnsiToOem(sMsg) ) && convert to "DOS String" 
     RETURN NIL
ENDIF

&& Surprenant, pas besoin de hb_StrToUtf8(), le fichier est déjà
&&    encodé en UTF8! Impression sur Zebra Parfaite
cBuf = '^XA'
fwrite( hDst, cBuf + hb_eol() )
cBuf = '^FO100,50^ADN,36,20^FDxyz*é&êà*^FS'
fwrite( hDst, cBuf + hb_eol() )
cBuf = '^XZ'
fwrite( hDst, cBuf + hb_eol() )

FCLOSE( hDst )

? 'WIN_PrintFileRaw: Send ZPL to Printer '+sPrinterName 
retCode = hb_ntos( WIN_PrintFileRaw( sPrinterName, (sOutFilename) ))
? "print retCode for "+sPrinterName+" = "+retCode
 

Ressources

jeudi 9 juillet 2015

Imprimante Zebra USB, ZPL et Spooler Windows - Sacré Windows!!!

Introduction
Par le passé, j'avais déjà écrit l'article "Imprimante Zebra USB, ZPL et CUPS - Damned CUPS!!!" déjà source de nombreuses informations.
Cette fois, je reprends le savoir acquis, ma petite Zebra LP 2824 plus en USB et j'essaye d'imprimer le fichier demo.zpl depuis ma machine Windows 7 et sur la Zebra qui y est branché via USB.

Mr Gates, je voudrais envoyer un fichier Raw à ma petite printer zébrée... mais pourquoi est-ce si tortueux?

Comme d'habitude avec W!nd0w$ il faut gratter un peu car rien n'est plus compliqué que de vouloir faire simple! C'est que derrière, je dois faire fonctionner le tout avec un bon vieux soft Clipper compilé en Win32 (vive HarbourProject).

NB: Moi qui me plaignait de CUPS la dernière fois, j'en était presque arrivé à considérer l'usage d'un Raspberry-Pi pour mettre ma Zebra sur le réseau!

Le but rechercher 
Envoyer le contenu du fichier demo.zpl (contenant du code ZPL) directement à l'imprimante Zebra

Installer l'imprimante Zebra
Hé bien, pour commencer, nous allons simplement utiliser les pilotes Windows livrés avec l'imprimante (sur un CD) et installer l'imprimante Zebra "LP 2824 Plus".
Sélectionnez le pilote d'imprimante avec le support ZPL (pas celle avec l'EPL).
Il faut être patient, Windows n'est pas forcement très rapide... cela m'aura pris pas moins de 15 minutes et l'imprimante installée deux fois ?!?!.

Faites bien attention durant l'installation, dans la configuration des ports, vous verrez l'utilisation d'un port du type USB00x (dans mon cas, ce fut USB001)

Faites une impression de "page de test", cela doit marcher, j'ai un morceau de logo Windows sur une étiquette.

Installer une imprimante Text / Generic
Ensuite, nous allons installation d'une imprimante générique de type texte et nous allons utiliser le port USB00x attribué à notre Zebra à l'étape précédente.
Durant l'installation, nous allons:
  • Donner un nom simple à cette imprimante (ex: ZEBRA_RAW) pour pouvoir y accéder facilement.
  • Partager cette imprimante afin qu'elle soit disponible sur le réseau (c'est vraiment essentiel pour imprimer un fichier depuis un ligne de commande!)
Au final, nous nous retrouvons avec une imprimante ZEBRA-RAW permettant l'envoi de commandes ZPL directement à l'imprimante.

Vous trouverez également plus d'information sur ce sujet dans l'article "Setting up a Raw Printer in Windows" (qz.io, anglais)

Préparer notre fichier demo.zpl

Ouvrez un notepad puis collez le code suivant;
^XA
^FO100,50^ADN,36,20^FDxxx^FS
^XZ

En sauvegardant votre fichier, faite bien attention à utiliser l'encodage UTF8 (je ne sais pas trop comment serait digéré de l'ANSI)

Envoyer le fichier demo.zpl à la Zebra
Comme le dit si bien Mitch dans son article "Send PRN File to Printer", il n'y a plus grand monde qui dispose d'une imprimante série.
Du coup les commandes COPY /B C:\FILENAME.PRN LPT1: ne sont plus d'actualité car elles ne fonctionne pas avec USB001 :-/

Par contre, notre monde over-connecté-sur-le-net permet d'utiliser une telle commande si lpt1: est remplacé par le nom d'une imprimante partagée sur le réseau (avec la structure \\nom_pc\nom_partage_imprimante )
Du coup, si l'on a partagé son imprimante Zebra sur le réseau (ce que j'avais chaudement conseillé au point précédent), il sera possible d'utiliser une commande comme celle-ci:

c:\temp> copy /B demo.zpl \\127.0.0.1\ZEBRA_RAW\
        1 fichier(s) copié(s).

Résultat:

Note: le /B indique une copie binaire tandis que 127.0.0.1 est l'adresse réseaux loopback (celle qui renvoi vers mon PC).
C'est un peu alambiqué mais cela fonctionne sans devoir installer Visual Studio ou n'importe quel autre soft.

Vous voila prêt à faire quelques essais en ZPL, il est très facile de trouver de l'information sur le NET. Le plus dure, a savoir un envoi RAW clean, vient d'être réaliser :-)

Happy coding :-)

lundi 6 juillet 2015

Identifiier les fonctions dans une DB PostgreSQL

Voici de petites requêtes pour PostGreSQL permettant d'identifier les fonctions déclarées dans une DB. Le petit truc à garder dans une boite à outil.

Saviez-vous que 3 caractères sont déjà très discriminatoires lors d'une recherche?
Ce SQL devient vachement pratique lorsqu'il y en beaucoup de fonction car elle permet de faire une recherche sur mot clé avec une "where" clause et un "like" :-) 

-- Lister les fonctions définies dans une DB
--   src: http://stackoverflow.com/questions/1559008/list-stored-functions-using-a-table-in-postgresql

SELECT  p.proname
FROM    pg_catalog.pg_namespace n
JOIN    pg_catalog.pg_proc p
ON      p.pronamespace = n.oid
WHERE   n.nspname = 'public';

-- Identifier sur base d'une partie du nom de la fonction
--    AND proname like '%ver%'




-- Lister les fonctions définies AVEC nom des arguments
--   Pratique lorsqu'il faut réaliser un DROP FUNCTION <name>(<args>);
--  

SELECT  proname, proargnames
FROM    pg_catalog.pg_namespace n
JOIN    pg_catalog.pg_proc p
ON      pronamespace = n.oid
WHERE   nspname = 'public';