Browse Source

Merge branch 'sqlwrapper'

Yann Weber 9 years ago
parent
commit
fff0225428

+ 90
- 0
Database/sqlalter.py View File

@@ -0,0 +1,90 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+import os
4
+
5
+import sqlalchemy as sqla
6
+from sqlalchemy.ext.compiler import compiles
7
+
8
+## @file sqlalter.py
9
+# This file defines all DDL (data definition langage) for the ALTER TABLE instructions
10
+# 
11
+# It uses the SqlAlchemy compilation and quoting methos to generate SQL
12
+
13
+
14
+class AddColumn(sqla.schema.DDLElement):
15
+    """ Defines the ddl for adding a column to a table """
16
+    def __init__(self,table, column):
17
+        """ Instanciate the DDL
18
+            @param table sqlalchemy.Table: A sqlalchemy table object
19
+            @param column sqlalchemy.Column: A sqlalchemy column object
20
+        """
21
+        self.col = column
22
+        self.table = table
23
+
24
+@compiles(AddColumn, 'mysql')
25
+@compiles(AddColumn, 'postgresql')
26
+@compiles(AddColumn, 'sqlite')
27
+def visit_add_column(element, ddlcompiler, **kw):
28
+    """ Compiles the AddColumn DDL for mysql, postgresql and sqlite"""
29
+    prep = ddlcompiler.sql_compiler.preparer
30
+    tname = prep.format_table(element.table)
31
+    colname = prep.format_column(element.col)
32
+    return 'ALTER TABLE %s ADD COLUMN %s %s'%(tname,  colname, element.col.type)
33
+
34
+@compiles(AddColumn)
35
+def visit_add_column(element, ddlcompiler, **kw):
36
+    raise NotImplementedError('Add column not yet implemented for '+str(ddlcompiler.dialect.name))
37
+
38
+class DropColumn(sqla.schema.DDLElement):
39
+    """ Defines the DDL for droping a column from a table """
40
+    def __init__(self, table, column):
41
+        """ Instanciate the DDL
42
+            @param table sqlalchemy.Table: A sqlalchemy table object
43
+            @param column sqlalchemy.Column: A sqlalchemy column object representing the column to drop
44
+        """
45
+        self.col = column
46
+        self.table = table
47
+
48
+@compiles(DropColumn,'mysql')
49
+@compiles(DropColumn, 'postgresql')
50
+def visit_drop_column(element, ddlcompiler, **kw):
51
+    """ Compiles the DropColumn DDL for mysql & postgresql """
52
+    prep = ddlcompiler.sql_compiler.preparer
53
+    tname = prep.format_table(element.table)
54
+    colname = prep.format_column(element.col)
55
+    return 'ALTER TABLE %s DROP COLUMN %s'%(tname, colname)
56
+
57
+@compiles(DropColumn)
58
+def visit_drop_column(element, ddlcompiler, **kw):
59
+    raise NotImplementedError('Drop column not yet implemented for '+str(ddlcompiler.dialect.name))
60
+
61
+class AlterColumn(sqla.schema.DDLElement):
62
+    """ Defines the DDL for changing the type of a column """
63
+    def __init__(self, table, column):
64
+        """ Instanciate the DDL
65
+            @param table sqlalchemy.Table: A sqlalchemy Table object
66
+            @param column sqlalchemy.Column: A sqlalchemy Column object representing the new column
67
+        """
68
+        self.col = column
69
+        self.table = table
70
+
71
+@compiles(AlterColumn, 'mysql')
72
+def visit_alter_column(element, ddlcompiler, **kw):
73
+    """ Compiles the AlterColumn DDL for mysql """
74
+    prep = ddlcompiler.sql_compiler.preparer
75
+    tname = prep.format_table(element.table)
76
+    colname = prep.format_column(element.col)
77
+    return 'ALTER TABLE %s ALTER COLUMN %s %s'%(tname, colname, element.col.type)
78
+
79
+@compiles(AlterColumn, 'postgresql')
80
+def visit_alter_column(element, ddlcompiler, **kw):
81
+    """ Compiles the AlterColumn DDL for postgresql """
82
+    prep = ddlcompiler.sql_compiler.preparer
83
+    tname = prep.format_table(element.table)
84
+    colname = prep.format_column(element.col)
85
+    return 'ALTER TABLE %s ALTER COLUMN %s TYPE %s'%(tname, colname, element.col.type)
86
+
87
+@compiles(AlterColumn)
88
+def visit_alter_column(element, ddlcompiler, **kw):
89
+    raise NotImplementedError('Alter column not yet implemented for '+str(ddlcompiler.dialect.name))
90
+

+ 0
- 73
Database/sqlobject.py View File

@@ -1,73 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-import os
4
-import logging as logger
5
-
6
-import sqlalchemy as sql
7
-
8
-from Database.sqlwrapper import SqlWrapper
9
-
10
-class SqlObject(object):
11
-    """ Object that make aliases with sqlalchemy
12
-        
13
-        Example usage of object that inherite from SqlObject :
14
-        
15
-        class foo(SlqObject,...):
16
-            def __init__(self, ...):
17
-                self.__class__.tname = 'foo_table'
18
-
19
-        f = foo(...)
20
-        req = f.where(f.col.id == 42)
21
-        res = f.rexec(req)
22
-
23
-        e = bar(...)
24
-        req = f.join(e.col.id == f.col.id)
25
-        res = f.rexec(req)
26
-
27
-    """
28
-
29
-    def __init__(self, tname):
30
-        if not type(tname) is str:
31
-            logger.error("Unable to instanciate, bad argument...")
32
-            raise TypeError('Excepted a string not a '+str(type(tname)))
33
-
34
-        self.tname = tname
35
-        self.table = self.Table()
36
-        pass
37
-
38
-    def Table(self):
39
-        self.table = sql.Table(self.tname, sql.MetaData(), autoload_with=SqlWrapper.rengine, autoload=True)
40
-        return self.table
41
-
42
-    @property
43
-    def col(self):
44
-        return self.table.c
45
-    
46
-    @property
47
-    def sel(self):
48
-        return sql.select([self.table])
49
-
50
-    @property
51
-    def where(self):
52
-        return self.sel.where
53
-
54
-    @property
55
-    def join(self):
56
-        return self.sel.join
57
-
58
-    @property
59
-    def rconn(self):
60
-        return SqlWrapper.rc()
61
-
62
-    @property
63
-    def wconn(self):
64
-        return SqlWrapper.wc()
65
-
66
-    def sFetchAll(self, sel):
67
-        return self.rexec(sel).fetchall()
68
-
69
-    def rexec(self, o):
70
-        return self.rconn.execute(o)
71
-
72
-    def wexec(self, o):
73
-        return self.wconn.execute(o)

+ 85
- 0
Database/sqlquerybuilder.py View File

@@ -0,0 +1,85 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+import os
4
+import logging as logger
5
+
6
+import sqlalchemy as sql
7
+
8
+from Database.sqlwrapper import SqlWrapper
9
+
10
+class SqlQueryBuilder():
11
+
12
+    def __init__(self, sqlwrapper, table):
13
+        if not type(sqlwrapper) is SqlWrapper:
14
+             logger.error("Unable to instanciate, bad argument...")
15
+             raise TypeError('Excepted a SqlWrapper not a '+str(type(sqlwrapper)))
16
+        self.table = table
17
+        self.sqlwrapper = sqlwrapper
18
+        self.proxy = None
19
+
20
+
21
+    def Select(self, arg):
22
+        """ Alias for select clause
23
+            @param arg iterable: arg must be a Python list or other iterable and contain eather table name/type or colum/literal_column
24
+        """
25
+
26
+        self.proxy = sql.select(arg)
27
+        return self.proxy
28
+
29
+    def Where(self, arg):
30
+        """ Alias for where clause
31
+            @param arg SQL expression object or string
32
+        """
33
+        self.proxy = self.proxy.where(arg)
34
+
35
+    def From(self, arg):
36
+        """ Alias for select_from clause
37
+            @param arg Table or table('tablename') or join clause
38
+        """
39
+        self.proxy = self.proxy.select_from(arg)
40
+
41
+    def Update(self):
42
+        self.proxy = self.table.update()
43
+
44
+    def Insert(self):
45
+        self.proxy = self.table.insert()
46
+
47
+    def Delete(self):
48
+        self.proxy = self.proxy.delete()
49
+
50
+    def Value(self, arg):
51
+        """
52
+        Allow you to specifies the VALUES or SET clause of the statement.
53
+        @param arg: VALUES or SET clause
54
+        """
55
+        self.proxy = self.proxy.values(arg)
56
+
57
+    def Execute(self, bindedparam):
58
+        """
59
+        Execute the sql query constructed in the proxy and return the result.
60
+        If no query then return False.
61
+        @return: query result on success else False
62
+        """
63
+        if(self.proxy.__str__().split() == 'SELECT'):
64
+            if('bindparam' in self.proxy.__str__()):
65
+                #on test separement la présence de la clause bindparam et le type de l'argument correspondant
66
+                #car si la clause est présente mais que l'argument est defectueux on doit renvoyer False et non pas executer la requete
67
+                if(type(bindedparam) is list and type(bindedparam[0]) is dict):
68
+                    return self.sqlwrapper.rconn.execute(self.proxy, bindedparam)
69
+                else:
70
+                    return False
71
+            else:
72
+                return self.sqlwrapper.rconn.execute(self.proxy)
73
+        elif(self.proxy is not None):
74
+            if('bindparam' in self.proxy.__str__()):
75
+                if(type(bindedparam) is list and type(bindedparam[0]) is dict):
76
+                    return self.sqlwrapper.wconn.execute(self.proxy, bindedparam)
77
+                else:
78
+                    return False
79
+            else:
80
+                return self.sqlwrapper.wconn.execute(self.proxy)
81
+        else:
82
+            return False
83
+
84
+
85
+

+ 4
- 4
Database/sqlsetup.py View File

@@ -4,11 +4,11 @@ from Database.sqlwrapper import SqlWrapper
4 4
 
5 5
 class SQLSetup(object): 
6 6
 
7
-    def initDb(self):
8
-        db = SqlWrapper()
7
+    def initDb(self, dbconfname = 'default'):
8
+        db = SqlWrapper(read_db = dbconfname, write_db = dbconfname)
9 9
         tables = self.get_schema()
10
-        for table in tables:
11
-            err = db.create_table(table)
10
+        db.dropAll()
11
+        db.createAllFromConf(tables)
12 12
 
13 13
     def get_schema(self):
14 14
         tables = []

+ 392
- 118
Database/sqlwrapper.py View File

@@ -1,118 +1,201 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 import os
3
+import re
3 4
 import logging as logger
4 5
 
5 6
 import sqlalchemy as sqla
7
+from sqlalchemy.ext.compiler import compiles
6 8
 from django.conf import settings
7 9
 
10
+from Database.sqlalter import *
11
+
8 12
 #Logger config
9 13
 logger.getLogger().setLevel('DEBUG')
10 14
 #To be able to use dango confs
11 15
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Lodel.settings")
12 16
 
13 17
 class SqlWrapper(object):
14
-    """ A wrapper class to sqlalchemy 
15
-        Usefull to provide a standart API
16
-    """
17
-
18
-    ##Read Engine
19
-    rengine = None
20
-    ##Write Engine
21
-    wengine = None
18
+    """ A wrapper class to sqlalchemy
22 19
 
23
-    ##Read connection
24
-    rconn = None
25
-    ##Write connection
26
-    wconn = None
20
+        Usefull to provide a standart API
27 21
 
22
+        __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
23
+    """
24
+    ENGINES = {'mysql': {
25
+                    'driver': 'pymysql',
26
+                    'encoding': 'utf8'
27
+                },
28
+                'postgresql': {
29
+                    'driver': 'psycopg2',
30
+                    'encoding': 'utf8',
31
+                },
32
+                'sqlite': {
33
+                    'driver': 'pysqlite',
34
+                    'encoding': 'utf8',
35
+                },
36
+    }
37
+    
28 38
     ##Configuration dict alias for class access
29 39
     config=settings.LODEL2SQLWRAPPER
30
-    ##SqlAlchemy logging
31
-    sqla_logging = False
40
+    
41
+    ##Wrapper instance list
42
+    wrapinstance = dict()
32 43
 
33
-    def __init__(self, alchemy_logs=None):
44
+    def __init__(self, name=None, alchemy_logs=None, read_db = "default", write_db = "default"):
34 45
         """ Instanciate a new SqlWrapper
46
+            @param name str: The wrapper name
35 47
             @param alchemy_logs bool: If true activate sqlalchemy logger
48
+            @param read_db str: The name of the db conf
49
+            @param write_db str: The name of the db conf
50
+
51
+            @todo Better use of name (should use self.cfg['wrapper'][name] to get engines configs
52
+            @todo Is it a really good idea to store instance in class scope ? Maybe not !!
36 53
         """
37 54
         
38
-        if (alchemy_logs != None and bool(alchemy_logs) != self.__class__.sqla_logging):
39
-            #logging config changed for sqlalchemy
40
-            self.__class__.restart()
55
+        self.sqlalogging = False if alchemy_logs == None else bool(alchemy_logs)
41 56
 
42
-        if not self.__class__.started():
43
-            self.__class__.start()
44
-        
57
+        if name == None:
58
+            self.name = read_db+'+'+write_db
59
+        else:
60
+            self.name = name
61
+    
62
+        self.r_dbconf = read_db
63
+        self.w_dbconf = write_db
64
+
65
+        self._checkConf() #raise if errors in configuration
66
+
67
+        if self.name in self.__class__.wrapinstance:
68
+            logger.warning("A SqlWrapper with the name "+self.name+" allready exist. Replacing the old one by the new one")
69
+        SqlWrapper.wrapinstance[self.name] = self
70
+
71
+        #Engine and wrapper initialisation
72
+        self.r_engine = self._getEngine(True, self.sqlalogging)
73
+        self.w_engine = self._getEngine(False, self.sqlalogging)
74
+        self.r_conn = None
75
+        self.w_conn = None
76
+
77
+
78
+        self.metadata = None #TODO : use it to load all db schema in 1 request and don't load it each table instanciation
79
+        self.meta_crea = None
80
+
81
+        logger.debug("New wrapper instance : <"+self.name+" read:"+str(self.r_engine)+" write:"+str(self.w_engine))
45 82
         pass
46 83
 
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
52
-        """
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())
84
+    @property
85
+    def cfg(self):
86
+        """ Return the SqlWrapper.config dict """
87
+        return self.__class__.config;
88
+    @property
89
+    def _engines_cfg(self):
90
+        return self.__class__.ENGINES;
56 91
 
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()
92
+    @property
93
+    def meta(self):
94
+        if self.metadata == None:
95
+            self.renewMetaData()
96
+        return self.metadata
97
+
98
+    def renewMetaData(self):
99
+        """ (Re)load the database schema """
100
+        if self.metadata == None:
101
+            self.metadata = sqla.MetaData(bind=self.r_engine, reflect=True)
64 102
         else:
65
-            c.wconn = c.wengine.connect()
66
-        return True #TODO attention c'est pas checké...
103
+            self.metadata = sqla.MetaData(bind=self.r_engine, reflect=True)
67 104
 
68
-    @classmethod   
69
-    def conn(c, read=True):
105
+    @property
106
+    def rconn(self):
107
+        """ Return the read connection
108
+            @warning Do not store the connection, call this method each time you need it
109
+        """
110
+        return self._getConnection(True)
111
+    @property
112
+    def wconn(self):
113
+        """ Return the write connection
114
+            @warning Do not store the connection, call this method each time you need it
115
+        """
116
+        return self._getConnection(False)
117
+
118
+    def _getConnection(self, read):
119
+        """ Return an opened connection
120
+            @param read bool: If true return the reading connection
121
+            @return A sqlAlchemy db connection
122
+            @private
123
+        """
70 124
         if read:
71
-            res = c.rconn
125
+            r = self.r_conn
72 126
         else:
73
-            res = c.wconn
127
+            r = self.w_conn
74 128
 
75
-        if res == None:
76
-            if not c.connect(read):
77
-                raise RuntimeError('Unable to connect to Db')
78
-            return c.conn(read)
129
+        if r == None:
130
+            #Connection not yet opened
131
+            self.connect(read)
132
+            r = self._getConnection(read) #TODO : Un truc plus safe/propre qu'un appel reccursif ?
133
+        return r
79 134
 
80
-        return c.rconn
81 135
 
82
-    @classmethod
83
-    def rc(c): return c.conn(True)
84
-    @classmethod
85
-    def wc(c): return c.conn(False)
136
+    def connect(self, read = None):
137
+        """ Open a connection to a database
138
+            @param read bool|None: If None connect both, if True only connect the read side (False the write side)
139
+            @return None
140
+        """
141
+        if read or read == None:
142
+            if self.r_conn != None:
143
+                logger.debug(' SqlWrapper("'+self.name+'") Unable to connect, already connected')
144
+            else:
145
+                self.r_conn = self.r_engine.connect()
86 146
 
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
147
+        if not read or read == None:
148
+            if self.w_conn != None:
149
+                logger.debug(' SqlWrapper("'+self.name+'") Unable to connect, already connected')
150
+            else:
151
+                self.w_conn = self.w_engine.connect()
152
+
153
+    def disconnect(self, read = None):
154
+        """ Close a connection to a database
155
+            @param read bool|None: If None disconnect both, if True only connect the read side (False the write side)
156
+            @return None
93 157
         """
94
-        c.checkConf()
95
-        if c.started():
96
-            logger.warning('Starting SqlWrapper but it is allready started')
97
-            return False
158
+        if read or read == None:
159
+            if self.r_conn == None:
160
+                logger.info('Unable to close read connection : connection not opened')
161
+            else:
162
+                self.r_conn.close()
163
+            self.r_conn = None
98 164
 
99
-        c.rengine = c._getEngine(read=True, sqlalogging=None)
100
-        c.wengine = c._getEngine(read=False, sqlalogging=None)
101
-        return True
165
+        if not read or read == None:
166
+            if self.r_conn == None:
167
+                logger.info('Unable to close write connection : connection not opened')
168
+            else:
169
+                self.w_conn.close()
170
+            self.w_conn = None
102 171
 
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
172
+    def reconnect(self, read = None):
173
+        """ Close and reopen a connection to a database
174
+            @param read bool|None: If None disconnect both, if True only connect the read side (False the write side)
175
+            @return None
176
+        """
177
+        self.disconnect(read)
178
+        self.connect(read)
113 179
 
114 180
     @classmethod
115
-    def _getEngine(c, read=True, sqlalogging = None):
181
+    def reconnectAll(c, read = None):
182
+        """ Reconnect all the wrappers 
183
+            @static
184
+        """
185
+        for wname in c.wrapinstance:
186
+            c.wrapinstance[wname].reconnect(read)
187
+            
188
+    def Table(self, tname):
189
+        """ Instanciate a new SqlAlchemy Table
190
+            @param tname str: The table name
191
+            @return A new instance of SqlAlchemy::Table
192
+        """
193
+        if not isinstance(tname, str):
194
+            return TypeError('Excepting a <class str> but got a '+str(type(tname)))
195
+        #return sqla.Table(tname, self.meta, autoload_with=self.r_engine, autoload=True)
196
+        return sqla.Table(tname, self.meta)
197
+
198
+    def _getEngine(self, read=True, sqlalogging = None):
116 199
         """ Return a sqlalchemy engine
117 200
             @param read bool: If True return the read engine, else 
118 201
             return the write one
@@ -121,16 +204,10 @@ class SqlWrapper(object):
121 204
             @todo Put the check on db config in SqlWrapper.checkConf()
122 205
         """
123 206
         #Loading confs
124
-        connconf = 'dbread' if read else 'dbwrite'
125
-        dbconf = connconf if connconf in c.config['db'] else 'default'
207
+        cfg = self.cfg['db'][self.r_dbconf if read else self.w_dbconf]
126 208
 
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']+'://'
209
+        edata = self._engines_cfg[cfg['ENGINE']] #engine infos
210
+        conn_str = ""
134 211
 
135 212
         if cfg['ENGINE'] == 'sqlite':
136 213
             #Sqlite connection string
@@ -153,10 +230,26 @@ class SqlWrapper(object):
153 230
             conn_str = '%s+%s://'%(cfg['ENGINE'], edata['driver'])
154 231
             conn_str += '%s@%s/%s'%(user,host,cfg['NAME'])
155 232
 
156
-        return sqla.create_engine(conn_str, encoding=edata['encoding'], echo=c._sqllog(sqlalogging))
233
+
234
+        ret = sqla.create_engine(conn_str, encoding=edata['encoding'], echo=self.sqlalogging)
235
+
236
+        logger.debug("Getting engine :"+str(ret))
237
+
238
+        return ret
157 239
 
158 240
     @classmethod
159
-    def checkConf(c):
241
+    def getWrapper(c, name):
242
+        """ Return a wrapper instance from a wrapper name
243
+            @param name str: The wrapper name
244
+            @return a SqlWrapper instance
245
+
246
+            @throw KeyError
247
+        """
248
+        if name not in c.wrapinstance:
249
+            raise KeyError("No wrapper named '"+name+"' exists")
250
+        return c.wrapinstance[name]
251
+
252
+    def _checkConf(self):
160 253
         """ Class method that check the configuration
161 254
             
162 255
             Configuration looks like
@@ -172,44 +265,225 @@ class SqlWrapper(object):
172 265
             - dbwrite (mandatory if no default db)
173 266
         """
174 267
         err = []
175
-        if 'db' not in c.config:
268
+        if 'db' not in self.cfg:
176 269
             err.append('Missing "db" in configuration')
177 270
         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+'"')
271
+            for dbname in [self.r_dbconf, self.w_dbconf]:
272
+                if dbname not in self.cfg['db']:    
273
+                    err.append('Missing "'+dbname+'" db configuration')
187 274
                 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
-
275
+                    db = self.cfg['db'][dbname]
276
+                    if 'ENGINE' not in db:
277
+                        err.append('Missing "ENGINE" in database "'+db+'"')
278
+                    else:
279
+                        if db['ENGINE'] not in self._engines_cfg:
280
+                            err.append('Unknown engine "'+db['ENGINE']+'"')
281
+                        elif db['ENGINE'] != 'sqlite' and 'USER' not in db:
282
+                            err.append('Missing "User" in configuration of database "'+dbname+'"')
283
+                    if 'NAME' not in db:
284
+                        err.append('Missing "NAME" in database "'+dbname+'"')
285
+                        
202 286
         if len(err)>0:
203 287
             err_str = "\n"
204 288
             for e in err:
205 289
                 err_str += "\t\t"+e+"\n"
206 290
             raise NameError('Configuration errors in LODEL2SQLWRAPPER:'+err_str)
207
-                
291
+    
292
+    def dropAll(self):
293
+        """ Drop ALL tables from the database """
294
+        if not settings.DEBUG:
295
+            logger.critical("Trying to drop all tables but we are not in DEBUG !!!")
296
+            raise RuntimeError("Trying to drop all tables but we are not in DEBUG !!!")
297
+        meta = sqla.MetaData(bind=self.w_engine, reflect = True)
298
+        meta.drop_all()
299
+        pass
208 300
 
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
301
+    def createAllFromConf(self, schema):
302
+        """ Create a bunch of tables from a schema
303
+            @param schema list: A list of table schema
304
+            @see SqlWrapper::createTable()
305
+        """
306
+        self.meta_crea = sqla.MetaData()
307
+
308
+        logger.info("Running function createAllFromConf")
309
+        for i,table in enumerate(schema):
310
+            if not isinstance(table, dict):
311
+                raise TypeError("Excepted a list of dict but got a "+str(type(schema))+" in the list")
312
+            self.createTable(**table)
313
+
314
+        self.meta_crea.create_all(bind = self.w_engine)
315
+        logger.info("All tables created")
316
+        self.meta_crea = None
317
+        self.renewMetaData()
318
+        pass
319
+            
320
+    def createTable(self, name, columns, **kw):
321
+        """ Create a table
322
+            @param name str: The table name
323
+            @param columns list: A list of columns description dict
324
+            @param extra dict: Extra arguments for table creation
325
+            @see SqlWrapper::createColumn()
326
+        """
327
+
328
+        if self.meta_crea == None:
329
+            self.meta_crea = sqla.MetaData()
330
+            crea_now = True
331
+        else:
332
+            crea_now = False
333
+
334
+        if not isinstance(name, str):
335
+            raise TypeError("<class str> excepted for table name, but got "+type(name))
336
+
337
+        res = sqla.Table(name, self.meta_crea, **kw)
338
+        for i,col in enumerate(columns):
339
+            res.append_column(self.createColumn(**col))
340
+
341
+        if crea_now:
342
+            self.meta_crea.create_all(self.w_engine)
343
+
344
+        #logger.debug("Table '"+name+"' created")
345
+        pass
346
+
347
+    def createColumn(self, **kwargs):
348
+        """ Create a Column
349
+            
350
+            Accepte named parameters :
351
+                - name : The column name
352
+                - type : see SqlWrapper::_strToSqlAType()
353
+                - extra : a dict like { "primarykey":True, "nullable":False, "default":"test"...}
354
+            @param **kwargs 
355
+        """
356
+        if not 'name' in kwargs or ('type' not in kwargs and 'type_' not in kwargs):
357
+            pass#ERROR
358
+
359
+        #Converting parameters
360
+        if 'type_' not in kwargs and 'type' in kwargs:
361
+            kwargs['type_'] = self._strToSqlAType(kwargs['type'])
362
+            del kwargs['type']
363
+
364
+        if 'extra' in kwargs:
365
+            #put the extra keys in kwargs
366
+            for exname in kwargs['extra']:
367
+                kwargs[exname] = kwargs['extra'][exname]
368
+            del kwargs['extra']
369
+
370
+        if 'foreignkey' in kwargs:
371
+            #Instanciate a fk
372
+            fk = sqla.ForeignKey(kwargs['foreignkey'])
373
+            del kwargs['foreignkey']
374
+        else:
375
+            fk = None
376
+
377
+        if 'primarykey' in kwargs:
378
+            #renaming primary_key in primarykey in kwargs
379
+            kwargs['primary_key'] = kwargs['primarykey']
380
+            del kwargs['primarykey']
381
+
382
+        res = sqla.Column(**kwargs)
383
+
384
+        if fk != None:
385
+            res.append_foreign_key(fk)
386
+
387
+        #logger.debug("Column '"+kwargs['name']+"' created")
388
+        return res
389
+    
390
+    def _strToSqlAType(self, strtype):
391
+        """ Convert a string to an sqlAlchemy column type """
392
+
393
+        if 'VARCHAR' in strtype:
394
+            return self._strToVarchar(strtype)
395
+        else:
396
+            try:
397
+                return getattr(sqla, strtype)
398
+            except AttributeError:
399
+                raise NameError("Unknown type '"+strtype+"'")
400
+        pass
401
+
402
+    def _strToVarchar(self, vstr):
403
+        """ Convert a string like 'VARCHAR(XX)' (with XX an integer) to a SqlAlchemy varchar type"""
404
+        check_length = re.search(re.compile('VARCHAR\(([\d]+)\)', re.IGNORECASE), vstr)
405
+        column_length = int(check_length.groups()[0]) if check_length else None
406
+        return sqla.VARCHAR(length=column_length)
407
+     
408
+
409
+    def dropColumn(self, tname, colname):
410
+        """ Drop a column from a table
411
+            @param tname str|sqlalchemy.Table: The table name or a Table object
412
+            @param colname str|sqlalchemy.Column: The column name or a column object
413
+            @return None
414
+        """
415
+        if tname not in self.meta.tables: #Useless ?
416
+            raise NameError("The table '"+tname+"' dont exist")
417
+        table = self.Table(tname)
418
+        col = sqla.Column(colname)
419
+
420
+        ddl = DropColumn(table, col)
421
+        sql = ddl.compile(dialect=self.w_engine.dialect)
422
+        sql = str(sql)
423
+        logger.debug("Executing SQL  : '"+sql+"'")
424
+        ret = bool(self.w_engine.execute(sql))
425
+
426
+        self.renewMetaData()
427
+        return ret
428
+
429
+    
430
+    def addColumn(self, tname, colname, coltype):
431
+        """ Add a column to a table
432
+            @param tname str: The table name
433
+            @param colname str: The column name
434
+            @param coltype str: The new column type
435
+
436
+            @return True if query success False if it fails
437
+        """
438
+        if tname not in self.meta.tables: #Useless ?
439
+            raise NameError("The table '"+tname+"' dont exist")
440
+        table = self.Table(tname)
441
+        newcol = self.createColumn(name=colname, type_ = coltype)
442
+
443
+        ddl = AddColumn(table, newcol)
444
+        sql = ddl.compile(dialect=self.w_engine.dialect)
445
+        sql = str(sql)
446
+        logger.debug("Executing SQL  : '"+sql+"'")
447
+        ret = bool(self.wconn.execute(sql))
448
+
449
+        self.renewMetaData()
450
+        return ret
451
+
452
+    def alterColumn(self, tname, colname, col_newtype):
453
+        """ Change the type of a column
454
+            @param tname str: The table name
455
+            @param colname str: The column name
456
+            @param col_newtype str: The column new type
457
+
458
+            @return True if query successs False if it fails
459
+        """
460
+        
461
+        if tname not in self.meta.tables: #Useless ?
462
+            raise NameError("The table '"+tname+"' dont exist")
463
+
464
+        col = self.createColumn(name=colname, type_=col_newtype)
465
+        table = self.Table(tname)
466
+
467
+        ddl = AlterColumn(table, newcol)
468
+        sql = ddl.compile(dialect=self.w_engine.dialect)
469
+        sql = str(sql)
470
+        logger.debug("Executing SQL  : '"+sql+"'")
471
+        ret = bool(self.wconn.execute(sql))
472
+
473
+        self.renewMetaData()
474
+        return ret
475
+
476
+    def _debug__printSchema(self):
477
+        """ Debug function to print the db schema """
478
+        print(self.meta)
479
+        for tname in self.meta.tables:
480
+            self._debug__printTable(tname)
481
+
482
+    def _debug__printTable(self, tname):
483
+        t = self.meta.tables[tname]
484
+        tstr = 'Table : "'+tname+'" :\n'
485
+        for c in t.c:
486
+            tstr += '\t\t"'+c.name+'"('+str(c.type)+') \n'
487
+        print(tstr)
488
+            
215 489
 

+ 0
- 0
Database/test/__init__.py View File


+ 0
- 519
Database/test/test_sqlwrapper.py View File

@@ -1,519 +0,0 @@
1
-import os
2
-import logging
3
-import random
4
-
5
-from unittest import TestCase
6
-from unittest.mock import MagicMock, Mock, patch, call
7
-import unittest
8
-
9
-import sqlalchemy
10
-from Database.sqlwrapper import SqlWrapper
11
-
12
-from django.conf import settings
13
-from Database.sqlsettings import SQLSettings
14
-
15
-os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Lodel.settings")
16
-
17
-BADNAMES = ['',  "  \t  ", "bad--", "%", "*", "`", '"', "'", "\0", "hello`World", 'Hello"world', 'foo%', '*bar.*', '%%', "hello\0world", print, 42 ]
18
-
19
-for c in ('*','%','`', "'", '"', "\\"):
20
-    for _ in range(16):
21
-        c = "\\"+c
22
-        BADNAMES.append(c)
23
-BADNAMES+=[chr(i) for i in list(range(0,0x09))+[0x0b, 0x0c]+list(range(0x0e, 0x01f))]
24
-
25
-class SqlWrapperTests(TestCase):
26
-
27
-    def setUp(self):
28
-        #Overwriting database conf
29
-        SQLSettings.DB_READ_CONNECTION_NAME = 'testdb'
30
-        SQLSettings.DB_WRITE_CONNECTION_NAME = 'testdb'
31
-        
32
-
33
-        self.testdb = os.path.join('/tmp/', 'lodel2_testdb.sqlite3')
34
-
35
-        settings.DATABASES['testdb'] = {
36
-            'ENGINE': 'sqlite',
37
-            'NAME': self.testdb,
38
-        }
39
-        #Disable logging but CRITICAL
40
-        logging.basicConfig(level=logging.CRITICAL)
41
-        pass
42
-
43
-    def tearDown(self):
44
-        try:
45
-            os.unlink(self.testdb) #Removing the test database
46
-        except FileNotFoundError: pass
47
-    
48
-    """ Testing standart instanciation of sqlwrapper """
49
-    def test_init_sqlwrapper(self):
50
-        sw = SqlWrapper()
51
-        self.assertIsInstance(sw, SqlWrapper)
52
-        sw2 = SqlWrapper()
53
-        self.assertIsInstance(sw2, SqlWrapper)
54
-
55
-    def test_get_engine(self):
56
-        sw = SqlWrapper()
57
-        engine = sw.get_engine(SQLSettings.DB_READ_CONNECTION_NAME)
58
-        self.assertIsInstance(engine, sqlalchemy.engine.base.Engine)
59
-    
60
-    def test_get_engine_badargs(self):
61
-        sw = SqlWrapper()
62
-        with self.assertRaises(Exception):
63
-            sw.get_engine(0)
64
-        with self.assertRaises(Exception):
65
-            sw.get_engine('')
66
-        with self.assertRaises(Exception):
67
-            sw.get_engine('           ')
68
-
69
-    
70
-    @patch.object(SqlWrapper, 'get_read_engine')
71
-    def test_execute_queries(self, mock_read):
72
-        queries = [ 'SELECT * FROM foo', 'SELECT * FROM bar', 'SELECT foo.id, bar.id FROM foo, bar WHERE foo.id = 42 AND bar.name = \'hello world !\'' ]
73
-
74
-        mock_read.return_value = MagicMock()
75
-        mock_engine = mock_read.return_value
76
-        mock_engine.connect.return_value = MagicMock()
77
-        mock_conn = mock_engine.connect.return_value
78
-        sw = SqlWrapper()
79
-
80
-        #One query execution
81
-        sw.execute(queries[0], 'read')
82
-        mock_conn.execute.assert_called_with(queries[0])
83
-        mock_conn.reset_mock()
84
-
85
-        #multiple queries execution
86
-        sw.execute(queries, 'read')
87
-
88
-        except_calls = [ call(q) for q in queries ]
89
-        self.assertEqual(except_calls, mock_conn.execute.mock_calls)
90
-
91
-    @patch.object(sqlalchemy.ForeignKeyConstraint, '__init__')
92
-    def test_create_fk_constraint_mock(self, mock_fk):
93
-        mock_fk.return_value = None
94
-
95
-        sw = SqlWrapper()
96
-        sw.create_foreign_key_constraint_object('foo','bar', 'foobar')
97
-        mock_fk.asser_called_with(['foo'], ['bar'], 'foobar')
98
-
99
-    def test_create_fk_constraint(self):
100
-        sw = SqlWrapper()
101
-        fk = sw.create_foreign_key_constraint_object('foo', 'bar', 'foobar')
102
-        self.assertIsInstance(fk, sqlalchemy.ForeignKeyConstraint)
103
-        
104
-    @unittest.skip('Dev') #TODO remove it
105
-    def test_create_fk_constraint_badargs(self):
106
-        sw = SqlWrapper()
107
-
108
-        
109
-        with self.assertRaises(Exception):
110
-            sw.create_foreign_key_constraint_object(['foo', 'bar'], 'foofoo', 'babar')
111
-        with self.assertRaises(Exception):
112
-            sw.create_foreign_key_constraint_object('bar', 2, 'babar')
113
-        with self.assertRaises(Exception):
114
-            sw.create_foreign_key_constraint_object(None, 'foo', 'bar')
115
-        with self.assertRaises(Exception):
116
-            sw.create_foreign_key_constraint_object('bar', None, 'babar')
117
-        with self.assertRaises(Exception):
118
-            sw.create_foreign_key_constraint_object(None, None, 'babar')
119
-        with self.assertRaises(Exception):
120
-            sw.create_foreign_key_constraint_object(print, 'foo', 'babar')
121
-        with self.assertRaises(Exception):
122
-            sw.create_foreign_key_constraint_object('foo', print, 'babar')
123
-        with self.assertRaises(Exception):
124
-            sw.create_foreign_key_constraint_object('foo', 'bar', print)
125
-        with self.assertRaises(Exception):
126
-            sw.create_foreign_key_constraint_object('bar', 'foo', 42)
127
-        with self.assertRaises(Exception):
128
-            sw.create_foreign_key_constraint_object('bar', 'foo', '   ')
129
-        with self.assertRaises(Exception):
130
-            sw.create_foreign_key_constraint_object(" \t  ", 'foo')
131
-        with self.assertRaises(Exception):
132
-            sw.create_foreign_key_constraint_object('bar', 'foo', "  \t ")
133
-
134
-        foocol = sqlalchemy.Column('foo',sqlalchemy.INTEGER)
135
-
136
-        with self.assertRaises(Exception):
137
-            sw.create_foreign_key_constraint_object(foocol, foocol)
138
-        with self.assertRaises(Exception):
139
-            sw.create_foreign_key_constraint_object(foocol, 'bar')
140
-        with self.assertRaises(Exception):
141
-            sw.create_foreign_key_constraint_object('foo', foocol)
142
-        with self.assertRaises(Exception):
143
-            sw.create_foreign_key_constraint_object('foo', 'bar', foocol)
144
-    
145
-    @patch.object(sqlalchemy.Column, '__init__')
146
-    def test_create_column(self, mockcol):
147
-        mockcol.return_value = None
148
-        sw = SqlWrapper()
149
-        foo = sw.create_column_object('foo', 'INTEGER')
150
-        mockcol.assert_called_once_with('foo', sqlalchemy.INTEGER)
151
-        mockcol.reset_mock()
152
-
153
-        foo = sw.create_column_object('foo', 'VARCHAR(50)')
154
-        self.assertEqual(mockcol.call_args[0][0], 'foo')
155
-        self.assertIsInstance(mockcol.call_args[0][1], sqlalchemy.VARCHAR)
156
-        self.assertEqual(mockcol.call_args[0][1].length, 50)
157
-        mockcol.reset_mock()
158
-
159
-        foo = sw.create_column_object('foo', 'TEXT')
160
-        mockcol.assert_called_once_with('foo', sqlalchemy.TEXT)
161
-        mockcol.reset_mock()
162
-
163
-        foo = sw.create_column_object('foo', 'DATE')
164
-        mockcol.assert_called_once_with('foo', sqlalchemy.DATE)
165
-        mockcol.reset_mock()
166
-
167
-        foo = sw.create_column_object('foo', 'BOOLEAN')
168
-        mockcol.assert_called_once_with('foo', sqlalchemy.BOOLEAN)
169
-        mockcol.reset_mock()
170
- 
171
-        xtrmlongname = ''
172
-        for _ in range(200):
173
-            xtrmlongname += 'veryvery'
174
-        xtrmlongname += 'longname'
175
-
176
-        foo = sw.create_column_object(xtrmlongname, 'TEXT')
177
-        mockcol.assert_called_once_with(xtrmlongname, sqlalchemy.TEXT)
178
-        mockcol.reset_mock()
179
-
180
-    def test_create_column_extra_fk(self):
181
-        sw = SqlWrapper()
182
-
183
-        extra = { 'foreignkey': 'bar' }
184
-        rescol = sw.create_column_object('foo', 'INTEGER', extra)
185
-        self.assertIsInstance(rescol, sqlalchemy.Column)
186
-        fk = rescol.foreign_keys.pop()
187
-        self.assertIsInstance(fk, sqlalchemy.ForeignKey)
188
-        self.assertEqual(fk._colspec, 'bar')
189
-
190
-    def test_create_column_extra_default(self):
191
-        sw = SqlWrapper()
192
-
193
-
194
-        extra = { 'default': None, 'nullable': True }
195
-        rescol = sw.create_column_object('foo', 'INTEGER', extra)
196
-        self.assertIsInstance(rescol.default, sqlalchemy.ColumnDefault)
197
-        self.assertEqual(rescol.default.arg, None)
198
-
199
-        extra = { 'default': "NULL", 'nullable': True }
200
-        rescol = sw.create_column_object('foo', 'INTEGER', extra)
201
-        self.assertIsInstance(rescol.default, sqlalchemy.ColumnDefault)
202
-        self.assertEqual(rescol.default.arg, "NULL")
203
-
204
-        extra = { 'default': 'null', 'nullable': True }
205
-        rescol = sw.create_column_object('foo', 'INTEGER', extra)
206
-        self.assertIsInstance(rescol.default, sqlalchemy.ColumnDefault)
207
-        self.assertEqual(rescol.default.arg, 'null')
208
-
209
-        extra = { 'default': 42 }
210
-        rescol = sw.create_column_object('foo', 'INTEGER', extra)
211
-        self.assertIsInstance(rescol.default, sqlalchemy.ColumnDefault)
212
-        self.assertEqual(rescol.default.arg, 42)
213
-
214
-        extra = { 'default': 'foobardefault' }
215
-        rescol = sw.create_column_object('foo', 'VARCHAR(50)', extra)
216
-        self.assertIsInstance(rescol.default, sqlalchemy.ColumnDefault)
217
-        self.assertEqual(rescol.default.arg, 'foobardefault')
218
-
219
-        extra = { 'default': 'foodefault' }
220
-        rescol = sw.create_column_object('foo', 'TEXT', extra)
221
-        self.assertIsInstance(rescol.default, sqlalchemy.ColumnDefault)
222
-        self.assertEqual(rescol.default.arg, 'foodefault')
223
-
224
-        extra = { 'default': True }
225
-        rescol = sw.create_column_object('foo', 'BOOLEAN', extra)
226
-        self.assertIsInstance(rescol.default, sqlalchemy.ColumnDefault)
227
-        self.assertEqual(rescol.default.arg, True)
228
-
229
-        extra = { 'default': False }
230
-        rescol = sw.create_column_object('foo', 'BOOLEAN', extra)
231
-        self.assertIsInstance(rescol.default, sqlalchemy.ColumnDefault)
232
-        self.assertEqual(rescol.default.arg, False)
233
-
234
-        extra = { 'default': "true" }
235
-        rescol = sw.create_column_object('foo', 'BOOLEAN', extra)
236
-        self.assertIsInstance(rescol.default, sqlalchemy.ColumnDefault)
237
-        self.assertEqual(rescol.default.arg, "true")
238
-
239
-        extra = { 'default': 0 }
240
-        rescol = sw.create_column_object('foo', 'BOOLEAN', extra)
241
-        self.assertIsInstance(rescol.default, sqlalchemy.ColumnDefault)
242
-        self.assertEqual(rescol.default.arg, 0)
243
-
244
-        extra = { 'default': 1 }
245
-        rescol = sw.create_column_object('foo', 'BOOLEAN', extra)
246
-        self.assertIsInstance(rescol.default, sqlalchemy.ColumnDefault)
247
-        self.assertEqual(rescol.default.arg, 1)
248
-
249
-
250
-    def test_create_column_extra_pknull(self):
251
-        sw = SqlWrapper()
252
-
253
-        for b in (True,False):
254
-            extra = { 'primarykey': b }
255
-            rescol = sw.create_column_object('foo', 'INTEGER', extra)
256
-            self.assertIsInstance(rescol, sqlalchemy.Column)
257
-            self.assertEqual(rescol.primary_key, b)
258
-
259
-            extra = { 'nullable': b }
260
-            rescol = sw.create_column_object('foo', 'INTEGER', extra)
261
-            self.assertIsInstance(rescol, sqlalchemy.Column)
262
-            self.assertEqual(rescol.nullable, b)
263
-
264
-            extra = { 'primarykey' : b, 'nullable': not b }
265
-            rescol = sw.create_column_object('foo', 'INTEGER', extra)
266
-            self.assertIsInstance(rescol, sqlalchemy.Column)
267
-            self.assertEqual(rescol.primary_key, b)
268
-            self.assertEqual(rescol.nullable, not b)
269
-
270
-            extra = { 'primarykey' : b, 'nullable': b }
271
-            rescol = sw.create_column_object('foo', 'INTEGER', extra)
272
-            self.assertIsInstance(rescol, sqlalchemy.Column)
273
-            self.assertEqual(rescol.primary_key, b)
274
-            self.assertEqual(rescol.nullable, b)
275
-
276
-
277
-
278
-    def test_create_column_extra_all(self):
279
-        sw = SqlWrapper()
280
-
281
-        extra = { 'primarykey': False, 'nullable': False, 'default':42, 'foreignkey': 'foobar'}
282
-        rescol = sw.create_column_object('foo', 'INTEGER', extra)
283
-        self.assertIsInstance(rescol, sqlalchemy.Column)
284
-        self.assertEqual(rescol.primary_key, False)
285
-        self.assertEqual(rescol.nullable, False)
286
-        self.assertIsInstance(rescol.default, sqlalchemy.ColumnDefault)
287
-        self.assertEqual(rescol.default.arg, 42)
288
-        fk = rescol.foreign_keys.pop()
289
-        self.assertIsInstance(fk, sqlalchemy.ForeignKey)
290
-        self.assertEqual(fk._colspec, 'foobar')
291
-
292
-    @unittest.skip('Dev') #TODO remove it
293
-    def test_create_column_badargs(self):
294
-        sw = SqlWrapper()
295
-        
296
-        cc = sw.create_column_object
297
-        ain = self.assertIsNone
298
-        
299
-        for bname in BADNAMES:
300
-            ain(cc(bname, 'INTEGER'))
301
-
302
-        ain(cc('c', 'INNNTEGER'))
303
-        ain(cc(" \t\t  ", 'TEXT'))
304
-        ain(cc('c', '             '))
305
-        ain(cc('c', 'VARCHAR(foo)'))
306
-        ain(cc('supercol', 'SUPERNOTTYPEDVARCHARSTR'))
307
-
308
-        ain(cc('c', None))
309
-        ain(cc(None, None))
310
-
311
-    @unittest.skip('Dev') #TODO remove it
312
-    def test_create_column_badextra(self):
313
-        sw = SqlWrapper()
314
-
315
-        cc = sw.create_column_object
316
-        ain = self.assertIsNone
317
-        
318
-        #Put junk in extra datas
319
-        for xtra_name in [ 'primarykey', 'nullable', 'foreignkey' ]:
320
-            for junk in [ print, sqlalchemy, SqlWrapper, '   ' ]+BADNAMES:
321
-                ain(cc('c', 'TEXT', { xtra_name: junk }))
322
-
323
-        for junk in [True, False, 52, '   ']+BADNAMES:
324
-            ain(cc('c', 'TEXT', { 'foreignkey': junk }))
325
-
326
-        for xtra_name in ('primarykey', 'nullalble'):
327
-            for junk in [4096, 'HELLO WORLD !', '   ']+BADNAMES:
328
-                ain(cc('c', 'TEXT', { xtra_name, junk } ))
329
-
330
-    @patch.object(sqlalchemy.Table, '__init__')
331
-    def test_create_table(self, mock_table):
332
-        #TODO check constraints
333
-        """ Create tables and check that the names are ok """
334
-        mock_table.return_value = None
335
-        sw = SqlWrapper()
336
-        
337
-        cols = [
338
-            { 'name': 'supercol', 'type': 'INTEGER', 'extra': { 'nullable': True } },
339
-            { 'name': 'superpk', 'type': 'INTEGER', 'extra': { 'primarykey': True} },
340
-            { 'name': 'col2', 'type': 'TEXT' }
341
-        ]
342
-        
343
-        for table_name in ['supertable', 'besttableever', 'foo-table']:
344
-
345
-            params = { 'name': table_name, 'columns': cols, 'constraints':dict() }
346
-
347
-            self.assertTrue(sw.create_table(params))
348
-            self.assertEqual(mock_table.call_args[0][0], table_name)
349
-        pass
350
-    
351
-    @patch.object(sqlalchemy.Table, 'append_column')
352
-    def test_create_table_col(self, mock_append):
353
-        #TODO check constraints
354
-        """ Create a table and check that the columns are OK """
355
-        sw = SqlWrapper()
356
-        
357
-        table_name = 'supertablecol'
358
-
359
-        cols = [
360
-            { 'name': 'supercol', 'type': 'INTEGER', 'extra': { 'nullable': True } },
361
-            { 'name': 'superpk', 'type': 'INTEGER', 'extra': { 'primarykey': True} },
362
-            { 'name': 'col2', 'type': 'TEXT' }
363
-        ]
364
-
365
-        except_coltype = [ sqlalchemy.INTEGER, sqlalchemy.INTEGER, sqlalchemy.TEXT ]
366
-
367
-        params = { 'name': table_name, 'columns': cols, 'constraints':dict() }
368
-    
369
-        sw.create_table(params) #This call return false because of the mock on append_column
370
-        
371
-        self.assertEqual(len(mock_append.mock_calls), len(cols))
372
-
373
-        for n,c in enumerate(mock_append.mock_calls):
374
-            self.assertEqual(c[1][0].name, cols[n]['name'])
375
-            self.assertIsInstance(c[1][0].type, except_coltype[n])
376
-        pass
377
-    
378
-    def test_create_table_recreate(self):
379
-        #TODO check constraints
380
-        """ Try to create the same table 2 times (except a False return the second time) """
381
-        sw = SqlWrapper()
382
-
383
-        params = { 'name': 'redondant', 'columns': [{'name':'foocol', 'type': 'INTEGER'}]}
384
-
385
-        self.assertTrue(sw.create_table(params))
386
-
387
-        params['columns'] = [{'name':'barcol', 'type': 'INTEGER'}]
388
-
389
-        self.assertFalse(sw.create_table(params))
390
-
391
-    @unittest.skip('dev') #TODO remove it
392
-    def test_create_table_badargs(self):
393
-        sw = SqlWrapper()
394
-
395
-        af = self.assertFalse
396
-        ct = sw.create_table
397
-
398
-        foocol = {'name': 'foocol', 'type': 'INTEGER'}
399
-
400
-        p = {'name': 'foo'}
401
-        af(ct(p)) #no columns
402
-        for bname in BADNAMES: #bad column name
403
-            p['columns'] = bname
404
-            af(ct(p))
405
-        p['columns']=[]; af(ct(p)) #empty columns TODO Or return True is normal ???
406
-
407
-
408
-        p = {'columns':[foocol]}
409
-        af(ct(p)) #no name
410
-        for bname in BADNAMES:
411
-            p['name'] = bname
412
-            af(ct(p))
413
-        pass
414
-
415
-    
416
-    def create_test_table(self, sw):
417
-        """ Create a table for test purpose """
418
-        table_name = 'testtable'
419
-        cols = [
420
-            { 'name': 'pk', 'type': 'INTEGER', 'extra': {'primarykey': True} },
421
-            { 'name': 'testtxt', 'type': 'TEXT', 'extra': {'nullable': True, 'default': 'hello'} },
422
-            { 'name': 'testchr', 'type': 'VARCHAR(50)', 'extra': {'nullable': True, 'default': 'hello world'} },
423
-        ]
424
-        
425
-        sw.create_table( { 'name': table_name, 'columns': cols} )
426
-        pass
427
-    
428
-    def test_get_table(self):
429
-        """ Try to get the testtable (check the name and type of return) """
430
-        sw = SqlWrapper()
431
-
432
-        self.create_test_table(sw)
433
-
434
-        rt = sw.get_table('testtable')
435
-        self.assertIsInstance(rt, sqlalchemy.Table)
436
-        self.assertEqual(rt.name, 'testtable')
437
-
438
-        rt = sw.get_table('testtable', 'write')
439
-        self.assertIsInstance(rt, sqlalchemy.Table)
440
-        self.assertEqual(rt.name, 'testtable')
441
-
442
-        rt = sw.get_table('testtable', 'read')
443
-        self.assertIsInstance(rt, sqlalchemy.Table)
444
-        self.assertEqual(rt.name, 'testtable')
445
-        pass
446
-
447
-    @unittest.skip('dev') #TODO remove skip
448
-    def test_get_table_badargs(self):
449
-        sw = SqlWrapper()
450
-
451
-        self.create_test_table(sw)
452
-
453
-        for badname in BADNAMES:
454
-            with self.assertRaises((sqlalchemy.exc.NoSuchTableError, Exception)):
455
-                sw.get_table(badname)
456
-
457
-        with self.assertRaises(sqlalchemy.exc.NoSuchTableError):
458
-            sw.get_table('FOOBAR')
459
-        with self.assertRaises(Exception):
460
-            sw.get_table(print)
461
-        with self.assertRaises(Exception):
462
-            sw.get_table(1)
463
-
464
-        #bad action types
465
-        with self.assertRaises(Exception):
466
-            sw.get_table('testtable', print)
467
-        with self.assertRaises(Exception):
468
-            sw.get_table('testtable', 'foobar')
469
-        with self.assertRaises(Exception):
470
-            sw.get_table('testtable', 42)
471
-        pass
472
-
473
-    def test_drop_table(self):
474
-        sw = SqlWrapper()
475
-        
476
-        self.create_test_table(sw)
477
-
478
-        self.assertTrue(sw.drop_table('testtable'))
479
-        self.assertFalse(sw.drop_table('testtable'))
480
-        self.assertFalse(sw.drop_table('nonexisting'))
481
-        pass
482
-
483
-    def test_drop_table_badargs(self):
484
-        sw = SqlWrapper()
485
-
486
-        self.create_test_table(sw)
487
-
488
-        af = self.assertFalse
489
-
490
-        for bname in BADNAMES:
491
-            self.assertFalse(sw.drop_table(bname))
492
-
493
-    def test_create_get_drop_table(self):
494
-        sw = SqlWrapper()
495
-        
496
-        funkynames = ('standart', "wow-nice", "-really-", "test_test-test", "_test", "test_", "test42", "foobar_123", "foobar-123", "foofoo--babar")
497
-        types = ['INTEGER', 'VARCHAR(5)', 'VARCHAR(128)', 'TEXT', 'BOOLEAN']
498
-        params = dict()
499
-
500
-        cols = []
501
-        for i,name in enumerate(funkynames):
502
-            cols.append({'name':name, 'type':types[i%len(types)]})
503
-        params['columns'] = cols
504
-
505
-
506
-        for name in random.sample(funkynames, len(funkynames)):
507
-            params['name'] = name
508
-            self.assertTrue(sw.create_table(params))
509
-
510
-        for name in random.sample(funkynames, len(funkynames)):
511
-            rt = sw.get_table(name)
512
-            self.assertIsInstance(rt, sqlalchemy.Table)#do we get a table ?
513
-            self.assertEqual(rt.name, name)
514
-
515
-        for name in random.sample(funkynames, len(funkynames)):
516
-            self.assertTrue(sw.drop_table(name))
517
-        pass
518
-        
519
-

+ 0
- 175
Database/test/test_sqlwrapper_querystring.py View File

@@ -1,175 +0,0 @@
1
-import os
2
-import logging
3
-import random
4
-
5
-from unittest import TestCase
6
-import unittest
7
-
8
-from Database.sqlwrapper import SqlWrapper
9
-
10
-from django.conf import settings
11
-from Database.sqlsettings import SQLSettings
12
-
13
-os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Lodel.settings")
14
-
15
-#Bad strings for injection tests
16
-INJECTIONS = [ 'foo UNION SELECT 1,2,3,4--', "foo' UNION SELECT 1,2,3--", 'foo" UNION SELECT 1,2,3--', 'foo` UNION SELECT 1,2,3,4--', '--', 'foo`; SELECT 1,2,3', 'foo"; SELECT 1,2,3', "foo'; SELECT 1,2,3", "; SELECT 1,2,3" ]
17
-NAMEINJECT = INJECTIONS + [ '%', '*', "\0", "\b\b\b\b\b\b\b\b\b" ]
18
-
19
-#Valid SQL types
20
-VTYPES = [ 'integer', 'varchar(1)', 'varchar(50)', 'text', 'boolean' ]
21
-
22
-class SqlWrapperQueryStrTests(TestCase):
23
-    
24
-    def setUp(self):
25
-        #creating a test table
26
-        sw = SqlWrapper()
27
-        self.ttn = 'testtable'
28
-        self.cols = [
29
-            { 'name': 'pk', 'type': 'INTEGER', 'extra': {'primarykey': True} },
30
-            { 'name': 'testtxt', 'type': 'TEXT', 'extra': {'nullable': True, 'default': 'hello'} },
31
-            { 'name': 'testchr', 'type': 'VARCHAR(50)', 'extra': {'nullable': True, 'default': 'hello world'} },
32
-            { 'name': 'testbool', 'type': 'BOOLEAN', 'extra': {'nullable':False, 'default': False}},
33
-        ]
34
-        
35
-        sw.create_table( { 'name': self.ttn, 'columns': self.cols} )
36
-
37
-
38
-        #Disable logging but CRITICAL
39
-        logging.basicConfig(level=logging.CRITICAL)
40
-        pass
41
-
42
-    def tearDown(self):
43
-        sw = SqlWrapper()
44
-        sw.drop_table(self.ttn)
45
-
46
-    @unittest.skip('dev') #TODO remove skip
47
-    def test_get_querystring(self):
48
-        sw = SqlWrapper()
49
-
50
-        actions = [ 'add_column', 'alter_column', 'drop_column' ]
51
-        dialects = [ 'default', 'mysql', 'postgresql' ]
52
-
53
-        for action in actions:
54
-            for dialect in dialects:
55
-                r = sw.get_querystring(action, dialect)
56
-                self.assertIsInstance(r, str)
57
-
58
-    @unittest.skip('dev') #TODO remove skip
59
-    def test_get_querystring_badargs(self):
60
-        sw = SqlWrapper()
61
-        
62
-        actions = [ 1, -1, print, [], 'foo']
63
-        dialects = actions
64
-        for action in actions:
65
-            for dialect in dialects:
66
-                with self.assertRaises(ValueError):
67
-                    r = sw.get_querystring(action, dialect)
68
-    
69
-    @unittest.skip('dev') #TODO remove skip
70
-    def test_add_column(self):
71
-        sw = SqlWrapper()
72
-
73
-        colnames = [ 'taddcol1', 'test-add-col', 'test_add_col', '-test', '_add', '__col__' ]
74
-
75
-        for i, name in enumerate(colnames):
76
-            col = { 'name': name, 'type': VTYPES[i%len(VTYPES)] }
77
-            self.assertTrue(sw.add_column(self.ttn, col))
78
-        pass
79
-
80
-    @unittest.skip('dev') #TODO remove skip
81
-    def test_add_column_badargs(self):
82
-        sw = SqlWrapper()
83
-
84
-        coolname = 'cool'
85
-        i=0
86
-        self.assertFalse(sw.add_column(self.ttn, {'type': 'INTEGER'}))
87
-        self.assertFalse(sw.add_column(self.ttn, {'name': 'foo'}))
88
-        self.assertFalse(sw.add_column(self.ttn, dict()))
89
-        self.assertFalse(sw.add_column(self.ttn, print))
90
-        self.assertFalse(sw.add_column(self.ttn, ['foo', 'integer']))
91
-        self.assertFalse(sw.add_column(self.ttn, None))
92
-        self.assertFalse(sw.add_column(self.ttn, 42))
93
-        self.assertFalse(sw.add_column(1, {'name':'foo', 'type':'integer'}))
94
-        self.assertFalse(sw.add_column(print, {'name':'foo', 'type':'integer'}))
95
-        self.assertFalse(sw.add_column([], {'name':'foo', 'type':'integer'}))
96
-        self.assertFalse(sw.add_column(dict(), {'name':'foo', 'type':'integer'}))
97
-        for badname in NAMEINJECT:
98
-            self.assertFalse(sw.add_column(self.ttn, {'name':badname, 'type':'INTEGER'}))
99
-            self.assertFalse(sw.add_column(self.ttn, {'name':coolname+str(i), 'type':badname}))
100
-            self.assertFalse(sw.add_column(badname, {'name':coolname+str(i), 'type':'INTEGER'}))
101
-            i+=1
102
-        
103
-    @unittest.skip('dev') #TODO remove skip
104
-    def test_alter_column(self):
105
-        sw = SqlWrapper()
106
-
107
-        colnames = ['talter', 'talter1', 'test_alter', 'test-alter-col', '-test_alter', '__test_alter__']
108
-        for i,name in enumerate(random.sample(colnames, len(colnames))):
109
-            col = { 'name': name, 'type': VTYPES[i%len(VTYPES)] }
110
-            self.assertTrue(sw.add_column( self.ttn, col))
111
-
112
-        for i,name in enumerate(random.sample(colnames, len(colnames))):
113
-            col = {'name': name, 'type': VTYPES[i%len(VTYPES)]}
114
-            self.assertTrue(self.ttn, col)
115
-        pass
116
-
117
-    @unittest.skip('dev') #TODO remove skip
118
-    def test_alter_column_badargs(self):
119
-        sw = SqlWrapper()
120
-
121
-        colnames = ['tabad', 'tabad1']
122
-
123
-        for i,name in enumerate(colnames):
124
-            col = { 'name': name, 'type': VTYPES[i%len(VTYPES)] }
125
-            self.assertTrue(sw.add_column(self.ttn, col))
126
-
127
-        for i,badname in enumerate(NAMEINJECT):
128
-            col = { 'name': badname, 'type': VTYPES[i%len(VTYPES)] }
129
-            self.assertFalse(sw.alter_column(self.ttn, col))
130
-
131
-            col = { 'name': colnames[i%len(colnames)], 'type': badname}
132
-            self.assertFalse(sw.alter_column(self.ttn, col))
133
-
134
-            col = { 'name': badname, 'type': NAMEINJECT[random.randint(0,len(NAMEINJECT))]}
135
-            self.assertFalse(sw.alter_column(self.ttn, col))
136
-
137
-            col = { 'name': colnames[i%len(colnames)], 'type': VTYPES[i%len(VTYPES)] }
138
-            self.assertFalse(sw.alter_column(badname, col))
139
-
140
-    def test_insert(self):
141
-        sw = SqlWrapper()
142
-
143
-        records = [
144
-            {   'pk': 0,
145
-                'testchr': 'Hello world !',
146
-                'testtext': 'Wow ! Super text... I\'m amazed',
147
-                'testbool': False
148
-            },
149
-            {   'pk': 1,
150
-                'testchr': 'Hello"world...--',
151
-                'testtext': 'Another wonderfull text. But this time with spécials chars@;,:--*/+\'{}]{[|~&ù^$*µ$£ê;<ç>\/*-+',
152
-                'testbool': True
153
-            },
154
-            {   'pk': 2 }, #default values for others
155
-            {   'pk': '3',
156
-                'testchr': None,
157
-                'testtext': None,
158
-                'testbool': 'true'
159
-            },
160
-            {   'pk': 4,
161
-                'testchr': '',
162
-                'testtext': '',
163
-                'testbool': 'false'
164
-            },
165
-            {   'pk': 5,
166
-                'testbool': 0
167
-            },
168
-            {   'pk': 6,
169
-                'testbool': 1
170
-            },
171
-            {   'pk':1024,
172
-                'testbool': False
173
-            },
174
-        ]
175
-                

+ 0
- 3
Database/tests.py View File

@@ -1,3 +0,0 @@
1
-from django.test import TestCase
2
-
3
-# Create your tests here.

Loading…
Cancel
Save