epub in git

Using git for version control works great, but epub is a binary (really zipped) format. To get any sensible information the content has to be expanded before it is added to git. The following pre-commit-hook automates this.

#!/usr/bin/env python

import sys, subprocess

if sys.platform == 'win32':
    import win32con, win32api
    def sethidden(stem):
        win32api.SetFileAttributes('.%s' % stem, 
            win32con.FILE_ATTRIBUTE_HIDDEN)
else:
    def sethidden(stem):
        pass

def process(name):
    if name.endswith('.epub'):
        stem = name[:-5]
        subprocess.call('rm -rf .%s' % stem, shell=True)
        subprocess.call('unzip %s -d .%s' % (name, stem),
            shell=True)
        subprocess.call('git add .%s' % stem, shell=True)
        sethidden(stem)

def main():

    lines = subprocess.check_output('git status -s', 
                shell=True).split('\n')

    for line in lines:
        words = line.split()
        try:
            if words[0] == 'R':
                name = words[3]
                process(name)
            elif words[0] in 'MA':
                name = words[1]
                process(name)
        except:
            pass

if __name__ == '__main__':
    main()

Methods for MutableSequence

The current wisdom recommends to inherit from collections.MutableSequence instead of List.

To make MutableSequence work, some methods have to be implemented:

  • __init__ or __new__
  • __getitem__
  • __setitem__
  • __delitem__
  • __len__
  • insert

One reason to implement this can be to ensure that the contents of the List have a specific type.

from collections import MutableSequence

class ListLike(MutableSequence):
    '''
    A Class that behaves like a List
    '''
    @staticmethod
    def __checkValue(value):
        if not isinstance(value, object):
           raise TypeError()

I prefer to use __init__ and to set the container here.

    def __init__(self):
        self.__store = []

__getitem__ retrieves an item by index (this is a List, not a Directory).

    def __getitem__(self, key):
        '''
        @param key: Index
        @type  key: L{int}
        @return: Item at location index
        '''
        return self.__store[key]

__setitem__ assigns a new value at index.

    def __setitem__(self, key, value):
        '''
        @param key: Index
        @type  key: L{int}
        @param value: New Value
        '''
        self.__checkValue(value)
        self.__store[key] = value

__delitem__ removes the item at index.

    def __delitem__(self, key):
        '''
        @param key: Index
        @type  key: L{int}
        '''
        del self.__store[key]

__len__ returns the length of the list (number of entries).

    def __len__(self):
        return len(self.__store)

insert adds an item at index.

    def insert(self, key, value):
        '''
        @param key: Index
        @type  key: L{int}
        @param value: New Value
        '''
        self.__checkValue(value)
        self.__store.insert(key, value)

Complete Code to copy below:

from collections import MutableSequence

class ListLike(MutableSequence):
    '''
    A Class that behaves like a List
    '''

    @staticmethod
    def __checkValue(value):
        if not isinstance(value, object):
           raise TypeError()

    def __init__(self):
        self.__store = []

    def __getitem__(self, key):
        '''
        @param key: Index
        @type  key: L{int}
        @return: Item at location index
        '''
        return self.__store[key]

    def __setitem__(self, key, value):
        '''
        @param key: Index
        @type  key: L{int}
        @param value: New Value
        '''
        self.__checkValue(value)
        self.__store[key] = value

    def __delitem__(self, key):
        '''
        @param key: Index
        @type  key: L{int}
        '''
        del self.__store[key]

    def __len__(self):
        return len(self.__store)

    def insert(self, key, value):
        '''
        @param key: Index
        @type  key: L{int}
        @param value: New Value
        '''
        self.__checkValue(value)
        self.__store.insert(key, value)

Python Singleton

Sometimes a Singleton comes in handy. In this case will be used to provide a directory of fonts. Loading fonts uses resources and should done only once. The Singleton will hold references to all fonts already loaded and will load a requested font, if not available yet.

The second example in PEP 318 shows an approach using a decorator.

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance</code>

@singleton
class MyClass:
    ...

This works, but does not allow parameters to be passed to __init__. Modifying the decorator a bit allows passing of parameters, in this case even updating the existing Singleton.

def singleton(cls):
    instances = {}
    def getinstance(*args, **kw):
        if cls not in instances:
            instances[cls] = cls()
        instance[cls].__init__(*args, **kw)
        return instances[cls]
    return getinstance</code>

@singleton
class OnlyOne:
    store = []
    def __init__(self, value=None):
        if value:
            self.append(value)
    def append(self, value):
        self.store.append(value)

if __name__ == '__main__':
    obj1 = OnlyOne(1)
    print obj1.store

    obj2 = OnlyOne(2)
    print obj2.store

    print obj1.store

This results in:

[1]
[1, 2]
[1, 2]

Exactly the result I am looking for. The actual code for the font directory will use a Dict, but that is just a minor modification.