# -*- coding: utf-8 -*- # # par Michel Claveau Informatique # http://mclaveau.com # import re,zipfile,time,sys,types import cPickle,shutil,os,stat import threading GlobalPVersion='0.82' """ TODO: vérif contraintes sur journal vérif append, listwappend, llistrawappend sur journal vérif empty sur journal empty => vide (journal) Modifs version 0.76 mise au premier plan de la feuille Excel dans eexcel Modifs version 0.72 cas de delete du dernier enregistrement d'une table Modifs version 0.71 gestion erreur jointure dans table.recno= gestion erreur positionnement dans searchexact Modifs version 0.64 ajout méthode : table.rlistallnumufast Modifs version 0.61 ajout de deux méthodes : selection.recuplistsansjointure table.selectionequ Modifs version 0.60 ajout de selection.eooocalc (exportation d'une sélection dans une feuille OpenOffice.org/CALC exemple : eooocalc(sel, feuille, lig, col, *champs) Modifs version 0.58 rlistall: ajout du num enregistrement en fin de chaque liste/record ("pour chaque ligne") recuplist : ajout du num enregistrement en fin de chaque liste/record ("pour chaque ligne") Modifs version 0.57 correction si delete 1er enregistrement Modifs version 0.56 ajout du nom de la table dans 'Jointure error' Modifs version 0.55 ajout, aux champs, des propriétés largeur, format, clabel, cexplication ; utilisés pour GUI (PyWiG + impressions) Modifs version 0.54 ajout de la propriété dermodif, qui contient time() de dernière modif d'une table, lors de son chargement ajout de la méthode .verifsimodif() qui permet de vérifier si la table a été modifiée par ailleurs Modifs version 0.53 ajout de la fonction toxml(sel, fichier, *champs) pour les sélections Modifs version 0.52 ajout de la méthode .explication() pour les champs Modifs version 0.42 correction d'un bug dans les jointures (repositionnements en cascade) Modifs version 0.41 ajout de la methode close() à base et table Modifs version 0.40 ajout de la methode close() à base et table enregistrement de table maintenant multi-threadé Modifs version 0.39 ajout des modes de compression, à l'enregistrement sont gérés : "ZIP" "BZ2" "FZIP" "CDB" Modifs version 0.38 utilisation de cPickle.HIGHEST_PROTOCOL pour enregistrer les tables (gain taille/vitesse de 30 %) Modifs version 0.37 ajout de scan() aux tables Modifs version 0.36 : meilleure gestion des erreurs lors d'insertion (pour 'uniq' et 'joint') ajout du critère APP (appartient, comme IN list) ajout de la méthode selectioncascade() aux tables Modifs version 0.35 : ajout d'une mémorisation/retour dans les opérations sur selection changement du calcul des tranches, pour bisect_left ajout de method enum à selection ajout de method enumcallback à selection Modifs version 0.34 : ajout de method visurc() aux tables Modifs version 0.33 : ajout de method end() aux tables Modifs version 0.32 : accélération cPickle (dont charge() est plus rapide) Modifs version 0.29 : correction d'un bug si selection juste après une suppression Modifs version 0.28 : correction d'une régression (rmax avait disparu, et a été remis) Modifs version 0.27 : si une table n'existe pas à l'ouverture, elle est initialisée avzec vide.cdb (qui doit exister dans le même dossier) Modifs version 0.26 : ajout des tranches Modifs version 0.25 : correction bug sur suppression index Modifs version 0.24 : ajout de selection.eexcel (exportation d'une sélection dans une feuille Excel exemple : eexcel(sel, feuille, lig, col, *champs) ajout de listselect.eexcel (exportation d'une liste (de listes) dans une feuille Excel ajout de table.incfermeture( qui effectue une sélection, et retourne une fonction de recherche incrémentale sur la listes de la sélection. (utilisation d'une fermeture) Modifs version 0.22 : correction sur l'affectation des champs à PonxV annulation de nameponxv Modifs version 0.21 : ajout de nettoievides() pour corriger le problème de records vides Modifs version 0.20 : ajout de nameponxv() dans les tables et bases Modifs version 0.14 : ajout du critère CNT (contient, pour String et Unicode) Modifs version 0.13 : correction affichage Modifs version 0.12 : ajout de rmax pour les sélections (retourne le rec et la valeur_max) d'une sélection Modifs version 0.08 : correction pointeur champ danc trigger update transfert de commit et rollback des tables vers la base ajout du journal (début) ajout nomjournal dans la classe 'base' Modifs version 0.07 : ajout du dossier ; dans base & charge() Modifs version 0.05 : llistappend (insertion d'une liste de listes ds une table) llistrawappend (insertion 'brute' d'une liste de listes ds une table) visurecchamps visu d'un enregistrement, (noms des) champs et valeurs Modifs version 0.04a : a: modification de autoname (appel plus simple) deboguage fromsearch pour les champs (renvoie sur idx.searchexact) Modifs version 0.03 : alter (table) selection.recuplist listselect.recuplist Modifs version 0.02 : corrections diverses autoname liste jointures par table rafraîchissement jointures par __getattr__ des tables CHANGEMENT paramètre nom dans tables et champs Modifs version 0.01 : base.idxactivate() champ : gestion des contraintes mini, maxi, long champ : gestion contrainte uniq (traitée au niveau table.set) table.set incomplet table.set returne un flag table.append retourne le numéro créé intégration dans ponxupdate corr idx.searchexact si inexistant """ """ idées path dans database fichier dans table contraintes : type (date, number, string) sélections interval superieur superieuregal inferieur inferieuregal journalisation contrôle suppression su utilisé dans jointure countall, cumul(total), min et max, pour une sélection alter table setlistlist setrawlistlist teste contraintes (d'une liste correspondant à un record) les noms des champs doivent être uniques, en ascii pur, sans accents les numeros d'enregistrement commencent à ... ce qui est saisi. Attention : pas d'ordre Toute jointure doit utiliser un champ indexé attention aux triggers circulaires : bloquant. les contraintes mini/maxi sont appliquées, sans bloquer (valeurs forcées) contrainte uniq traité au niveau table.set set doit obligatoirement contenir les valeurs des champs uniq attention : aux triggers circulaires attention : aux jointures circulaires """ import collections, types,bisect, chata, os,time,locale import os,os.path locale.setlocale(locale.LC_ALL, "") global dfb_basedefaut def autoname(g): d=g.keys() code='' for nom in d: if isinstance(g[nom],chata.champ): code+=nom+'.name="'+nom+'"\n' for nom in d: if isinstance(g[nom],chata.table): code+=nom+'.name="'+nom+'"\n' exec(code,g,g) return code class MyError(Exception): def __init__(self, value): self.value = value def __str__(self): return repr(self.value) class llist2xml(object): from xml.dom.minidom import Document doc = Document() def __init__(self, nomtable, nomschamp, lliste): rootName = nomtable.encode('utf-8','replace') self.root = self.doc.createElement(rootName) self.nomschamp=nomschamp self.doc.appendChild(self.root) for numlig,liste in enumerate(lliste): tag = self.doc.createElement(str(numlig)) tagligne=self.root.appendChild(tag) for tagName,l in zip(self.nomschamp,liste): tagchamp = self.doc.createElement(tagName) tagligne.appendChild(tagchamp) if isinstance(l, types.StringType): data=l elif isinstance(l, types.UnicodeType): data=l elif isinstance(l, types.StringTypes): data=l else: data=str(l) tagdata = self.doc.createTextNode(data) tagchamp.appendChild(tagdata) def display(self): print self.doc.toprettyxml(indent=" ") def write(self, fichier="C:\\temp.xml", encoding="utf-8"): f=open(fichier,"w") self.doc.writexml(f,indent=" ",encoding=encoding) f.close() class tranche(object): """ définition d'une tranche, pour classement Les tranches permettent de compter, totaliser, ou 'callbacker' des données, en les groupant. """ def __init__(self, *valeurs, **kw): """ Prépare une tranche ; on envoie la liste des valeurs-charnières. """ self.crit=list(valeurs) self.crit.sort() self.nb=[0]*(len(valeurs)+1) self.result=[0]*(len(valeurs)+1) try: self.mode = kw['operateur'] except: self.mode = 'COUNT' try: self.callback = kw['fonction'] except: self.callback = None def cmode(self,operateur,fonction=None): """ défini le type de calcul de classement COUNT compte le nombre d'ocurrences SUM totalise une valeur CALLBACK totalise le résultat d'une fonction, appelée avec deux paramètres: critère et valeur """ if operateur.upper()=="COUNT": self.mode="COUNT" elif operateur.upper()=="SUM": self.mode="SUM" elif operateur.upper()=="CALLBACK": self.mode="CALLBACK" self.callback=fonction def visu(self): """ Affiche les différentes tranches, avec le résultat """ total=0 for i,j in zip(self.crit,self.result): print "jusqu'a",i,":",j total += j total += self.result[-1] print "apres ",self.crit[-1],":",self.result[-1] print "----------" print "Total : ",total def resultat(self): """ retourne la liste des critères de la tranche, et la liste des résultats attn: la longueur des listes est ajustée. """ #lret=self.crit #lret.append(lret[-1]) return(self.crit+[self.crit[-1]],self.result) def calc(self,critere,valeur=None): """ Effectue le calcul (groupement + calcul des résultats) """ if valeur is None: valeur=critere if self.mode=="CALLBACK": tcrit,tval=self.callback(critere,valeur) if tcrit == 1: #print 246,self.crit,tcrit pass index=bisect.bisect_left(self.crit,tcrit) if tcrit == 1: #print 249,index #sys.exit() pass self.nb[index]+=1 self.result[index]+=tval else: index=bisect.bisect_left(self.crit,critere) if self.mode=="COUNT": self.nb[index]+=1 self.result[index]+=1 elif self.mode=="SUM": self.nb[index]+=1 self.result[index]+=valeur class selection(object): @staticmethod def visu(sel, *champs): l=list(sel) table=champs[0].table for num in l: table.recno=num print '--',num,'<>' for c in champs: print c.value, pass print @staticmethod def rmax(sel, champ): l=list(sel) table=champ.table bufnum=table.recno vmax=None vrec=0 for num in l: table.recno=num if vmax==None: vmax=champ.value vrec=num else: if champ.value>vmax: vmax=champ.value vrec=num table.recno=bufnum return vmax,vrec @staticmethod def sort(sel, *champs): l=list(sel) table=champs[0].table bufnum=table.recno lsorti=[] for num in l: table.recno=num ltmp=[] for c in champs: ltmp.append(c.value) ltmp.append(num) lsorti.append(ltmp) lsorti.sort() table.recno=bufnum return [ l[-1] for l in lsorti] @staticmethod def eexcel(sel, feuille, lig, col,*champs): """ Exportation d'une sélection dans une feuille Excel, à l'emplacement {lig,col} pxexcel doit être présent """ l=list(sel) table=champs[0].table bufnum=table.recno lsorti=[] for num in l: table.recno=num ltmp=[] for c in champs: ltmp.append(c.value) ltmp.append(num) lsorti.append(ltmp) lsorti.sort() import pxexcel x=pxexcel.exl() time.sleep(0.05) x.open(feuille,True) x.selActiveCelluleA(lig,col) x.setLRange(lsorti,lig,col) table.recno=bufnum x.visible() x.premierplan() #return lsorti #return [ l[-1] for l in lsorti] @staticmethod def eooocalc(sel, feuille, lig, col,*champs): """ Exportation d'une sélection dans une feuille OpenOffice.org CALC, à l'emplacement {lig,col} pxooo doit être présent """ l=list(sel) table=champs[0].table bufnum=table.recno lsorti=[] for num in l: table.recno=num ltmp=[] for c in champs: ltmp.append(c.value) ltmp.append(num) lsorti.append(ltmp) lsorti.sort() import pxooo x=pxooo.calc(feuille) time.sleep(1) x.setcellulelist((col-1,lig-1), lvaleur=lsorti) table.recno=bufnum @staticmethod def toxml(sel, fichier, *champs): """ Exportation en XML d'une sélection dans le fichier fichier, pour les champs indiqués. Exemple: chata.selection.toxml( sel, "L:\\fictest.xml", inv,clef,libelle,date,montant) """ from xml.dom.minidom import Document nomschamp=[ch.name for ch in champs] l=list(sel) table=champs[0].table bufnum=table.recno lsorti=[] for num in l: table.recno=num ltmp=[] for c in champs: ltmp.append(c.value) ltmp.append(num) lsorti.append(ltmp) lsorti.sort() xml=llist2xml(table.name, nomschamp, lsorti) xml.write(fichier) @staticmethod def recuplist(sel, *champs): """ à partir d'un objet sélection, on récupère le contenu de certains champs, sous forme d'une liste de listes """ if not isinstance(sel,set): raise TypeError("le 1er parametre doit etre une selection") l=list(sel) table=champs[0].table bufnum=table.recno lret=[] for num in l: table.recno=num llig=[] for c in champs: llig.append(c.value) #llig.append(num) lret.append(llig) table.recno=bufnum return lret @staticmethod def recuplistrnum(sel, *champs): """ à partir d'un objet sélection, on récupère le contenu de certains champs, sous forme d'une liste de listes """ if not isinstance(sel,set): raise TypeError("le 1er parametre doit etre une selection") l=list(sel) table=champs[0].table bufnum=table.recno lret=[] for num in l: table.recno=num llig=[] for c in champs: llig.append(c.value) llig.append(num) lret.append(llig) table.recno=bufnum return lret @staticmethod def recuplistallrnum(sel,table=None): """ à partir d'un objet sélection, on récupère le contenu de tous les champs, sous forme d'une liste de listes """ if not isinstance(sel,set): raise TypeError("le 1er parametre doit etre une selection") l=list(sel) bufnum=table.recno lret=[] for num in l: table.recno=num llig=[] for i in range(table.nbcol): llig.append(table.champl[i].value) llig.append(num) lret.append(llig) table.recno=bufnum return lret @staticmethod def recuplistsansjointure(sel, *champs): """ à partir d'un objet sélection, on récupère le contenu de certains champs, sous forme d'une liste de listes N'EFFECTUE PAS LES JOINTURES (plus rapide, mais limité aux valeurs de la table elle-même) """ if not isinstance(sel,set): raise TypeError("le 1er parametre doit etre une selection") table=champs[0].table bufnum=table.recno lnumch=[] for c in champs: #print 560, "colonne", table.champd[c] lnumch.append(table.champd[c]) lret=[] for num in sel: #print 561, table.rec[num] record=table.rec[num] ###llig=[record[i] for i in lnumch] """ llig=[] for i in lnumch: llig.append(record[i]) """ ###lret.append(llig) lret.append([record[i] for i in lnumch]) table.recno=bufnum return lret @staticmethod def recuplistnum(sel, *champs): """ à partir d'un objet sélection, on récupère le contenu de certains champs, sous forme d'une liste de listes """ if not isinstance(sel,set): raise TypeError("le 1er parametre doit etre une selection") l=list(sel) table=champs[0].table bufnum=table.recno lret=[] for num in l: table.recno=num llig=[] for c in champs: llig.append(c.value) llig.append(num) lret.append(llig) table.recno=bufnum return lret @staticmethod def calctranche(sel,tra,champcritere,champvaleur=None): if champvaleur is None: champvaleur=champcritere table=champcritere.table bufnum=table.recno lret=[] st='' for num in list(sel): tra.calc(champcritere.value, champvaleur.value) table.recno=num table.recno=bufnum return(tra.resultat()) @staticmethod def enum(sel, champ): """ Liste les valeurs présentent pour un champ d'une table, et retourne un dict des valeurs, avec le nombre d'apparitions (+nb le nb total) """ table=champ.table bufnum=table.recno d={} for num in list(sel): table.recno=num d[champ.value] = d.setdefault(champ.value,0)+1 table.recno=bufnum return d,len(sel) @staticmethod def enumcallback(sel, champ, fonction): """ comme enum, sauf appel d'une fonction par callback, pour déterminer le critère(clef) et la quantité un dict des valeurs, avec la somme des quantités (+nb le nb total) """ table=champ.table bufnum=table.recno d={} for num in list(sel): table.recno=num valeur,qte = fonction(champ.value) d[valeur] = d.setdefault(valeur,0)+qte table.recno=bufnum return d,len(sel) class listselect(object): @staticmethod def visu(l, *champs): table=champs[0].table for num in l: table.recno=num for c in champs: print c.value, pass print @staticmethod def recuplist(l, *champs): table=champs[0].table lret=[] for num in l: table.recno=num llig=[] for c in champs: llig.append(c.value) lret.append(llig) return lret @staticmethod def eexcel(l, feuille, lig, col,*champs): """ Exportation d'une liste dans une feuille Excel, à l'emplacement {lig,col} pxexcel doit être présent """ table=champs[0].table lsorti=[] for num in l: table.recno=num ltmp=[] for c in champs: ltmp.append(c.value) ltmp.append(num) lsorti.append(ltmp) lsorti.sort() import pxexcel x=pxexcel.exl() time.sleep(0.05) x.open(feuille,True) x.selActiveCelluleA(lig,col) x.setLRange(lsorti,lig,col) x.visible() x.premierplan() #return lsorti #return [ l[-1] for l in lsorti] class journal(object): def __init__(self, dossier='', nom='defaut'): self.jr=[] self.name=nom self.dossier=dossier self.utilisateur=os.environ['USERNAME'] self.poste=os.environ['COMPUTERNAME'] self.actif=True def activer(self, mode=True): self.actif=mode def vide(self): self.jr=[] def enregistre(self, mode=None): """ enregistre """ if self.actif: f=open(self.dossier+self.name+'.jrnl','wb') cPickle.dump(self.jr,f,cPickle.HIGHEST_PROTOCOL) f.close() def charge(self): """ ouvre (lit) """ if self.actif: try: if os.path.isfile(self.dossier+self.name+'.jrnl'): f=open(self.dossier+self.name+'.jrnl','rb') self.jr = cPickle.load(f) f.close() else: self.jr=[] except: self.actif=False def add(self,champ,num,quoi,nouvaleur): if quoi=="UPD": #print 186,quoi,champ,champ.name self.jr.append([time.time(),champ.name,champ.table.recno,quoi,nouvaleur]) else: self.jr.append([time.time(),champ.name,num,quoi,None]) def visu(self): print print "Longueur du journal :",len(self.jr) print for l in self.jr: datim=time.strftime("%d.%m.%y %H:%M:%S", time.gmtime(l[0])) if l[3]=='UPD': print datim,l[3], l[1].ljust(12),l[2],l[4] pass else: print datim,l[3], l[1].ljust(12),l[2] pass class champ(object): def __init__(self, name=None, joint=None, index=False, trigbeforeupdate=None, trigafterupdate=None, mini=None, maxi=None, uniq=None, long=None): self.name=name self.table=None self.base=dfb_basedefaut self.joint=joint if index: self.idx=idx() self.idx.champ=self self.trigbeforeupdate=trigbeforeupdate self.trigafterupdate=trigafterupdate self.contraintemini=mini self.contraintemaxi=maxi if uniq: self.contrainteuniq=uniq if long>0: self.contraintelong=long self.clabel={} self.cexplication={} self.format=0 self.largeur=120 def label(self, langue="FR"): if langue in self.clabel: return self.clabel[langue] else: return self.clabel[langue] def explication(self, langue="FR"): if langue in self.cexplication: return self.cexplication[langue] else: return self.cexplication[langue] def setclabel(self, **kw): for k,v in kw.items(): self.clabel[k]=v def setcexplication(self, **kw): for k,v in kw.items(): self.cexplication[k]=v def test(self): #print; print; d=globals().keys() for i in d: #print i pass def fromrecord(self,recno=None): if not recno: recno=self.table.recno return self.table.rec[recno][self.position] def fromsearch(self, champ, valeur): temprec=champ.idx.searchexact(valeur) if temprec: return self.fromrecord(temprec) def triggerbeforeupdate(self, num, champ, dataold, datanew): """ déclencheur appelé avant modification (au niveau du champ) Si retour True ça continue Si retour False, annulation de la modif """ result=True if self.trigbeforeupdate: self.trigbeforeupdate(num, champ, dataold, datanew) return result def triggerafterupdate(self, num, champ, dataold, datanew): """ déclencheur appelé après modification (au niveau du champ) """ self.base.journal.add(champ,num,'UPD',datanew) if self.trigafterupdate: self.trigafterupdate(num, champ, dataold, datanew) def contrainte(self,datanew): flag=0 if not(self.contraintemini is None): if datanewself.contraintemaxi: flag=1 if self.contraintelong>0: if len(datanew)>self.contraintelong: flag=1 return flag def contrainteapplique(self,num,dataold,datanew): flag=0 if not(self.contraintemini is None): if datanewself.contraintemaxi: datanew=self.contraintemaxi flag=1 if self.contraintelong>0: if len(datanew)>self.contraintelong: datanew=datanew[:self.contraintelong] flag=1 return datanew def __getattr__(self, name): if name=='value': try: recno=self.table.recno #print 676,recno,self.joint,self.table.rec[recno][self.position] if self.joint: recjointure=self.table.rec[recno][self.position] if recjointure: #ligne suivante inutile, depuis jointure par __getattr__ ? #self.joint.table.recno=recjointure return self.joint.table.rec[recjointure][self.joint.position] else: if recno: return self.table.rec[recno][self.position] except: return None def __setattr__(self, name, valeur, mode='modif'): if name=='value': recno=self.table.recno oldval=self.value flag=self.contrainte(valeur) if flag>0: valeur=self.contrainteapplique(recno,oldval,valeur) if self.triggerbeforeupdate(recno,self,oldval,valeur): #print 373 if self.joint and (valeur != u"") and (valeur is not None): #import ponx #ponx.msginfo(694,valeur) recjointure=self.joint.idx.searchexact(valeur) #ponx.msginfo(696,recjointure) #ponx.msginfo(recno,self.position) #print 376 if recjointure: #rendu inutile par __getattr__ ? #self.joint.table.recno=recjointure #print 380, recjointure self.table.rec[recno][self.position]=recjointure else: raise MyError("Jointure error ("+self.table.name+")") else: while self.position>=len(self.table.rec[recno]): self.table.rec[recno].append(None) self.table.rec[recno][self.position]=valeur if self.idx: if self.base.idxactive: if mode=='ins': self.idx.ins(newval,recno) else: self.idx.change(oldval,recno,valeur) self.triggerafterupdate(recno,self,oldval,valeur) else: return False else: object.__setattr__(self, name, valeur) return True def getvalue(self): try: recno=self.table.recno if self.joint: recjointure=self.table.rec[recno][self.position] if recjointure: #ligne suivante inutile, depuis jeointure par __getattr__ ? #self.joint.table.recno=recjointure return self.joint.table.rec[recjointure][self.joint.position] else: if recno: return self.table.rec[recno][self.position] except: return None class champbuf(object): """ objet pour mémorisation des valeurs d'un champ, utilisé pour remplir un databuf """ def __init__(self, champ,value,recno): self.champ=champ self.value=value self.recno=recno class databuf(object): """ objet destiné à mémoriser les valeurs courantes des champs. Chaque nom de champ est une instance de champbuf, avec, comme attributs : - .value = valeur - .champ = lien vers le champ - .recno = num.record d'où vient la valeur """ def __init__(self): pass def visu(self): pass class base(object): def __init__(self,dossier='', nomjournal=None): global dfb_basedefaut dfb_basedefaut=self self.dchamp={} self.lchamp=[] self.ltable=[] self.idxactive=True self.dossier=dossier if self.dossier!='': if not self.dossier.endswith('\\'): self.dossier+='\\' self.nomjournal="defaut" if nomjournal is not None: self.nomjournal=nomjournal self.journal=journal(self.dossier,self.nomjournal) else: self.journal=journal(self.dossier,self.nomjournal) self.journal.activer(False) def localbuf(self): """ Mémorise un ensemble des valeurs horizontales (de tous les enregistrements courants) retourne un objet de type databuf , qui contient (des 'champbuf') les valeurs et un lien vers le champ. Cela peut être très utile dans les environnement multithreadés, comme les serveurs Web """ buffer=databuf() for t in self.ltable: for c in t.champl: setattr(buffer,c.name,champbuf(c,c.value,t.recno)) return buffer def charge(self,dossier='',mode=None): if dossier=='': self.dossier=os.getcwd()+'\\' else: self.dossier=dossier if self.dossier!='': if not self.dossier.endswith('\\'): self.dossier+='\\' for t in self.ltable: #print t.name, try: t.charge(mode) #print "OK" except: #print "" pass self.journal.charge() def close(self): for t in self.ltable: t.close() def nameponxv(self): for t in self.ltable: t.nameponxv() def rollback(self): self.charge() self.journal.charge() def enregistre(self, mode=None): for t in self.ltable: t.enregistre(mode) self.journal.enregistre(mode) def commit(self, mode=None): self.enregistre(mode) #self.journal.enregistre(mode) #déporté dans enregistre() def idxactivate(self, flag=True): self.idxactive=flag class idx(object): """ index """ def __init__(self): self.clef=[] self.recno=[] def ins(self,valeur,recno): position=bisect.bisect_right(self.clef,valeur) self.clef.insert(position,valeur) self.recno.insert(position,recno) def delete(self,valeur,recno=None): if len(self.clef)==0: return position=bisect.bisect_left(self.clef,valeur) if not recno: recno=self.recno[position] while self.clef[position]==valeur: if self.recno[position]==recno: del self.clef[position] del self.recno[position] return if position>=len(self.clef)-1: return position+=1 def change(self,oldvaleur,recno,newvaleur): self.delete(oldvaleur,recno) self.ins(newvaleur,recno) def searchexact(self, pvaleur): if len(self.clef)>0: valeur=pvaleur if type(self.clef[0]) != type(valeur): if type(self.clef[0]) is unicode: try: valeur=unicode(pvaleur) except: pass else: try: valeur=int(pvaleur) except: pass """ if type(self.clef[0]) != type(valeur): import ponx ponx.msginfo(type(self.clef[0]),type(valeur)) print "Différence de type clef/valeur :",type(self.clef[0]),type(valeur) """ try: position=bisect.bisect_left(self.clef,valeur) except: pass if position>=0 and position0: for k,j in self.jointd.iteritems(): if len(self.rec[valeur])>0: #if self.rec[valeur][j[0]] is not None: # j[2].recno=self.rec[valeur][j[0]] #recno(joint) = colonne_de_départ.value try: if self.rec[valeur][j[0]] is not None: j[2].recno=self.rec[valeur][j[0]] #recno(joint) = colonne_de_départ.value except: pass try: object.__setattr__(self, name, valeur) except: print 1241,"erreur" def secondaire(self, prefix='a_', dossier=None, fichier=None): lch=[] code='' code2='' code3='' for ch in self.champl: exec("global "+ch.name,globals(),globals()) exec(ch.name+"=champ()",globals(),globals()) tmpch=eval(ch.name) if ch.joint is not None: setattr(tmpch,"joint",ch.joint) lch.append(ch.name) code += ch.name+'.name="'+ch.name+'"\n' code2 += 'ctemp.'+ch.name+'.name="'+ch.name+'"\n' code3 += 'ctemp.'+ch.name+'.table=ctemp\n' exec(code,globals(),globals()) exec("ctemp=table2("+",".join(lch)+")",globals(),globals()) for ch in self.champl: setattr(ctemp,ch.name,eval(ch.name)) exec(code2,globals(),globals()) exec(code3,globals(),globals()) if fichier is None: ctemp.fichier=prefix.self.fichier else: ctemp.fichier=fichier ctemp.metfichier(dossier=dossier,fichier=fichier) return ctemp def infojoint(self): print print 'Jointures de la table',self.name for k,j in self.jointd.iteritems(): print ' ',k.name,' colonne:',j[0],' champ:',j[1].name,' table:',j[2].name pass def infotable(self): pass print print 'Table',self.name print ' fichier ',self.fichier print ' nb champs ',self.nbcol print ' nb joints ',len(self.jointd) print ' nb index ',len(self.idxd) print ' nb records',len(self.rec) def infochamp(self): print print 'Champs de la table',self.name for c in self.champl: print ' ',c.position,' \t',c.name, if c.joint: print ' ; jointure =>',c.joint.name,'('+c.joint.table.name+')', pass if c.idx: print ' ; index: True' pass else: print pass def nameponxv(self): """ NE PEUT PAS MARCHER (car PonxV ne peut contenir des objets appelables) Affecte les champs d'une table à PonxV """ pass """ import ponx code='' for c in self.champl: setattr(ponx.vpublic,c.name,c) #c.value print """ def verifsimodif(self, mode=None): """ vérifié si une table a été modifiée depuis le dernier chargement (par un évènement extérieur) retourne True si la table a été modifiée ; False si non modifiée début de gestion optimiste des verrous """ if self.dermodif==os.stat(os.path.join(self.base.dossier,self.fichier))[stat.ST_MTIME]: return False else: return True def charge(self, mode=None): """ ouvre une table Normalement, cette fonction ne doit pas être appelée directement. """ if self.fichier=='': if mode=="ZIP": self.fichier=self.name+'.zip' elif mode=="BZ2": self.fichier=self.name+'.bz2' elif mode=="GZIP": self.fichier=self.name+'.gzip' else: mode="CDB" self.fichier=self.name+'.cdb' if mode is None: if self.fichier.endswith('.zip'): mode="ZIP" elif self.fichier.endswith('.bz2'): mode="BZ2" elif self.fichier.endswith('.gzip'): mode="GZIP" else: mode="CDB" if mode=="ZIP": if not self.fichier.endswith('.zip'): self.fichier=self.fichier[:-4]+'.zip' elif mode=="BZ2": if not self.fichier.endswith('.bz2'): self.fichier=self.fichier[:-4]+'.bz2' elif mode=="GZIP": if not self.fichier.endswith('.gzip'): self.fichier=self.fichier[:-5]+'.gzip' if mode=="ZIP": if not os.path.isfile(self.base.dossier+'\\'+self.fichier): #shutil.copyfile(self.base.dossier+'\\vide.zip',self.base.dossier+'\\'+self.fichier) pass z=zipfile.ZipFile(os.path.join(self.base.dossier,self.fichier),"r") self.rec = cPickle.loads(z.read(self.fichier)) z.close() elif mode=="BZ2": import bz2 f=bz2.BZ2File(os.path.join(self.base.dossier,self.fichier),'r') zzz = f.read() self.rec = cPickle.loads(zzz) f.close() elif mode=="GZIP": import gzip f = gzip.open(os.path.join(self.base.dossier,self.fichier), 'rb') self.rec = cPickle.loads(f.read()) f.close() else: #if mode=="CDB": if not os.path.isfile(self.base.dossier+'\\'+self.fichier): #shutil.copyfile(self.base.dossier+'\\vide.cdb',self.base.dossier+'\\'+self.fichier) pass self.rec = cPickle.loads(open(self.base.dossier+'\\'+self.fichier,'rb').read()) """ stat.ST_CTIME date de création stat.ST_MTIME date de modification """ self.dermodif=os.stat(os.path.join(self.base.dossier,self.fichier))[stat.ST_MTIME] #print 1220,self.fichier,self.dermodif," ", #print time.strftime("%d.%m.%Y %H:%M:%S", time.localtime(self.dermodif)) self.reindexall() def chargezip(self): """ ouvre une table Normalement, cette fonction ne doit pas être appelée directement. """ if self.fichier=='': self.fichier=self.name+'.zip' if not os.path.isfile(self.base.dossier+'\\'+self.fichier): #shutil.copyfile(self.base.dossier+'\\vide.zip',self.base.dossier+'\\'+self.fichier) pass z=zipfile.ZipFile(self.base.dossier+self.fichier,"r") #info=z.getinfo(self.fichier) #bytes=z.read(self.fichier) #print 891,len(z.read(self.fichier)) self.rec = cPickle.loads(z.read(self.fichier)) z.close() self.reindexall() def rollback(self): self.base.rollback() def close(self): if self.tache is not None: while self.tache.isAlive(): time.sleep(0.1) def enregistre(self, mode=None): self.enrmode=mode if self.tache is not None: while self.tache.isAlive(): time.sleep(0.1) def enregistre_arriere(*par): """ enregistre une table Normalement, cette fonction ne doit pas être appelée directement. """ mode=self.enrmode if self.fichier=='': if mode=="ZIP": self.fichier=self.name+'.zip' elif mode=="BZ2": self.fichier=self.name+'.bz2' elif mode=="GZIP": self.fichier=self.name+'.gzip' else: mode="CDB" self.fichier=self.name+'.cdb' if mode is None: if self.fichier.endswith('.zip'): mode="ZIP" elif self.fichier.endswith('.bz2'): mode="BZ2" elif self.fichier.endswith('.gzip'): mode="GZIP" else: mode="CDB" if mode=="ZIP": if not self.fichier.endswith('.zip'): self.fichier=self.fichier[:-4]+'.zip' elif mode=="BZ2": if not self.fichier.endswith('.bz2'): self.fichier=self.fichier[:-4]+'.bz2' elif mode=="GZIP": if not self.fichier.endswith('.gzip'): self.fichier=self.fichier[:-4]+'.gzip' time.sleep(0.1) if mode=="ZIP": data=cPickle.dumps(self.rec,cPickle.HIGHEST_PROTOCOL) f=zipfile.ZipFile(os.path.join(self.base.dossier,self.fichier),'w',zipfile.ZIP_DEFLATED) f.writestr(self.fichier,data) f.close() elif mode=="BZ2": data=cPickle.dumps(self.rec,cPickle.HIGHEST_PROTOCOL) import bz2 f=bz2.BZ2File(os.path.join(self.base.dossier,self.fichier),'w') f.write(data) f.close() elif mode=="GZIP": import gzip data=cPickle.dumps(self.rec,cPickle.HIGHEST_PROTOCOL) f = gzip.open(os.path.join(self.base.dossier,self.fichier), 'wb') f.write(data) f.close() else: #if mode=="CDB": f=open(os.path.join(self.base.dossier,self.fichier),'wb') #cPickle.dump(self.rec,f) cPickle.dump(self.rec,f,cPickle.HIGHEST_PROTOCOL) f.close() self.base.journal.enregistre() time.sleep(0.1) #print self.name,u"enregistré." self.enrmode=mode self.dermodif=os.stat(os.path.join(self.base.dossier,self.fichier))[stat.ST_MTIME] self.tache=threading.Thread(target=enregistre_arriere, name="enregistrement_table") self.tache.setDaemon(True) self.tache.start() time.sleep(0.01) def enregistrezip(self): """ enregistre une table Normalement, cette fonction ne doit pas être appelée directement. """ #if self.fichier=='': # self.fichier=self.name+'.cdb' #f=open(os.path.join(self.base.dossier,self.fichier),'wb') #cPickle.dump(self.rec,f) data=cPickle.dumps(self.rec,cPickle.HIGHEST_PROTOCOL) if self.fichier=='': self.fichier=self.name+'.zip' self.fichier = self.fichier[:-4]+".zip" f=zipfile.ZipFile(os.path.join(self.base.dossier,self.fichier),'w',zipfile.ZIP_DEFLATED) f.writestr(self.fichier,data) f.close() def commit(self): self.base.commit() def alter(self, champ,addafter=None,addbefore=None,remove=False,default=None): """ Attn: pas de contraintes pour les champs alter mais index et jointures sont gérés Exemples d'utilisation: table.alter(famille, addafter=prix) table.alter(sousfamille, addafter=famille, default='aluminium') table.alter(famille, addbefore=conditionnement) table.alter(famille, remove=True) """ if (not(addafter is None)) or (not(addbefore is None)): if not(addafter is None): position=addafter.position+1 if not(addbefore is None): position=addbefore.position for c in self.champd: if self.champd[c]>=position: self.champd[c]+=1 if c.position>=position: c.position+=1 self.champd[champ]=position if champ.joint: for j in self.jointd: if self.jointd[champ][0]>=position: self.jointd[champ][0]+=1 self.jointd[champ]=(position,champ.joint,champ.joint.table) #colonne,champjoint,tablejoint self.champl.insert(position,champ) self.nbcol+=1 champ.table=self champ.position=position if champ.idx: self.idxd[champ]=champ.idx self.idxl.insert(position,self.idxd[champ]) else: self.idxl.insert(position,None) self.recvide=[None]*(self.nbcol) if champ.joint: recjointure=champ.joint.idx.searchexact(default) if recjointure: default=recjointure else: default=None for r,l in self.rec.iteritems(): l.insert(position,default) if champ.idx: self.reindexall() if remove: position=champ.position for c in self.champd: if self.champd[c]>position: self.champd[c]-=1 if c.position>position: c.position-=1 del self.champd[champ] if champ.joint: for j in self.jointd: if self.jointd[champ][0]>position: self.jointd[champ][0]-=1 del self.jointd[champ] del self.champl[position] self.nbcol-=1 champ.table=None champ.position=None if champ.idx: del self.idxd[champ] del self.idxl[position] self.recvide=[None]*(self.nbcol) for r,l in self.rec.iteritems(): del l[position] if champ.idx: del champ.idx def triggerbeforeinsert(self, num, data): """ déclencheur appelé avant insertion (au niveau record) data peut être vide Si retour True ça continue Si retour False, annulation de l'insertion """ self.base.journal.add(self,num,'INS',None) return True def triggerbeforedelete(self, num, data): """ déclencheur appelé avant suppression (au niveau record) Si retour True ça continue Si retour False, annulation de la suppression """ return True def triggerafterdelete(self, num, data): """ déclencheur appelé après suppression (au niveau record) """ self.base.journal.add(self,num,'DEL',None) pass def contrainte(self,num,lval): flag=True for c in self.champd: if c.contrainteuniq: if c.idx.searchexact(lval[c.position]): flag=False #import ponx #ponx.msginfo(1665,"Existe deja : "+repr(lval[c.position])+" champ "+c.name) raise MyError("Unique violation error") pass """ if flag: if c.joint: if c.idx.searchexact(lval[c.position]): flag=False pass """ return flag def moveto(self,num=0): self.recno=num def end(self): self.moveto(len(self.rec)) def set(self, num=None, *valeurs): """ affecte toutes les valeurs d'un coup """ if num is None: num=self.recno lval=list(valeurs) flag=True self.mode='modif' if not self.rec.has_key(num): self.mode='ins' try: flag=self.contrainte(num,lval) except MyError as err: flag=False import ponx #ponx.msginfo("1780 "+str(err.value),repr(lval)) #raise MyError(err.value) # à revoir ! +++ important except: flag=False if flag: flag=self.triggerbeforeinsert(num, lval) if flag: if self.mode=='ins': self.rec[num]=self.recvide[:] if len(self.rec[num])==0: self.rec[num]=self.recvide[:] self.recno=num for col in range(len(valeurs)): self.champl[col].value=lval[col] """ try: self.champl[col].value=lval[col] except MyError as err: flag=False print print "set (1601)",err.value,repr(lval[col])," col.",col print raise MyError(err.value) except: flag=False """ #print 1808,num,self.rec[num] self.mode='' return flag def append(self, *valeurs): """ affecte toutes les valeurs d'un coup, avec un record nouveau """ if len(self.rec)>0: numax=max(self.rec.keys()) if type(numax) is unicode: self.suppkeyzero() numax=max(self.rec.keys()) if numax is None: num=1 else: num=max(self.rec.keys())+1 else: num=1 self.recno=num if self.set(num,*valeurs): return num else: try: del self.rec[num] except: pass return False def suppkeyzero(self, *valeurs): """ supprime les keys non int """ nb=0 for i in self.rec.keys(): if not isinstance(i,int): del(self.rec[i]) nb+=1 return nb def suppjointrompu(self): """ supprime les jointures rompues """ lret=[] if len(self.jointd)>0: lj=[] for col, j in self.jointd.items(): jcol,jchamp,jtable=j # col.position (position dans la table de départ) # jcol (n° rec de la table de réf) # jtable (table de référence) # jchamp.position (col/position dans la table de référence) lj.append((col.position,jcol,jchamp.position,jtable)) l_a_supprimer=[] for num,recdata in self.rec.iteritems(): for colposition, jcol, position, jtable in iter(lj): try: recjointure=recdata[colposition] if recjointure is None: pass else: if not recjointure in jtable.rec: #print 1782,recjointure, "absent" l_a_supprimer.append(num) break else: if len(jtable.rec[recjointure])0: num=max(self.rec)+1 else: num=1 if self.set(num,*valeurs): nbret+=1 return nbret def llistrawappend(self, lval): """ utilise une liste de liste, pour affecter toutes les valeurs d'un coup, avec un record nouveau pour chaque liste Cela est fait d'une manière "brute" (raw), sans contrôles, pour accélérer les choses """ nbret=0 if len(self.rec)>0: num=max(self.rec) else: num=0 numd=num for valeurs in lval: num+=1 self.rec[num]=valeurs return num-numd def delete(self, num=None): """ supprime un record, par son numero """ if not num: num=self.recno self.recno=num if self.rec.has_key(num): lval=self.rec[num] flag=self.triggerbeforedelete(num,lval) if flag: for col in range(self.nbcol): champ=self.champl[col] if champ.idx: champ.idx.delete(champ.value,num) #champ.value=None del self.rec[num] self.triggerafterdelete(num,lval) nouvnum=self.recno+1 if not self.rec.has_key(nouvnum): try: nouvnum=self.rec.keys()[0] except: nouvnum=0 self.recno=nouvnum def deleteraw(self, num=None): """ supprime un record sans controles ni intégrité """ #if not num: # num=self.recno self.recno=num if self.rec.has_key(num): del self.rec[num] self.recno=1 nouvnum=self.recno+1 if not self.rec.has_key(nouvnum): nouvnum=self.rec.keys()[0] self.recno=nouvnum def empty(self): # vide une table (sans controles) self.rec=collections.defaultdict(list) self.recno=None self.reindexall() def nettoievides(self): """ supprime d'éventuels records vides """ buffer=self.recno nb=0 for num in self.rec.keys(): if self.rec[num]==[]: nb=nb+1 #print 908,"Vide:",num self.deleteraw(num) self.enregistre() #self.moveto(buffer) self.moveto(1) return(nb) def reindex(self, champ): """ (re)-crée complètement un index 'force' (pas de controle) """ #import ponx #ponx.msginfo("905",str(champ.name)) #ponx.msginfo("905",str(champ.position)) if champ.joint is None: li=[[self.rec[id][champ.position],id] for id in self.rec if not (id is None) and len(self.rec[id])>1] """ li=[] for id in self.rec: try: if (id is not None) and len(self.rec[id])>0: li.append([self.rec[id][champ.position],id]) except: print 1663,len(self.rec[id]) pass """ else: joint=champ.joint jtable=joint.table jposition=joint.position def vjointed(id,champ): pointeur = self.rec[id][champ.position] valeur=jtable.rec[pointeur][jposition] return valeur li=[[vjointed(id,champ),id] for id in self.rec if not (id is None)] li.sort() champ.idx.clef=[ l[0] for l in li] champ.idx.recno=[ l[1] for l in li] def reindexall(self): """ (re)-crée complètement tous les index de la table """ for champ in self.champd: if champ.idx: self.reindex(champ) def visu(self, num=None): """ visu (print) le contenu de tous les champs d'un enregistrement """ buffer=self.recno if num: self.recno=num else: num=self.recno print '--',num,'--' for i in range(self.nbcol): print self.champl[i].value, pass print self.recno=buffer def visurc(self, num=None): """ visu (print) le contenu de tous les champs d'un enregistrement, avec, pour chaque ligne, n° colonne, nom du champ, valeur """ buffer=self.recno if num: self.recno=num else: num=self.recno print '--',num,'--' for i in range(self.nbcol): print i,self.champl[i].name,':',self.champl[i].value pass print self.recno=buffer def visurecchamps(self, num=None): buffer=self.recno if num: self.recno=num else: num=self.recno print '==',num,'--(table '+self.name+')-- ' for i in range(self.nbcol): print i,self.champl[i].name,":",self.champl[i].value,' \t ',type(self.champl[i].value) pass print self.recno=buffer def visuall(self): buffer=self.recno for num in self.rec.keys(): self.recno=num print '--',num,':\t', for i in range(self.nbcol): print self.champl[i].value, pass print self.recno=buffer def textfileall(self,f): buffer=self.recno for num in self.rec.keys(): self.recno=num txt='-- '+str(num)+" " for i in range(self.nbcol): txt += " "+str(self.champl[i].value) pass f.write(txt+"\r\n") self.recno=buffer def rlistall(self): """ retourne une liste de listes de toutes les valeurs de la table ; une liste par record, inclue dans une liste de tous les records. """ buffer=self.recno lret=[] for num in self.rec.keys(): self.recno=num l=[] for i in range(self.nbcol): l.append(self.champl[i].value) #l.append(num) #modif du 12.08.2011 lret.append(l) self.recno=buffer return lret def rlistallnum(self): """ retourne une liste de listes de toutes les valeurs de la table ; une liste par record, inclue dans une liste de tous les records. 12.08.2011 : ajout du num enregistrement en fin de chaque liste/record ("pour chaque ligne") """ buffer=self.recno lret=[] for num in self.rec.keys(): self.recno=num l=[] for i in range(self.nbcol): #print 2077,self.champl[i].value l.append(self.champl[i].value) l.append(num) #modif du 12.08.2011 lret.append(l) self.recno=buffer return lret def rlistallnumufast(self): """ retourne une liste de listes de toutes les valeurs de la table ; une liste par record, inclue dans une liste de tous les records. + num enregistrement en fin de chaque liste/record ("pour chaque ligne") """ """ buffer=self.recno lret=[] if len(self.jointd)>0: for num in self.rec.keys(): ltmp= self.rec[num]+[None]*(self.nbcol-len(self.rec[num]))+[num] for col, j in self.jointd.items(): jcol,jchamp,jtable=j recjointure=self.rec[num][jcol] #jcol (n° rec de la table de réf) if recjointure is not None: try: ltmp[col.position]=jtable.rec[recjointure][0] #jtable (table de référence) except: pass lret.append(ltmp) """ """ buffer=self.recno lret=[] if len(self.jointd)>0: lj=[] for col, j in self.jointd.items(): jcol,jchamp,jtable=j # jcol (n° rec de la table de réf) # jtable (table de référence) # jchamp.position (col/position dans la table de référence) lj.append((col.position,jcol,jchamp.position,jtable)) for num in self.rec.keys(): ltmp=self.rec[num]+[None]*(self.nbcol-len(self.rec[num]))+[num] for colposition, jcol, position, jtable in lj: recjointure=self.rec[num][jcol] if recjointure is not None: try: ltmp[colposition]=jtable.rec[recjointure][position] except: pass lret.append(ltmp) else: for num in self.rec.keys(): lret.append(self.rec[num]+[None]*(self.nbcol-len(self.rec[num]))+[num]) """ buffer=self.recno lret=[] if len(self.jointd)>0: lj=[] for col, j in self.jointd.items(): jcol,jchamp,jtable=j # col.position (position dans la table de départ) # jcol (n° rec de la table de réf) # jtable (table de référence) # jchamp.position (col/position dans la table de référence) lj.append((col.position,jcol,jchamp.position,jtable)) for num,recdata in self.rec.iteritems(): ltmp=recdata+[None]*(self.nbcol-len(recdata))+[num] for colposition, jcol, position, jtable in iter(lj): try: recjointure=recdata[colposition] if recjointure is None: pass else: try: ltmp[colposition]=jtable.rec[recjointure][jchamp.position] except: pass except: pass lret.append(ltmp) else: for num,recdata in self.rec.iteritems(): lret.append(recdata+[None]*(self.nbcol-len(recdata))+[num]) #for num in self.rec.iterkeys(): # lret.append(self.rec[num]+[None]*(self.nbcol-len(self.rec[num]))+[num]) self.recno=buffer return lret def export(self, sel): """ exporte dans le fichier self.name+".exp" la liste de listes de toutes les valeurs de la table pour la sélection (liste d'enregistrements) sel une liste par record, inclue dans une liste de tous les records. """ buffer=self.recno lret=[] if len(self.jointd)>0: lj=[] for col, j in self.jointd.items(): jcol,jchamp,jtable=j # col.position (position dans la table de départ) # jcol (n° rec de la table de réf) # jtable (table de référence) # jchamp.position (col/position dans la table de référence) lj.append((col.position, jcol, jchamp.position, jtable)) for num in sel: self.recno=num recdata = self.rec[num] #print 2277,recdata ltmp=recdata+[None]*(self.nbcol-len(recdata)) for colposition, jcol, position, jtable in iter(lj): try: recjointure=recdata[colposition] if recjointure is None: pass else: try: ltmp[colposition]=jtable.rec[recjointure][jchamp.position] except: pass except: pass lret.append(ltmp) else: for num,recdata in self.rec.iteritems(): lret.append(recdata+[None]*(self.nbcol-len(recdata))) # sortir dans self.name+".exp" #print 2299,"-"*66 fichierexp=self.base.dossier+'\\'+self.name+'.exp' f=open(fichierexp,'wb') cPickle.dump(lret,f,cPickle.HIGHEST_PROTOCOL) f.close() self.recno=buffer return fichierexp def scan(self, fcallback=None, chpanel=None): """ Déroule une table, selon - l'index du champ chpanel, - la selection chpanel - l'ordre par défaut, si chpanel is None et, pour chaque enregistrement : - retourne l'itérateur next, si fonction fcallback is None - sinon, appelle la fonction fcallback avec deux paramètres : table et num.record """ buffer=self.recno if chpanel is None: panel=self.rec.keys() elif isinstance(chpanel,set): panel=list(chpanel) else: panel=[k for k in chpanel.idx.recno] if fcallback is None: def scan_interne_iter(tbl, panel): for num in panel: tbl.recno=num yield(num) return scan_interne_iter(self,panel) else: for num in panel: self.recno=num fcallback(self,num) self.recno=buffer def lmax(self, colonne=0): """ retourne la longueur maxi d'une colonne de données """ import types if self.champd.has_key(colonne): colonne=self.champd[colonne] maxi=0 for num,l in self.rec.iteritems(): maxi=max(maxi, len(repr(l[colonne]))) return maxi def cmax(self, colonne=0): import types if self.champd.has_key(colonne): colonne=self.champd[colonne] maxi=None for num,l in self.rec.iteritems(): try: maxi=max(maxi, l[colonne]) except: pass return maxi def cmin(self, colonne=0): import types if self.champd.has_key(colonne): colonne=self.champd[colonne] mini='zzz' for num,l in self.rec.iteritems(): try: mini=min(mini, l[colonne]) except: pass return mini def csum(self, colonne=0): import types if self.champd.has_key(colonne): colonne=self.champd[colonne] total=0.0 for num,l in self.rec.iteritems(): try: total=total+l[colonne] except: pass return total def cnb(self, colonne=0): import types if self.champd.has_key(colonne): colonne=self.champd[colonne] nb=0 for num,l in self.rec.iteritems(): nb+=1 return nb def cset(self, colonne=0, valeur=0): """ Affecte une valeur à toute une colonne (à un champ, pour toute la table) exemple: matable.cset("montant",0.0) attention : ignore les contraintes """ import types if self.champd.has_key(colonne): colonne=self.champd[colonne] flag=True while flag: flag=False for num,l in self.rec.iteritems(): try: l[colonne]=valeur except: if len(l)==0: flag=True if flag: self.nettoievides() def selectioncallback(self, s, func): for recno in s: func(recno,self.rec[recno]) def selectioncascade(self, schamp, **criteres): """ Comme selection(), sauf que les jointures sont mises à jour à chaque enregistrement de la table Retourne une sélection d'enregistrements répondant aux critères : EQU égal à NEQ non égal à (différent de) LSS inférieur à LEQ inférieur ou égal à GTR supérieur à GEQ supérieur ou égal à CNT contient... (string ou Unicode) REM Match (expression régulière) APP Appartient ('IN' List) s'il y a plusieurs critères, ils sont traités avec 'ET' (intersection) Exemples : tarticle.selection(quantite,GEQ=100,LSS=200) tarticle.selection(famille,EQU='boisson') """ for compar,crit in criteres.iteritems(): if compar=='REM': criteres[compar]=re.compile(crit,re.IGNORECASE) buffer=self.recno lret=[] for num,l in self.rec.iteritems(): self.recno=num valeur=schamp.value gflag=True for compar,crit in criteres.iteritems(): flag=False if not (valeur is None): if compar=='EQU': flag = valeur==crit elif compar=='NEQ': flag = valeur!=crit elif compar=='LSS': flag = valeurcrit elif compar=='GEQ': flag = valeur>=crit elif compar=='CNT': if valeur!='': flag = (crit in valeur) elif compar=='APP': flag = (valeur in crit) elif compar=='REM': if valeur!='': if crit.search(valeur) is None: flag=False else: flag=True gflag = gflag and flag if gflag: lret.append(num) self.recno=buffer return set(lret) def selection(self, colonne, **criteres): """ Retourne une sélection d'enregistrements répondant aux critères : EQU égal à NEQ non égal à (différent de) LSS inférieur à LEQ inférieur ou égal à GTR supérieur à GEQ supérieur ou égal à CNT contient... (string ou Unicode) REM Match (expression régulière) APP Appartient ('IN' List) s'il y a plusieurs critères, ils sont traités avec 'ET' (intersection) Exemples : tarticle.selection(quantite,GEQ=100,LSS=200) tarticle.selection(famille,EQU='boisson') """ for compar,crit in criteres.iteritems(): if compar=='REM': criteres[compar]=re.compile(crit,re.IGNORECASE) if self.champd.has_key(colonne): colonne=self.champd[colonne] if self.champl[colonne].joint: joint=self.champl[colonne].joint jtable=joint.table position=joint.position lret=[] for num,l in self.rec.iteritems(): try: tempnumrec=l[colonne] except: continue if len(jtable.rec[tempnumrec])<=position: continue try: valeur=jtable.rec[tempnumrec][position] except: #print 2173,len(jtable.rec[tempnumrec]), #print jtable.rec[tempnumrec] continue gflag=True for compar,crit in criteres.iteritems(): flag=False if not (valeur is None): if compar=='EQU': flag = valeur==crit elif compar=='NEQ': flag = valeur!=crit elif compar=='LSS': flag = valeurcrit elif compar=='GEQ': flag = valeur>=crit elif compar=='CNT': if valeur!='': flag = (crit in valeur) elif compar=='APP': flag = (valeur in crit) elif compar=='REM': if valeur!='': if crit.search(valeur) is None: flag=False else: flag=True gflag = gflag and flag if gflag: lret.append(num) return set(lret) else: lret=[] #import ponx #ponx.msginfo('chata 1330',str(colonne)) for num,l in self.rec.iteritems(): try: valeur=l[colonne] gflag=True for compar,crit in criteres.iteritems(): flag=False if not (valeur is None): if compar=='EQU': flag = valeur==crit elif compar=='NEQ': flag = valeur!=crit elif compar=='LSS': flag = valeurcrit elif compar=='GEQ': flag = valeur>=crit elif compar=='CNT': if valeur!='': flag = (crit in valeur) elif compar=='APP': flag = (valeur in crit) elif compar=='REM': if valeur!='': if crit.search(valeur) is None: flag=False else: flag=True gflag = gflag and flag if gflag: lret.append(num) except: pass return set(lret) def selectionselection(self, select, colonne, **criteres): """ Comme selection( sauf que la table est parcourue d'après une sélection exemple : s1=lecrit.selection(ltiers, EQU=choix_tiers) sel=lecrit.selectionselection(s1, llettrage, LSS="A") """ for compar,crit in criteres.iteritems(): if compar=='REM': criteres[compar]=re.compile(crit,re.IGNORECASE) if self.champd.has_key(colonne): colonne=self.champd[colonne] if self.champl[colonne].joint: joint=self.champl[colonne].joint jtable=joint.table position=joint.position lret=[] for numsel in select: num=numsel l=self.rec[num] try: tempnumrec=l[colonne] except: continue if len(jtable.rec[tempnumrec])<=position: continue try: valeur=jtable.rec[tempnumrec][position] except: continue gflag=True for compar,crit in criteres.iteritems(): flag=False if not (valeur is None): if compar=='EQU': flag = valeur==crit elif compar=='NEQ': flag = valeur!=crit elif compar=='LSS': flag = valeurcrit elif compar=='GEQ': flag = valeur>=crit elif compar=='CNT': if valeur!='': flag = (crit in valeur) elif compar=='APP': flag = (valeur in crit) elif compar=='REM': if valeur!='': if crit.search(valeur) is None: flag=False else: flag=True gflag = gflag and flag if gflag: lret.append(num) return set(lret) else: lret=[] for numsel in select: num=numsel l=self.rec[num] try: valeur=l[colonne] gflag=True for compar,crit in criteres.iteritems(): flag=False if not (valeur is None): if compar=='EQU': flag = valeur==crit elif compar=='NEQ': flag = valeur!=crit elif compar=='LSS': flag = valeurcrit elif compar=='GEQ': flag = valeur>=crit elif compar=='CNT': if valeur!='': flag = (crit in valeur) elif compar=='APP': flag = (valeur in crit) elif compar=='REM': if valeur!='': if crit.search(valeur) is None: flag=False else: flag=True gflag = gflag and flag if gflag: lret.append(num) except: pass return set(lret) def selectionequ(self, colonne, **criteres): """ comme selection, sauf optimisé pour un seul EQU """ if self.champd.has_key(colonne): colonne=self.champd[colonne] crit = criteres['EQU'] if self.champl[colonne].joint: joint=self.champl[colonne].joint jtable=joint.table position=joint.position lret=[] for num,l in self.rec.iteritems(): try: tempnumrec=l[colonne] except: continue if len(jtable.rec[tempnumrec])<=position: continue try: if jtable.rec[tempnumrec][position]==crit: lret.append(num) except: #print 2266,len(jtable.rec[tempnumrec]),tempnumrec,position #print jtable.rec[tempnumrec] continue return set(lret) else: lret=[] for num,l in self.rec.iteritems(): try: if l[colonne]==crit: lret.append(num) except: pass return set(lret) def selectioninterval(self, colonne, mini, maxi): """ Retourne une sélection d'enregistrements mini>= et <=maxi Si c'est un champ de jointure, la jointure est jouée """ import types if self.champd.has_key(colonne): colonne=self.champd[colonne] if self.champl[colonne].joint: joint=self.champl[colonne].joint jtable=joint.table position=joint.position lret=[] for num,l in self.rec.iteritems(): try: #pour le cas d'échec de la jointure valeur=jtable.rec[l[colonne]][position] except: valeur=l[colonne] if valeur>=mini and valeur<=maxi: lret.append(num) return set(lret) else: return set((num for num,l in self.rec.iteritems() if l[colonne]>=mini and l[colonne]<=maxi)) def selectionall(self): """ Retourne tous les enregistrements """ import types lret=[] for num,l in self.rec.iteritems(): lret.append(num) return set(lret) def selectionjointure(self, colonne, selection): """ Retourne une sélection d'enregistrements pour lesquels le champ de jointure appartient à une (autre) sélection donnée. Comme un champ est unique, donner le champ de jointure suffit pour retrouver la table et la jointure """ import types if self.champd.has_key(colonne): colonne=self.champd[colonne] return set((num for num,l in self.rec.iteritems() if l[colonne] in selection)) def idxintervalle(self,champ,mini,maxi): return self.idxd[champ].intervalle(mini,maxi) def idxselectionintervalle(self,champ,mini,maxi): return self.idxd[champ].selectionintervalle(mini,maxi) def idxlistselectintervalle(self,champ,mini,maxi): return self.idxd[champ].listselectintervalle(mini,maxi) def indeximmediat(self, colonne): """ TODO à revoir """ import types if self.champd.has_key(colonne): colonne=self.champd[colonne] return [ [l[colonne],num] for num,l in self.rec.iteritems()] def incfermeture(self, colonne, numchampcherche, *champs, **criteres): """ sélectionne des enregistrements, en fait une liste, et retourne une fonction de recherche incrémentale sur cette liste (utilisation d'une fermeture). colonne est le champ de sélection initiale **criteres est un dictionnaire des critères de la sélection initiale numchampcherche est le numero de la colonne, dans la liste résultat, pour la recherche incrémentale En pratique, il ne s'agit pas d'une vrai recherche incrémentale, mais plutôt d'une recherche sur un résultat intermédiaire mémorisé (par la fermeture) Cela permet d'annuler une recherche, pas à pas. La fermeture permet d'économiser une instance d'une classe, et simplifie le code. """ sel=self.selection(colonne, **criteres) mlist=chata.selection.recuplist(sel,*champs) if len(mlist)>0: mlist.sort() def calc(expression): """ expression est une expression régulière pour le recherche incrémentale """ try: robj=re.compile(expression,re.IGNORECASE) return([m for m in mlist if robj.search(m[numchampcherche])]) except: return([]) """ try: robj=re.compile(expression,re.IGNORECASE) nb=0 for m in mlist: try: if robj.search(m[numchampcherche]): glist.append(m) nb+=1 except: pass except: nb=0 """ return glist return(calc) def incfermeture2(self, colonne, numchampcherche, *champs, **criteres): sel=self.selection(colonne, **criteres) mlist=chata.selection.recuplist(sel,*champs) if len(mlist)>0: mlist.sort() def calc(expression): """ expression est une expression régulière pour le recherche incrémentale """ try: robj=re.compile(expression,re.IGNORECASE) glist = [m for m in mlist if robj.search(m[numchampcherche])] except: glist = [] #return glist lret=[] for l in glist: s=u"" for i in l: if i is None: s += "|" elif isinstance(i, (basestring,unicode)): s += i+"|" else: s += unicode(i)+"|" lret.append(s[:-1]) return(lret) return(calc) class base2(object): def __init__(self,dossier=None,nomjournal=''): if dossier is None: self.dossier=os.getcwd() else: self.dossier=dossier if self.dossier!='': if not self.dossier.endswith('\\'): self.dossier+='\\' self.nomjournal=nomjournal self.journal=journal(self.dossier,self.nomjournal) class table2(table): def __init__(self, *champs): self.name=None self.fichier='' self.base=base2() ### NON ! pas pour les tables secondaires : dfb_basedefaut.ltable.append(self) self.rec=collections.defaultdict(list) self.recno=None self.idxd={} self.idxl=[] i=0 self.champd={} self.champl=[] self.jointd={} self.nbcol=0 self.mode=None for c in champs: c.table=self c.position=i self.champd[c]=i self.champl.append(c) if c.joint: self.jointd[c]=(i,c.joint,c.joint.table) #colonne,champjoint,tablejoint self.nbcol+=1 if c.idx: self.idxd[c]=c.idx self.idxl.append(self.idxd[c]) else: self.idxl.append(None) i+=1 self.recvide=[None]*(self.nbcol) self.tache=None def metfichier(self,dossier=None,fichier='chata_temp'): if dossier is None: dossier=os.getcwd() else: dossier=dossier if dossier!='': if not dossier.endswith('\\'): dossier += '\\' self.base.dossier=dossier self.fichier=fichier def ouvre(self): self.charge() self.base.journal.charge() def compar(self,champacomparer,champindex): lcommun=[] lplus=[] lval1=[] buffer=self.recno for num in self.rec.keys(): self.recno=num result=champindex.idx.searchexact(champacomparer.value) if result: lcommun.append(num) lval1.append(champacomparer.value) else: lplus.append(num) lval2=[] t=champindex.table for num in t.rec.keys(): t.recno=num lval2.append(champindex.value) lmanque=list(set(lval2)-set(lval1)) self.recno=buffer return(lcommun,lplus,lmanque)