Browse Source

New version of sqlwrapper that is not a singleton anymore

Yann Weber 9 years ago
parent
commit
dde02cadd0
1 changed files with 149 additions and 120 deletions
  1. 149
    120
      Database/sqlwrapper.py

+ 149
- 120
Database/sqlwrapper.py View File

@@ -11,108 +11,149 @@ logger.getLogger().setLevel('DEBUG')
11 11
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Lodel.settings")
12 12
 
13 13
 class SqlWrapper(object):
14
-    """ A wrapper class to sqlalchemy 
15
-        Usefull to provide a standart API
16
-    """
14
+    """ A wrapper class to sqlalchemy
17 15
 
18
-    ##Read Engine
19
-    rengine = None
20
-    ##Write Engine
21
-    wengine = None
22
-
23
-    ##Read connection
24
-    rconn = None
25
-    ##Write connection
26
-    wconn = None
16
+        Usefull to provide a standart API
27 17
 
18
+        __Note__ : This class is not thread safe (sqlAlchemy connections are not). Create a new instance of the class to use in different threads or use SqlWrapper::copy
19
+    """
20
+    ENGINES = {'mysql': {
21
+                    'driver': 'pymysql',
22
+                    'encoding': 'utf8'
23
+                },
24
+                'postgresql': {
25
+                    'driver': 'psycopg2',
26
+                    'encoding': 'utf8',
27
+                },
28
+                'sqlite': {
29
+                    'driver': 'pysqlite',
30
+                    'encoding': 'utf8',
31
+                },
32
+    }
33
+    
28 34
     ##Configuration dict alias for class access
29 35
     config=settings.LODEL2SQLWRAPPER
30
-    ##SqlAlchemy logging
31
-    sqla_logging = False
36
+    
37
+    ##Wrapper instance list
38
+    wrapinstance = dict()
32 39
 
33
-    def __init__(self, alchemy_logs=None):
40
+    def __init__(self, name="default", alchemy_logs=None, read_db = "default", write_db = "default"):
34 41
         """ Instanciate a new SqlWrapper
42
+            @param name str: The wrapper name
35 43
             @param alchemy_logs bool: If true activate sqlalchemy logger
44
+            @param read_db str: The name of the db conf
45
+            @param write_db str: The name of the db conf
36 46
         """
37 47
         
38
-        if (alchemy_logs != None and bool(alchemy_logs) != self.__class__.sqla_logging):
39
-            #logging config changed for sqlalchemy
40
-            self.__class__.restart()
48
+        self.sqlalogging = False if alchemy_logs == None else bool(alchemy_logs)
41 49
 
42
-        if not self.__class__.started():
43
-            self.__class__.start()
44
-        
50
+        self.name = name
51
+    
52
+        self.r_dbconf = read_db
53
+        self.w_dbconf = write_db
54
+
55
+        self.checkConf() #raise if errors in configuration
56
+
57
+        if name in self.__class__.wrapinstance:
58
+            logger.wraning("A SqlWrapper with the name "+name+" allready exist. Replacing the old one by the new one")
59
+        SqlWrapper.wrapinstance[name] = self
60
+
61
+        #Engine and wrapper initialisation
62
+        self.r_engine = self._getEngine(True, self.sqlalogging)
63
+        self.w_engine = self._getEngine(False, self.sqlalogging)
64
+        self.r_conn = None
65
+        self.w_conn = None
45 66
         pass
46 67
 
47
-    @classmethod
48
-    def table(c, tname):
49
-        """ Return a SqlAlchemy Table object
50
-            @param o str: Table name
51
-            @return a SqlAlchemy Table instance
68
+    @property
69
+    def cfg(self): return self.__class__.config;
70
+    @property
71
+    def engines_cfg(self): return self.__class__.ENGINES;
72
+
73
+    @property
74
+    def rconn(self):
75
+        """ Return the read connection
76
+            @warning Do not store the connection, call this method each time you need it
52 77
         """
53
-        if not isinstance(tname, str):
54
-            raise TypeError("Excepting a str but got a "+str(type(name)))
55
-        return sqla.Table(o, sqla.MetaData())
78
+        return self.getConnection(True)
79
+    @property
80
+    def wconn(self):
81
+        """ Return the write connection
82
+            @warning Do not store the connection, call this method each time you need it
83
+        """
84
+        return self.getConnection(False)
56 85
 
57
-    @classmethod
58
-    def connect(c,read = None):
59
-        
60
-        if read == None:
61
-            return c.connect(True) and c.coonect(False)
62
-        elif read:
63
-            c.rconn = c.rengine.connect()
64
-        else:
65
-            c.wconn = c.wengine.connect()
66
-        return True #TODO attention c'est pas checké...
67 86
 
68
-    @classmethod   
69
-    def conn(c, read=True):
87
+    def getConnection(self, read):
88
+        """ Return an opened connection
89
+            @param read bool: If true return the reading connection
90
+            @return A sqlAlchemy db connection
91
+        """
70 92
         if read:
71
-            res = c.rconn
93
+            r = self.r_conn
72 94
         else:
73
-            res = c.wconn
95
+            r = self.w_conn
74 96
 
75
-        if res == None:
76
-            if not c.connect(read):
77
-                raise RuntimeError('Unable to connect to Db')
78
-            return c.conn(read)
97
+        if r == None:
98
+            #Connection not yet opened
99
+            self.connect(read)
100
+            r = self.getConnection(read) #TODO : Un truc plus safe/propre qu'un appel reccursif ?
101
+        return r
79 102
 
80
-        return c.rconn
81 103
 
82
-    @classmethod
83
-    def rc(c): return c.conn(True)
84
-    @classmethod
85
-    def wc(c): return c.conn(False)
86
-
87
-    @classmethod
88
-    def start(c, sqlalogging = None):
89
-        """ Load engines
90
-            Open connections to databases
91
-            @param sqlalogging bool: overwrite class parameter about sqlalchemy logging
92
-            @return False if already started
104
+    def connect(self, read = None):
105
+        """ Open a connection to a database
106
+            @param read bool|None: If None connect both, if True only connect the read side (False the write side)
107
+            @return None
93 108
         """
94
-        c.checkConf()
95
-        if c.started():
96
-            logger.warning('Starting SqlWrapper but it is allready started')
97
-            return False
109
+        if read or read == None:
110
+            if self.r_conn != None:
111
+                logger.debug(' SqlWrapper("'+self.name+'") Unable to connect, already connected')
112
+            else:
113
+                self.r_conn = self.r_engine.connect()
98 114
 
99
-        c.rengine = c._getEngine(read=True, sqlalogging=None)
100
-        c.wengine = c._getEngine(read=False, sqlalogging=None)
101
-        return True
115
+        if not read or read == None:
116
+            if self.w_conn != None:
117
+                logger.debug(' SqlWrapper("'+self.name+'") Unable to connect, already connected')
118
+            else:
119
+                self.w_conn = self.w_engine.connect()
102 120
 
103
-    @classmethod
104
-    def stop(c): c.rengine = c.wengine = None; pass
105
-    @classmethod
106
-    def restart(c): c.stop(); c.start(); pass
107
-    @classmethod
108
-    def started(c): return (c.rengine != None and c.rengine != None)
109
-    
110
-    @classmethod
111
-    def _sqllog(c,sqlalogging = None):
112
-        return bool(sqlalogging) if sqlalogging != None else c.sqla_logging
121
+    def disconnect(self, read = None):
122
+        """ Close a connection to a database
123
+            @param read bool|None: If None disconnect both, if True only connect the read side (False the write side)
124
+            @return None
125
+        """
126
+        if read or read == None:
127
+            self.r_conn.close()
128
+            self.r_conn = None
129
+
130
+        if not read or read == None:
131
+            self.w_conn.close()
132
+            self.w_conn = None
133
+
134
+    def reconnect(self, read = None):
135
+        """ Close and reopen a connection to a database
136
+            @param read bool|None: If None disconnect both, if True only connect the read side (False the write side)
137
+            @return None
138
+        """
139
+        self.disconnect(read)
140
+        self.connect(read)
113 141
 
114 142
     @classmethod
115
-    def _getEngine(c, read=True, sqlalogging = None):
143
+    def reconnectAll(c, read = None):
144
+        for wname in c.wrapinstance:
145
+            c.wrapinstance[wname].reconnect(read)
146
+            
147
+    def Table(self, tname):
148
+        """ Instanciate a new SqlAlchemy Table
149
+            @param tname str: The table name
150
+            @return A new instance of SqlAlchemy::Table
151
+        """
152
+        if not isinstance(tname, str):
153
+            return TypeError('Excepting a <class str> but got a '+str(type(tname)))
154
+        return sqla.Table(tname, sqla.MetaData(), autoload_with=self.r_engine, autoload=True)
155
+
156
+    def _getEngine(self, read=True, sqlalogging = None):
116 157
         """ Return a sqlalchemy engine
117 158
             @param read bool: If True return the read engine, else 
118 159
             return the write one
@@ -121,16 +162,10 @@ class SqlWrapper(object):
121 162
             @todo Put the check on db config in SqlWrapper.checkConf()
122 163
         """
123 164
         #Loading confs
124
-        connconf = 'dbread' if read else 'dbwrite'
125
-        dbconf = connconf if connconf in c.config['db'] else 'default'
165
+        cfg = self.cfg['db'][self.r_dbconf if read else self.w_dbconf]
126 166
 
127
-        if dbconf not in c.config['db']: #This check should not be here
128
-            raise NameError('Configuration error no db "'+dbconf+'" in configuration files')
129
-        
130
-        cfg = c.config['db'][dbconf] #Database config
131
-        
132
-        edata = c.config['engines'][cfg['ENGINE']] #engine infos
133
-        conn_str = cfg['ENGINE']+'+'+edata['driver']+'://'
167
+        edata = self.engines_cfg[cfg['ENGINE']] #engine infos
168
+        conn_str = ""
134 169
 
135 170
         if cfg['ENGINE'] == 'sqlite':
136 171
             #Sqlite connection string
@@ -153,10 +188,21 @@ class SqlWrapper(object):
153 188
             conn_str = '%s+%s://'%(cfg['ENGINE'], edata['driver'])
154 189
             conn_str += '%s@%s/%s'%(user,host,cfg['NAME'])
155 190
 
156
-        return sqla.create_engine(conn_str, encoding=edata['encoding'], echo=c._sqllog(sqlalogging))
191
+        return sqla.create_engine(conn_str, encoding=edata['encoding'], echo=self.sqlalogging)
157 192
 
158 193
     @classmethod
159
-    def checkConf(c):
194
+    def getWrapper(c, name):
195
+        """ Return a wrapper instance from a wrapper name
196
+            @param name str: The wrapper name
197
+            @return a SqlWrapper instance
198
+
199
+            @throw KeyError
200
+        """
201
+        if name not in c.wrapinstance:
202
+            raise KeyError("No wrapper named '"+name+"' exists")
203
+        return c.wrapinstance[name]
204
+
205
+    def checkConf(self):
160 206
         """ Class method that check the configuration
161 207
             
162 208
             Configuration looks like
@@ -172,44 +218,27 @@ class SqlWrapper(object):
172 218
             - dbwrite (mandatory if no default db)
173 219
         """
174 220
         err = []
175
-        if 'db' not in c.config:
221
+        if 'db' not in self.cfg:
176 222
             err.append('Missing "db" in configuration')
177 223
         else:
178
-            if 'default' not in c.config['db']:
179
-                if 'dbread' not in c.config:
180
-                    err.append('Missing "dbread" in configuration and  no "default" db declared')
181
-                if 'dbwrite' not in c.config:
182
-                    err.append('Missing "dbwrite" in configuration and no "default" db declared')
183
-            for dbname in c.config['db']:
184
-                db = c.config['db'][dbname]
185
-                if 'ENGINE' not in db:
186
-                    err.append('Missing "ENGINE" in database "'+db+'"')
224
+            for dbname in [self.r_dbconf, self.w_dbconf]:
225
+                if dbname not in self.cfg['db']:    
226
+                    err.append('Missing "'+dbname+'" db configuration')
187 227
                 else:
188
-                    if db['ENGINE'] != 'sqlite' and 'USER' not in db:
189
-                        err.append('Missing "USER" in database "'+db+'"')
190
-                if 'NAME' not in db:
191
-                    err.append('Missing "NAME" in database "'+db+'"')
192
-        
193
-        if len(c.config['engines']) == 0:
194
-            err.append('Missing "engines" in configuration')
195
-        for ename in c.config['engines']:
196
-            engine = c.config['engines'][ename]
197
-            if 'driver' not in engine:
198
-                err.append('Missing "driver" in database engine "'+ename+'"')
199
-            if 'encoding' not in engine:
200
-                err.append('Missing "encoding" in database engine "'+ename+'"')
201
-
228
+                    db = self.cfg['db'][dbname]
229
+                    if 'ENGINE' not in db:
230
+                        err.append('Missing "ENGINE" in database "'+db+'"')
231
+                    else:
232
+                        if db['ENGINE'] not in self.engines_cfg:
233
+                            err.append('Unknown engine "'+db['ENGINE']+'"')
234
+                        elif db['ENGINE'] != 'sqlite' and 'USER' not in db:
235
+                            err.append('Missing "User" in configuration of database "'+dbname+'"')
236
+                    if 'NAME' not in db:
237
+                        err.append('Missing "NAME" in database "'+dbname+'"')
238
+                        
202 239
         if len(err)>0:
203 240
             err_str = "\n"
204 241
             for e in err:
205 242
                 err_str += "\t\t"+e+"\n"
206 243
             raise NameError('Configuration errors in LODEL2SQLWRAPPER:'+err_str)
207 244
                 
208
-
209
-    @property
210
-    def cfg(self):
211
-        """ Get the dict of options for the wrapper
212
-            Its an alias to the classes property SqlWrapper.config
213
-            @return a dict containing the Db settings"""
214
-        return self.__class__.config
215
-

Loading…
Cancel
Save