# -*- coding: cp1252 -*- # # par Michel Claveau Informatique # http://mclaveau.com # GlobalPVersion='0.04a' """ 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 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 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, print @staticmethod def sort(sel, *champs): l=list(sel) 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() return [ l[-1] for l in lsorti] @staticmethod def recuplist(sel, *champs): l=list(sel) 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 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, 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 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 def test(self): print; print; d=globals().keys() for i in d: print i 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) """ if self.trigafterupdate: self.trigafterupdate(num, champ, dataold, datanew) def contrainte(self,datanew): flag=0 if self.contraintemini!=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 self.contraintemini!=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': 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] 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): if self.joint: recjointure=self.joint.idx.searchexact(valeur) if recjointure: #rendu inutile par __getattr__ ? #self.joint.table.recno=recjointure self.table.rec[recno][self.position]=recjointure else: return False else: 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,champ,oldval,valeur) else: return False else: object.__setattr__(self, name, valeur) return True class base(object): def __init__(self): global dfb_basedefaut dfb_basedefaut=self self.dchamp={} self.lchamp=[] self.ltable=[] self.idxactive=True def charge(self): for t in self.ltable: t.charge() def enregistre(self): for t in self.ltable: t.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, valeur): position=bisect.bisect_left(self.clef,valeur) if position>=0 and position0: for k,j in self.jointd.iteritems(): if j[0]>0: j[2].recno=self.rec[valeur][j[0]] #recno(joint) = colonne_de_départ.value object.__setattr__(self, name, valeur) 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 def infotable(self): 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+')', if c.idx: print ' ; index: True' else: print def charge(self): """ ouvre une table """ import cPickle if self.fichier=='': self.fichier=self.name+'.cdb' f=open(self.fichier,'rb') self.rec = cPickle.load(f) f.close() self.reindexall() def rollback(self): self.charge() def enregistre(self): """ enregistre une table """ import cPickle if self.fichier=='': self.fichier=self.name+'.cdb' f=open(self.fichier,'wb') cPickle.dump(self.rec,f) f.close() def commit(self): self.enregistre() def alter(self, champ,addafter=None,addbefore=None,remove=False,default=None): """ Attn: pas de ontraintes 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 addafter!=None or addbefore!=None: if addafter!=None: position=addafter.position+1 if addbefore!=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 """ 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) """ 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 pass return flag def moveto(self,num=0): self.recno=num def set(self, num=None, *valeurs): """ affecte toutes les valeurs d'un coup """ if not num: num=self.recno lval=list(valeurs) flag=True self.mode='modif' if not self.rec.has_key(num): self.mode='ins' flag=self.contrainte(num,lval) if flag: flag=self.triggerbeforeinsert(num, lval) if flag: if self.mode=='ins': self.rec[num]=self.recvide[:] self.recno=num for col in range(len(valeurs)): self.champl[col].value=lval[col] self.mode='' return flag def append(self, *valeurs): """ affecte toutes les valeurs d'un coup, avec un record nouveau """ if len(self.rec)>0: num=max(self.rec)+1 else: num=1 if self.set(num,*valeurs): return num def delete(self, num=None): """ supprime un record """ 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] champ.value=None if champ.idx: champ.idx.delete(None,num) del self.rec[num] self.triggerafterdelete(num,lval) def reindex(self, champ): """ (re)-crée complètement un index 'force' (pas de controle) """ li=[[self.rec[id][champ.position],id] for id in self.rec] 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): 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, 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, print self.recno=buffer 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(): maxi=max(maxi, l[colonne]) 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(): mini=min(mini, l[colonne]) 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(): total=total+l[colonne] 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 selectioncallback(self, s, func): for recno in s: func(recno,self.rec[recno]) 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 à 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') """ """ def EQU(valeur,compar): return valeur==compar def NEQ(valeur,compar): return valeur!=compar def LSS(valeur,compar): return valeur=compar def GEQ(valeur,compar): return valeur>=compar """ 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(): valeur=jtable.rec[l[colonne]][position] gflag=True for compar,crit in criteres.iteritems(): flag=False if compar=='EQU': flag = valeur==crit elif compar=='NEQ': flag = valeur!=crit elif compar=='LSS': flag = valeurcrit elif compar=='GEQ': flag = valeur>=crit gflag = gflag and flag if gflag: lret.append(num) return set(lret) else: lret=[] for num,l in self.rec.iteritems(): valeur=l[colonne] gflag=True for compar,crit in criteres.iteritems(): flag=False if compar=='EQU': flag = valeur==crit elif compar=='NEQ': flag = valeur!=crit elif compar=='LSS': flag = valeurcrit elif compar=='GEQ': flag = valeur>=crit gflag = gflag and flag if gflag: lret.append(num) 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(): valeur=jtable.rec[l[colonne]][position] 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 selectionjointure(self, colonne, selection): """ Retourne une sélection d'enregistrements pour lesquele le champ de jointure appartient à une (autre) sélection donnée """ 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()]