Sunday, July 23, 2006

Masodik nekifutas

Elozo postomban leirt modszer nemigazan hasznalhato addig amig nincs osztaly decorator. Most mashogy kozelitettem meg a problemat es a class static valtozojaba taroltam a interfaceket. pl.:


class ITest:
def test(): pass

class Test:
__implements__ = ( 'ITest', 'interface_pkg.interfaces.iclone.clone.Cloneable', 'serializable.Serializable' )

def sample(self): pass
def serialize(self): pass
def clone(self): pass
def test(self): pass


A syntax hasonlo mint zopenal (de azt nem tudom hogy ellenorzi). Lathato hogy modul/csomag nevet fel kell tunetni az osztaly neve elott. Ha nincs odairva semmi (mint az ITest-nel) akkor az aktualis fileban fogja keresni az adott osztalyt(interfacet).

Az ellenorzes pedig a kovetkezokeppen mukodik. A modul importalast hookoltam, igy a sajat importerem lefog futni minden import elott (ezert kell torolni az import cachet). Ez az importer ellenorzi az importalando fileban levo osszes osztalyt, megnezi van-e valamelyiknek __implements__ static attributuma. Ha talal ilyet, akkor az abban talalhato interfaceket betolti, es lekerdezi a benne levo metodusokat. Majd megnezi hogy az implementalo oszalyban ezek benne vannak-e. Ha igen akkor visszater a modullal, egyebkent hibat ir es None-t kuld vissza, ami ImportError-t fog okozni.

Hasznalat pedig a kovetkezo. Eloszor interface_checker -t kell importalni, ami hookolja magat. Innentol kezdve ha valamit importalunk akkor azt leellenorzni. Ebbol viszont mar ki lehet talalni mi a problema ezzel a modszerrel. A __main__ modul betoltesenel hamarabb nem tudja hookolni magat (mert legkorabban ott lehet importalni) igy az abban levo osztalyokat nem ellenorzni.

Ezenkivul kicsit olyan haxolos benyomast kelthet a dolog. Azert teszem megis koze, mert a import hookolas egy jo feature, es hata inspiral masokat egyeb problemak
megoldasara. Pl az urlimport - amivel httprol lehet python modult betolteni - is ezt hasznalja. De pl olyat meg nem lattam hogy adatabazisbol lehetne ugyanigy importalni, pedig ez is hasznos lehet.

Ime InterfaceImporter highly experimental verzioja:


class InterfaceImporter:

"""
TODO: check number of parameters
TODO: asserterror
TODO: check main module
"""

disable = False

def __init__(self, path):
print '[init]: at', path

def find_module(self, fullname, path=None):
if InterfaceImporter.disable: return None
print '[find module]: %s at %s' % ( fullname, path )
return self

def load_module(self, fullname):
print '[load module] at', fullname

InterfaceImporter.disable = True

try:
mod = __import__(fullname)

# get all class from the module
classes = [ i for i in dir( mod ) if type(getattr(mod, i)) == ClassType ]

print 'Module %s contains %s class(es)' % (fullname, classes)

try:
for clazz in classes:

clazz = getattr(mod, clazz)

if not hasattr( clazz, '__implements__'):
# no __implements___ field found, skip this class
print 'No interface in %s' % (clazz)
continue

interfaces = getattr( clazz, '__implements__')
print '%s implements %s interface(s)' % ( clazz, interfaces )

iobjes = []
for inf in interfaces:
if inf.rfind( '.' ) == -1:
ipkg = fullname
iname = inf
inf = ipkg + '.' + inf
else:
iname = inf[inf.rindex('.')+1:]
ipkg = inf[:inf.rindex('.')]

iobj = __import__( ipkg )
for isub in inf.split('.')[1:]:
iobj = getattr(iobj, isub )

iobjes.append( iobj )

self.checkInterfaces( clazz, iobjes )

except AssertionError, e:
print "implementation error: %s" % (str(e))
return None
except Exception, ex:
print 'Error:', str(ex)
return None
else:
print 'All method implemented'
finally:
InterfaceImporter.disable = False

return mod

def getMethods( self, clz ):
return [i for i in dir(clz) if type(getattr(clz, i)) == MethodType]

def checkInterfaces( self, clazz, interfaces ):
cmethods = self.getMethods( clazz )
print 'class methods of %s: %s' % ( clazz,cmethods )
for inf in interfaces:
imethods = self.getMethods( inf )
print 'interface methods of %s: %s' % ( inf, imethods )
for i in imethods:
assert i in cmethods, `i` + ' not correctly implemented'

# import hook
sys.path_hooks = [x for x in sys.path_hooks if x.__name__ != 'InterfaceImporter']
sys.path_hooks.append(InterfaceImporter)
sys.path_importer_cache.clear()


Az import hookolasrol bovebben pep-0302-ben lehet olvasni. Itt azt is leirjak hogy milyen protocolt kell kovetnie az importer osztalynak. Ez a protocol dolog olyan a pythonnal mint az interface statictyping nyelveknel. Ez mindossze annyit tesz hogy doksiba leirjak hogy milyen metodusok szuksegesek ahhoz h mukodjon. Ha valamit kihagyunk majd runtime kiderul, de amig hapog es duck-kent viselkedik addig duck :)
Egyebkent meg unittest rulez.

0 comments: