summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext/proxy.py
blob: 4db001653b0cf29c261b2e02755702d3475da79d (plain)
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
try:
    from threading import local
except ImportError:
    from sqlalchemy.util import ThreadLocal as local

from sqlalchemy import sql
from sqlalchemy.engine import create_engine
from sqlalchemy.types import TypeEngine

import thread, weakref

class ProxyEngine(object):
    """
    SQLEngine proxy. Supports lazy and late initialization by
    delegating to a real engine (set with connect()), and using proxy
    classes for TableImpl, ColumnImpl and TypeEngine.
    """

    def __init__(self):
        # create the local storage for uri->engine map and current engine
        self.storage = local()
        self.storage.connection = {}
        self.storage.engine = None
        self.tables = {}
            
    def connect(self, uri, opts=None, **kwargs):
        """Establish connection to a real engine.
        """
        key = "%s(%s,%s)" % (uri, repr(opts), repr(kwargs))
        try:
            map = self.storage.connection
        except AttributeError:
            self.storage.connection = {}
            self.storage.engine = None
            map = self.storage.connection
        try:
            self.engine = map[key]
        except KeyError:
            map[key] = create_engine(uri, opts, **kwargs)
            self.storage.engine = map[key]
            
    def get_engine(self):
        if self.storage.engine is None:
            raise AttributeError('No connection established')
        return self.storage.engine

    def set_engine(self, engine):
        self.storage.engine = engine
        
    engine = property(get_engine, set_engine)
            
    def hash_key(self):
        return "%s(%s)" % (self.__class__.__name__, id(self))

    def oid_column_name(self):
        # NOTE: setting up mappers fails unless the proxy engine returns
        # something for oid column name, and the call happens too early
        # to proxy, so effecticely no oids are allowed when using
        # proxy engine
        if self.storage.engine is None:
            return None
        return self.get_engine().oid_column_name()
    
    def columnimpl(self, column):
        """Proxy point: return a ProxyColumnImpl
        """
        return ProxyColumnImpl(self, column)

    def tableimpl(self, table):
        """Proxy point: return a ProxyTableImpl
        """
        return ProxyTableImpl(self, table)
        
    def type_descriptor(self, typeobj):
        """Proxy point: return a ProxyTypeEngine 
        """
        return ProxyTypeEngine(self, typeobj)

    def __getattr__(self, attr):
        # call get_engine() to give subclasses a chance to change
        # connection establishment behavior
        if self.get_engine() is not None:
            return getattr(self.engine, attr)
        raise AttributeError('No connection established in ProxyEngine: '
                             ' no access to %s' % attr)

        
class ProxyColumnImpl(sql.ColumnImpl):
    """Proxy column; defers engine access to ProxyEngine
    """
    def __init__(self, engine, column):
        sql.ColumnImpl.__init__(self, column)
        self._engine = engine
        self.impls = weakref.WeakKeyDictionary()
    def _get_impl(self):
        e = self.engine
        try:
            return self.impls[e]
        except KeyError:
            impl = e.columnimpl(self.column)
            self.impls[e] = impl
    def __getattr__(self, key):
        return getattr(self._get_impl(), key)
    engine = property(lambda self: self._engine.engine)

class ProxyTableImpl(sql.TableImpl):
    """Proxy table; defers engine access to ProxyEngine
    """
    def __init__(self, engine, table):
        sql.TableImpl.__init__(self, table)
        self._engine = engine
        self.impls = weakref.WeakKeyDictionary()
    def _get_impl(self):
        e = self.engine
        try:
            return self.impls[e]
        except KeyError:
            impl = e.tableimpl(self.table)
            self.impls[e] = impl
            return impl
    def __getattr__(self, key):
        return getattr(self._get_impl(), key)

    engine = property(lambda self: self._engine.engine)

class ProxyType(object):
    """ProxyType base class; used by ProxyTypeEngine to construct proxying
    types    
    """
    def __init__(self, engine, typeobj):
        self._engine = engine
        self.typeobj = typeobj

    def __getattribute__(self, attr):
        if attr.startswith('__') and attr.endswith('__'):
            return object.__getattribute__(self, attr)
        
        engine = object.__getattribute__(self, '_engine').engine
        typeobj = object.__getattribute__(self, 'typeobj')        
        return getattr(engine.type_descriptor(typeobj), attr)

    def __repr__(self):
        return '<Proxy %s>' % (object.__getattribute__(self, 'typeobj'))
    
class ProxyTypeEngine(object):
    """Proxy type engine; creates dynamic proxy type subclass that is instance
    of actual type, but proxies engine-dependant operations through the proxy
    engine.    
    """
    def __new__(cls, engine, typeobj):
        """Create a new subclass of ProxyType and typeobj
        so that internal isinstance() calls will get the expected result.
        """
        if isinstance(typeobj, type):
            typeclass = typeobj
        else:
            typeclass = typeobj.__class__
        typed = type('ProxyTypeHelper', (ProxyType, typeclass), {})
        return typed(engine, typeobj)