Layers

Ho scritto una libreria python per sovrapporre più directory in un filesystem virtuale, si chiama Layers e lo trovate su http://github.com/robertolupi/layers. Le regole (ricette) con cui le varie versioni di un file (ingredienti) vengono mescolate insieme sono personalizzabili.

Un esempio: costruiamo un VFS partendo da quattro directory, di cui le prime due montate alla radice del VFS mentre le ultime due montate in due distinte sottodirectory. Specifichiamo poi la ricetta per mescolare i files .txt:

from layers import VirtualFileSystem, concatenate_files

vfs = VirtualFileSystem()
vfs.add_layer('layer1')
vfs.add_layer('layer2')
vfs.add_layer('layer3_sub','sub')
vfs.add_layer('layer4_urka','urka')
vfs.set_recipe('.txt', concatenate_files)

fd = vfs.open_file('foo.txt','r')

Nel’esempio ho usato una ricetta predefinita, ma costruirne di proprie è facilissimo. Ecco l’implementazione di concatenate_files:

def concatenate_files(alternatives, mode=None, *args, **kwargs):
    """Concatenate files from the lowest to the higher layer in order.
    """
    return fileinput.input(alternatives, mode=mode, *args, **kwargs)

La parte più interessante però non è il filesystem virtuale, ma la possibilità di montare un VFS come package Python. Layers si occupa di mixare opportunamente funzioni, classi e metodi.

Se layer1/foo.py contiene questo codice:

class Foo(object):
    def method1(self):
        return 'layer1'
    def method2(self):
        return 'layer1'

Mentre layer2/foo.py contiene quest’altro codice:

class Foo(object):
    def method1(self):
        return 'layer2'

Allora posso posso scrivere questo:

from layers import LayeredPackage
lp = LayeredPackage('testpkg')
lp.add_layer('layer1')
lp.add_layer('layer2')
lp.install()

from testpkg import foo
f = foo.Foo()
print f.method1() # scrive layer2
print f.method2() # scrive layer1

In pratica, la classe Foo in foo.py del package virtuale testpkg si comporta come se fosse scritta così:

class Foo(object):
    def method1(self):
        return 'layer2'
    def method2(self):
        return 'layer1'

L’implementazione usa l’ereditarietà multipla, costruendo a run-time una nuova classe. L’implementazione reale di Foo è più o meno questa:

class Layer1Foo(object):
    def method1(self):
        return 'layer1'
    def method2(self):
        return 'layer1'

class Layer2Foo(object):
    def method1(self):
        return 'layer2'

class FinalFoo(Layer2Foo,Layer1Foo):
    pass

Quindi in layer2/foo.py posso anche usare super(Foo, self).method1() per chiamare l’implementazione di method1() del layer precedente.

A cosa serve Layers? Per ora a poco, il codice non è maturo (manca il supporto ai packages, può mixare solo i moduli). È nato come esperimento per replicare la semantica dei mixin di GenroPy, semplificandone l’implementazione.

In potenza, Layers (o i mixin di GenroPy) possono essere usati laddove i linguaggi statici usano IoC containers: quindi ovunque ci sia necessità di specializzare il codice per un particolare ambiente (es. per la personalizzazione durante l’installazione presso un dato cliente, durante i test per iniettare metodi e classi di Mockup, etc.).

Per ora, lo userò per imparare setuptools/Distribute e magari per dare un’occhiata a FUSE.