Welcome, guest | Sign In | My Account | Store | Cart

The alias metaclass allows a declarative way of creating aliases to attributes on a class. The main purpose of which is to adapt classes for duck-typing (see doc-string for me detail).

Python, 127 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
"""
Alias allows the dynamic aliasing of class attributes to a sequence of aliases
using metaclass programming to give a declarative way of expressing aliases as
an inner class on the object in question.

The main use of such a system would be to unify a disparate set of objects with
similar, but differently named attributes so that they can access be a single
piece of code.

Taking the example below, we define two types of records - an old-style and
new-style record. Note that the attributes on the class describe the same
things, but are named differently. By declaring a set of suitable aliases on
each style class we can then include both styles in our record set and treat
them as one:

>>> from alias import NewRecord, OldRecord, RecordSet
>>> rs = RecordSet(NewRecord(), OldRecord())
>>> rs.records
[<alias.NewRecord object at ...>, <alias.OldRecord object at ...>]
>>> rs.sort_by_added()
>>> rs.records
[<alias.OldRecord object at ...>, <alias.NewRecord object at ...>]
>>> [r.removed for r in rs.records]
[False, False]
>>> rs.mark_all_as_removed()
>>> [r.removed for r in rs.records]
[True, True]
>>> [r.deleted for r in rs.records]
[True, True]
>>> for r in rs.records: r.save()
Storing record: Old type record
Saving record: New style record
"""
import inspect

class AliasDescriptor(object):
    def __init__(self, alias):
        self.alias = alias
    
    def __get__(self, instance, owner):
        return getattr(instance, owner.__dict__['__aliases'][self.alias])
    
    def __set__(self, instance, value):
        setattr(instance,
                instance.__class__.__dict__['__aliases'][self.alias],
                value)
    
    def __delete__(self, instance):
        raise AttributeError('Deleting of aliased attribute %r is not supported' % self.alias)
        

class AliasType(type):
    def __new__(cls, name, bases, attrs):
        new_class = super(AliasType, cls).__new__(cls, name, bases, attrs)
        
        # see whether we've got an Alias inner class:
        if inspect.isclass(attrs.get('Alias', None)):
            alias = attrs['Alias']
            setattr(new_class, '__aliases', {})
            for attr, value in alias.__dict__.items():
                # don't want any "private" attrs appearing
                if attr.startswith('_'):
                    continue
                # don't care if these aren't tuples or lists
                if not isinstance(value, (tuple, list)):
                    continue
                # no point in setting up an alias for something which isn't a
                # class attribute:
                if not attr in attrs:
                    continue
                
                # if we've got to here put the lookups into the __aliases dict:
                for key in value:
                    new_class.__dict__['__aliases'][key] = attr
                    setattr(new_class, key, AliasDescriptor(key))
            
        return new_class

class Aliaser(object):
    __metaclass__ = AliasType
    
from datetime import datetime

class BaseRecord(object):
    """Dummy base class to demonstrate multiple-inheretance"""
    pass

class OldRecord(BaseRecord, Aliaser):
    title = 'Old type record'
    created = datetime(2007, 1, 1, 9)
    deleted = False
    
    def store(self):
        print 'Storing record: %s' % self.title
    
    class Alias:
        created = ('date_created', 'created_date', 'added')
        deleted = ('removed',)
        store = ('save',)
        
class NewRecord(BaseRecord, Aliaser):
    title = "New style record"
    added = datetime(2009, 1, 1, 10)
    removed = False
    
    def save(self):
        print 'Saving record: %s' % self.title
    
    class Alias:
        added = ('date_created', 'created_date', 'created')
        removed = ('deleted',)
        save = ('store')
        
class RecordSet(object):
    def __init__(self, *records):
        self.records = list(records)
    
    def sort_by_added(self):
        self.records.sort(key=lambda r: r.added)
    
    def mark_all_as_removed(self):
        for record in self.records:
            record.removed = True

if __name__ == "__main__":
    import doctest
    doctest.testmod(optionflags=doctest.ELLIPSIS)