瀏覽代碼

Merge branch 'master' of git.labocleo.org:lodel2

Driky 9 年之前
父節點
當前提交
c7b6615436

+ 90
- 0
Database/sqlalter.py 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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
+

+ 22
- 21
Database/sqlsetup.py 查看文件

@@ -1,11 +1,12 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 
3 3
 from Database.sqlwrapper import SqlWrapper
4
+import sqlalchemy as sql
4 5
 
5 6
 class SQLSetup(object): 
6 7
 
7
-    def initDb(self, dbconfname = 'default'):
8
-        db = SqlWrapper(read_db = dbconfname, write_db = dbconfname)
8
+    def initDb(self, dbconfname = 'default', alchemy_logs=None):
9
+        db = SqlWrapper(read_db = dbconfname, write_db = dbconfname, alchemy_logs=alchemy_logs)
9 10
         tables = self.get_schema()
10 11
         db.dropAll()
11 12
         db.createAllFromConf(tables)
@@ -14,13 +15,13 @@ class SQLSetup(object):
14 15
         tables = []
15 16
 
16 17
         default_columns = [
17
-            {"name":"uid",          "type":"VARCHAR(50)", "extra":{"foreignkey":"uids.uid", "nullable":False, "primarykey":True}},
18
+            {"name":"uid",          "type":"INTEGER", "extra":{"foreignkey":"uids.uid", "nullable":False, "primarykey":True}},
18 19
             {"name":"name",         "type":"VARCHAR(50)", "extra":{"nullable":False, "unique":True}},
19 20
             {"name":"string",       "type":"TEXT"},
20 21
             {"name":"help",         "type":"TEXT"},
21 22
             {"name":"rank",         "type":"INTEGER"},
22
-            {"name":"date_update",  "type":"DATE"},
23
-            {"name":"date_create",  "type":"DATE"}
23
+            {"name":"date_create",  "type":"DATETIME"},
24
+            {"name":"date_update",  "type":"DATETIME"},
24 25
         ]
25 26
 
26 27
         # Table listing all objects created by lodel, giving them an unique id
@@ -47,7 +48,7 @@ class SQLSetup(object):
47 48
         # Table listing the types
48 49
         em_type = {"name":"em_type"}
49 50
         em_type['columns'] = default_columns + [
50
-            {"name":"class_id",     "type":"VARCHAR(50)", "extra":{"foreignkey":"em_class.uid", "nullable":False}},
51
+            {"name":"class_id",     "type":"INTEGER", "extra":{"foreignkey":"em_class.uid", "nullable":False}},
51 52
             {"name":"sortcolumn",   "type":"VARCHAR(50)", "extra":{"default":"rank"}},
52 53
             {"name":"icon",         "type":"INTEGER"},
53 54
         ]
@@ -56,8 +57,8 @@ class SQLSetup(object):
56 57
         # relation between types: which type can be a child of another
57 58
         em_type_hierarchy = {"name":"em_type_hierarchy"}
58 59
         em_type_hierarchy['columns'] = [
59
-            {"name":"superior_id",    "type":"VARCHAR(50)", "extra":{"foreignkey":"em_type.uid", "nullable":False, "primarykey":True}},
60
-            {"name":"subordinate_id", "type":"VARCHAR(50)", "extra":{"foreignkey":"em_type.uid", "nullable":False, "primarykey":True}},
60
+            {"name":"superior_id",    "type":"INTEGER", "extra":{"foreignkey":"em_type.uid", "nullable":False, "primarykey":True}},
61
+            {"name":"subordinate_id", "type":"INTEGER", "extra":{"foreignkey":"em_type.uid", "nullable":False, "primarykey":True}},
61 62
             {"name":"nature",         "type":"VARCHAR(50)"},
62 63
         ]
63 64
         tables.append(em_type_hierarchy)
@@ -65,17 +66,17 @@ class SQLSetup(object):
65 66
        # Table listing the fieldgroups of a class
66 67
         em_fieldgroup = {"name":"em_fieldgroup"}
67 68
         em_fieldgroup['columns'] = default_columns + [
68
-            {"name":"class_id",     "type":"VARCHAR(50)", "extra":{"foreignkey":"em_class.uid", "nullable":False}},
69
+            {"name":"class_id",     "type":"INTEGER", "extra":{"foreignkey":"em_class.uid", "nullable":False}},
69 70
         ]
70 71
         tables.append(em_fieldgroup)
71 72
 
72 73
         # Table listing the fields of a fieldgroup
73 74
         em_field = {"name":"em_field"}
74 75
         em_field['columns'] = default_columns + [
75
-            {"name":"fieldtype_id",   "type":"VARCHAR(50)", "extra":{"nullable":False}},
76
-            {"name":"fieldgroup_id",  "type":"VARCHAR(50)", "extra":{"foreignkey":"em_fieldgroup.uid", "nullable":False}},
77
-            {"name":"rel_to_type_id", "type":"VARCHAR(50)", "extra":{"foreignkey":"em_type.uid", "nullable":False}}, # if relational: type this field refer to
78
-            {"name":"rel_field_id",   "type":"VARCHAR(50)", "extra":{"foreignkey":"em_type.uid", "nullable":False}}, # if relational: field that specify the rel_to_type_id
76
+            {"name":"fieldtype",   "type":"VARCHAR(50)", "extra":{"nullable":False}},
77
+            {"name":"fieldgroup_id",  "type":"INTEGER", "extra":{"foreignkey":"em_fieldgroup.uid", "nullable":False}},
78
+            {"name":"rel_to_type_id", "type":"INTEGER", "extra":{"foreignkey":"em_type.uid", "nullable":True, "server_default": sql.text('NULL')}}, # if relational: type this field refer to
79
+            {"name":"rel_field_id",   "type":"INTEGER", "extra":{"foreignkey":"em_type.uid", "nullable":True, "server_default": sql.text('NULL')}}, # if relational: field that specify the rel_to_type_id
79 80
             {"name":"optional",       "type":"BOOLEAN"},
80 81
             {"name":"internal",       "type":"BOOLEAN"},
81 82
             {"name":"icon",           "type":"INTEGER"},
@@ -85,8 +86,8 @@ class SQLSetup(object):
85 86
         # selected field for each type
86 87
         em_field_type = {"name":"em_field_type"}
87 88
         em_field_type['columns'] = [
88
-            {"name":"type_id",   "type":"VARCHAR(50)", "extra":{"foreignkey":"em_type.uid", "nullable":False, "primarykey":True}},
89
-            {"name":"field_id",  "type":"VARCHAR(50)", "extra":{"foreignkey":"em_field.uid", "nullable":False, "primarykey":True}},
89
+            {"name":"type_id",   "type":"INTEGER", "extra":{"foreignkey":"em_type.uid", "nullable":False, "primarykey":True}},
90
+            {"name":"field_id",  "type":"INTEGER", "extra":{"foreignkey":"em_field.uid", "nullable":False, "primarykey":True}},
90 91
         ]
91 92
         tables.append(em_field_type)
92 93
 
@@ -94,12 +95,12 @@ class SQLSetup(object):
94 95
         objects = {
95 96
             "name":"objects",
96 97
             "columns":[
97
-                {"name":"uid",         "type":"VARCHAR(50)", "extra":{"foreignkey":"uids.uid", "nullable":False, "primarykey":True}},
98
+                {"name":"uid",         "type":"INTEGER", "extra":{"foreignkey":"uids.uid", "nullable":False, "primarykey":True}},
98 99
                 {"name":"string",      "type":"VARCHAR(50)"},
99
-                {"name":"class_id",    "type":"VARCHAR(50)", "extra":{"foreignkey":"em_class.uid"}},
100
-                {"name":"type_id",     "type":"VARCHAR(50)", "extra":{"foreignkey":"em_type.uid"}},
101
-                {"name":"date_update", "type":"DATE"},
102
-                {"name":"date_create", "type":"DATE"},
100
+                {"name":"class_id",    "type":"INTEGER", "extra":{"foreignkey":"em_class.uid"}},
101
+                {"name":"type_id",     "type":"INTEGER", "extra":{"foreignkey":"em_type.uid"}},
102
+                {"name":"date_create", "type":"DATETIME"},
103
+                {"name":"date_update", "type":"DATETIME"},
103 104
                 {"name":"history",     "type":"TEXT"}
104 105
             ]
105 106
         }
@@ -110,7 +111,7 @@ class SQLSetup(object):
110 111
         files = {
111 112
             "name":"files",
112 113
             "columns":[
113
-                {"name":"uid",     "type":"VARCHAR(50)", "extra":{"foreignkey":"uids.uid", "nullable":False, "primarykey":True}},
114
+                {"name":"uid",     "type":"INTEGER", "extra":{"foreignkey":"uids.uid", "nullable":False, "primarykey":True}},
114 115
                 {"name":"field1",  "type":"VARCHAR(50)"}
115 116
             ]
116 117
         }

+ 73
- 0
Database/sqlutils.py 查看文件

@@ -0,0 +1,73 @@
1
+# -*- coding: utf-8 -*-
2
+import os
3
+import re
4
+import logging as logger
5
+
6
+import sqlalchemy as sqla
7
+from django.conf import settings
8
+
9
+
10
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Lodel.settings")
11
+
12
+ENGINES = {'mysql': {
13
+				'driver': 'pymysql',
14
+				'encoding': 'utf8'
15
+			},
16
+			'postgresql': {
17
+				'driver': 'psycopg2',
18
+				'encoding': 'utf8',
19
+			},
20
+			'sqlite': {
21
+				'driver': 'pysqlite',
22
+				'encoding': 'utf8',
23
+			},
24
+}
25
+
26
+sqlcfg = settings.LODEL2SQLWRAPPER
27
+
28
+def getEngine(ename = 'default', sqlalogging = None):
29
+	""" Return a sqlalchemy engine
30
+		@param read bool: If True return the read engine, else 
31
+		return the write one
32
+		@return a sqlachemy engine instance
33
+
34
+		@todo Put the check on db config in SqlWrapper.checkConf()
35
+	"""
36
+	#Loading confs
37
+	cfg = sqlcfg['db'][ename]
38
+
39
+	edata = ENGINES[cfg['ENGINE']] #engine infos
40
+	conn_str = ""
41
+
42
+	if cfg['ENGINE'] == 'sqlite':
43
+		#Sqlite connection string
44
+		conn_str = '%s+%s:///%s'%( cfg['ENGINE'],
45
+			edata['driver'],
46
+			cfg['NAME'])
47
+	else:
48
+		#Mysql and Postgres connection string
49
+		user = cfg['USER']
50
+		user += (':'+cfg['PASSWORD'] if 'PASSWORD' in cfg else '')
51
+		
52
+		if 'HOST' not in cfg:
53
+			logger.info('Not HOST in configuration, using localhost')
54
+			host = 'localhost'
55
+		else:
56
+			host = cfg['HOST']
57
+
58
+		host += (':'+cfg['PORT'] if 'PORT' in cfg else '')
59
+
60
+		conn_str = '%s+%s://'%(cfg['ENGINE'], edata['driver'])
61
+		conn_str += '%s@%s/%s'%(user,host,cfg['NAME'])
62
+
63
+
64
+	ret = sqla.create_engine(conn_str, encoding=edata['encoding'], echo=sqlalogging)
65
+
66
+	logger.debug("Getting engine :"+str(ret))
67
+
68
+	return ret
69
+
70
+def meta(engine):
71
+		res = sqla.MetaData()
72
+		res.reflect(bind=engine)
73
+		return res

+ 171
- 38
Database/sqlwrapper.py 查看文件

@@ -1,13 +1,19 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 import os
3 3
 import re
4
-import logging as logger
4
+import logging
5 5
 
6 6
 import sqlalchemy as sqla
7
+from sqlalchemy.ext.compiler import compiles
7 8
 from django.conf import settings
8 9
 
10
+from Database.sqlalter import *
11
+
9 12
 #Logger config
10
-logger.getLogger().setLevel('DEBUG')
13
+#logger.getLogger().setLevel('WARNING')
14
+logger = logging.getLogger('lodel2.Database.sqlwrapper')
15
+logger.setLevel('WARNING')
16
+logger.propagate = False
11 17
 #To be able to use dango confs
12 18
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Lodel.settings")
13 19
 
@@ -38,26 +44,32 @@ class SqlWrapper(object):
38 44
     ##Wrapper instance list
39 45
     wrapinstance = dict()
40 46
 
41
-    def __init__(self, name="default", alchemy_logs=None, read_db = "default", write_db = "default"):
47
+    def __init__(self, name=None, alchemy_logs=None, read_db = "default", write_db = "default"):
42 48
         """ Instanciate a new SqlWrapper
43 49
             @param name str: The wrapper name
44 50
             @param alchemy_logs bool: If true activate sqlalchemy logger
45 51
             @param read_db str: The name of the db conf
46 52
             @param write_db str: The name of the db conf
53
+
54
+            @todo Better use of name (should use self.cfg['wrapper'][name] to get engines configs
55
+            @todo Is it a really good idea to store instance in class scope ? Maybe not !!
47 56
         """
48 57
         
49 58
         self.sqlalogging = False if alchemy_logs == None else bool(alchemy_logs)
50 59
 
51
-        self.name = name
60
+        if name == None:
61
+            self.name = read_db+'+'+write_db
62
+        else:
63
+            self.name = name
52 64
     
53 65
         self.r_dbconf = read_db
54 66
         self.w_dbconf = write_db
55 67
 
56
-        self.checkConf() #raise if errors in configuration
68
+        self._checkConf() #raise if errors in configuration
57 69
 
58
-        if name in self.__class__.wrapinstance:
59
-            logger.warning("A SqlWrapper with the name "+name+" allready exist. Replacing the old one by the new one")
60
-        SqlWrapper.wrapinstance[name] = self
70
+        if self.name in self.__class__.wrapinstance:
71
+            logger.warning("A SqlWrapper with the name "+self.name+" allready exist. Replacing the old one by the new one")
72
+        SqlWrapper.wrapinstance[self.name] = self
61 73
 
62 74
         #Engine and wrapper initialisation
63 75
         self.r_engine = self._getEngine(True, self.sqlalogging)
@@ -66,33 +78,53 @@ class SqlWrapper(object):
66 78
         self.w_conn = None
67 79
 
68 80
 
69
-        self.meta = None #TODO : use it to load all db schema in 1 request and don't load it each table instanciation
81
+        self.metadata = None #TODO : use it to load all db schema in 1 request and don't load it each table instanciation
70 82
         self.meta_crea = None
83
+
84
+        logger.debug("New wrapper instance : <"+self.name+" read:"+str(self.r_engine)+" write:"+str(self.w_engine))
71 85
         pass
72 86
 
87
+    @classmethod
88
+    def getEngine(c, ename = 'default', logs = None):
89
+        return c(read_db = ename, write_db = ename, alchemy_logs = logs).r_engine
90
+
91
+    @property
92
+    def cfg(self):
93
+        """ Return the SqlWrapper.config dict """
94
+        return self.__class__.config;
73 95
     @property
74
-    def cfg(self): return self.__class__.config;
96
+    def _engines_cfg(self):
97
+        return self.__class__.ENGINES;
98
+
75 99
     @property
76
-    def engines_cfg(self): return self.__class__.ENGINES;
100
+    def meta(self):
101
+        if self.metadata == None:
102
+            self.renewMetaData()
103
+        return self.metadata
104
+
105
+    def renewMetaData(self):
106
+        """ (Re)load the database schema """
107
+        self.metadata = sqla.MetaData(bind=self.r_engine)
108
+        self.metadata.reflect()
77 109
 
78 110
     @property
79 111
     def rconn(self):
80 112
         """ Return the read connection
81 113
             @warning Do not store the connection, call this method each time you need it
82 114
         """
83
-        return self.getConnection(True)
115
+        return self._getConnection(True)
84 116
     @property
85 117
     def wconn(self):
86 118
         """ Return the write connection
87 119
             @warning Do not store the connection, call this method each time you need it
88 120
         """
89
-        return self.getConnection(False)
121
+        return self._getConnection(False)
90 122
 
91
-
92
-    def getConnection(self, read):
123
+    def _getConnection(self, read):
93 124
         """ Return an opened connection
94 125
             @param read bool: If true return the reading connection
95 126
             @return A sqlAlchemy db connection
127
+            @private
96 128
         """
97 129
         if read:
98 130
             r = self.r_conn
@@ -102,7 +134,7 @@ class SqlWrapper(object):
102 134
         if r == None:
103 135
             #Connection not yet opened
104 136
             self.connect(read)
105
-            r = self.getConnection(read) #TODO : Un truc plus safe/propre qu'un appel reccursif ?
137
+            r = self._getConnection(read) #TODO : Un truc plus safe/propre qu'un appel reccursif ?
106 138
         return r
107 139
 
108 140
 
@@ -129,11 +161,17 @@ class SqlWrapper(object):
129 161
             @return None
130 162
         """
131 163
         if read or read == None:
132
-            self.r_conn.close()
164
+            if self.r_conn == None:
165
+                logger.info('Unable to close read connection : connection not opened')
166
+            else:
167
+                self.r_conn.close()
133 168
             self.r_conn = None
134 169
 
135 170
         if not read or read == None:
136
-            self.w_conn.close()
171
+            if self.r_conn == None:
172
+                logger.info('Unable to close write connection : connection not opened')
173
+            else:
174
+                self.w_conn.close()
137 175
             self.w_conn = None
138 176
 
139 177
     def reconnect(self, read = None):
@@ -146,6 +184,9 @@ class SqlWrapper(object):
146 184
 
147 185
     @classmethod
148 186
     def reconnectAll(c, read = None):
187
+        """ Reconnect all the wrappers 
188
+            @static
189
+        """
149 190
         for wname in c.wrapinstance:
150 191
             c.wrapinstance[wname].reconnect(read)
151 192
             
@@ -156,7 +197,8 @@ class SqlWrapper(object):
156 197
         """
157 198
         if not isinstance(tname, str):
158 199
             return TypeError('Excepting a <class str> but got a '+str(type(tname)))
159
-        return sqla.Table(tname, sqla.MetaData(), autoload_with=self.r_engine, autoload=True)
200
+        #return sqla.Table(tname, self.meta, autoload_with=self.r_engine, autoload=True)
201
+        return sqla.Table(tname, self.meta)
160 202
 
161 203
     def _getEngine(self, read=True, sqlalogging = None):
162 204
         """ Return a sqlalchemy engine
@@ -169,7 +211,7 @@ class SqlWrapper(object):
169 211
         #Loading confs
170 212
         cfg = self.cfg['db'][self.r_dbconf if read else self.w_dbconf]
171 213
 
172
-        edata = self.engines_cfg[cfg['ENGINE']] #engine infos
214
+        edata = self._engines_cfg[cfg['ENGINE']] #engine infos
173 215
         conn_str = ""
174 216
 
175 217
         if cfg['ENGINE'] == 'sqlite':
@@ -194,7 +236,11 @@ class SqlWrapper(object):
194 236
             conn_str += '%s@%s/%s'%(user,host,cfg['NAME'])
195 237
 
196 238
 
197
-        return sqla.create_engine(conn_str, encoding=edata['encoding'], echo=self.sqlalogging)
239
+        ret = sqla.create_engine(conn_str, encoding=edata['encoding'], echo=self.sqlalogging)
240
+
241
+        logger.debug("Getting engine :"+str(ret))
242
+
243
+        return ret
198 244
 
199 245
     @classmethod
200 246
     def getWrapper(c, name):
@@ -208,7 +254,7 @@ class SqlWrapper(object):
208 254
             raise KeyError("No wrapper named '"+name+"' exists")
209 255
         return c.wrapinstance[name]
210 256
 
211
-    def checkConf(self):
257
+    def _checkConf(self):
212 258
         """ Class method that check the configuration
213 259
             
214 260
             Configuration looks like
@@ -232,13 +278,10 @@ class SqlWrapper(object):
232 278
                     err.append('Missing "'+dbname+'" db configuration')
233 279
                 else:
234 280
                     db = self.cfg['db'][dbname]
235
-                    if 'ENGINE' not in db:
236
-                        err.append('Missing "ENGINE" in database "'+db+'"')
237
-                    else:
238
-                        if db['ENGINE'] not in self.engines_cfg:
239
-                            err.append('Unknown engine "'+db['ENGINE']+'"')
240
-                        elif db['ENGINE'] != 'sqlite' and 'USER' not in db:
241
-                            err.append('Missing "User" in configuration of database "'+dbname+'"')
281
+                    if db['ENGINE'] not in self._engines_cfg:
282
+                        err.append('Unknown engine "'+db['ENGINE']+'"')
283
+                    elif db['ENGINE'] != 'sqlite' and 'USER' not in db:
284
+                        err.append('Missing "User" in configuration of database "'+dbname+'"')
242 285
                     if 'NAME' not in db:
243 286
                         err.append('Missing "NAME" in database "'+dbname+'"')
244 287
                         
@@ -253,7 +296,8 @@ class SqlWrapper(object):
253 296
         if not settings.DEBUG:
254 297
             logger.critical("Trying to drop all tables but we are not in DEBUG !!!")
255 298
             raise RuntimeError("Trying to drop all tables but we are not in DEBUG !!!")
256
-        meta = sqla.MetaData(bind=self.w_engine, reflect = True)
299
+        meta = sqla.MetaData(bind=self.w_engine)
300
+        meta.reflect()
257 301
         meta.drop_all()
258 302
         pass
259 303
 
@@ -264,11 +308,16 @@ class SqlWrapper(object):
264 308
         """
265 309
         self.meta_crea = sqla.MetaData()
266 310
 
311
+        logger.info("Running function createAllFromConf")
267 312
         for i,table in enumerate(schema):
313
+            if not isinstance(table, dict):
314
+                raise TypeError("Excepted a list of dict but got a "+str(type(schema))+" in the list")
268 315
             self.createTable(**table)
269
-
270
-        self.meta_crea.create_all(self.w_engine)
316
+        
317
+        self.meta_crea.create_all(bind = self.w_engine)
318
+        logger.info("All tables created")
271 319
         self.meta_crea = None
320
+        self.renewMetaData()
272 321
         pass
273 322
             
274 323
     def createTable(self, name, columns, **kw):
@@ -288,12 +337,16 @@ class SqlWrapper(object):
288 337
         if not isinstance(name, str):
289 338
             raise TypeError("<class str> excepted for table name, but got "+type(name))
290 339
 
340
+        #if not 'mysql_engine' in kw and self.w_engine.dialect.name == 'mysql':
341
+        #    kw['mysql_engine'] = 'InnoDB'
342
+
291 343
         res = sqla.Table(name, self.meta_crea, **kw)
292 344
         for i,col in enumerate(columns):
293 345
             res.append_column(self.createColumn(**col))
294 346
 
295 347
         if crea_now:
296 348
             self.meta_crea.create_all(self.w_engine)
349
+            logger.debug("Table '"+name+"' created")
297 350
 
298 351
         pass
299 352
 
@@ -306,12 +359,13 @@ class SqlWrapper(object):
306 359
                 - extra : a dict like { "primarykey":True, "nullable":False, "default":"test"...}
307 360
             @param **kwargs 
308 361
         """
309
-        if not 'name' in kwargs or not 'type' in kwargs:
362
+        if not 'name' in kwargs or ('type' not in kwargs and 'type_' not in kwargs):
310 363
             pass#ERROR
311 364
 
312 365
         #Converting parameters
313
-        kwargs['type_'] = self._strToSqlAType(kwargs['type'])
314
-        del kwargs['type']
366
+        if 'type_' not in kwargs and 'type' in kwargs:
367
+            kwargs['type_'] = self._strToSqlAType(kwargs['type'])
368
+            del kwargs['type']
315 369
 
316 370
         if 'extra' in kwargs:
317 371
             #put the extra keys in kwargs
@@ -331,13 +385,12 @@ class SqlWrapper(object):
331 385
             kwargs['primary_key'] = kwargs['primarykey']
332 386
             del kwargs['primarykey']
333 387
 
334
-
335
-        logger.debug('Column creation arguments : '+str(kwargs))
336 388
         res = sqla.Column(**kwargs)
337 389
 
338 390
         if fk != None:
339 391
             res.append_foreign_key(fk)
340 392
 
393
+        #logger.debug("Column '"+kwargs['name']+"' created")
341 394
         return res
342 395
     
343 396
     def _strToSqlAType(self, strtype):
@@ -357,6 +410,86 @@ class SqlWrapper(object):
357 410
         check_length = re.search(re.compile('VARCHAR\(([\d]+)\)', re.IGNORECASE), vstr)
358 411
         column_length = int(check_length.groups()[0]) if check_length else None
359 412
         return sqla.VARCHAR(length=column_length)
360
-        
413
+     
361 414
 
415
+    def dropColumn(self, tname, colname):
416
+        """ Drop a column from a table
417
+            @param tname str|sqlalchemy.Table: The table name or a Table object
418
+            @param colname str|sqlalchemy.Column: The column name or a column object
419
+            @return None
420
+        """
421
+        if tname not in self.meta.tables: #Useless ?
422
+            raise NameError("The table '"+tname+"' dont exist")
423
+        table = self.Table(tname)
424
+        col = sqla.Column(colname)
425
+
426
+        ddl = DropColumn(table, col)
427
+        sql = ddl.compile(dialect=self.w_engine.dialect)
428
+        sql = str(sql)
429
+        logger.debug("Executing SQL  : '"+sql+"'")
430
+        ret = bool(self.w_engine.execute(sql))
431
+
432
+        self.renewMetaData()
433
+        return ret
434
+
435
+    
436
+    def addColumn(self, tname, colname, coltype):
437
+        """ Add a column to a table
438
+            @param tname str: The table name
439
+            @param colname str: The column name
440
+            @param coltype str: The new column type
441
+
442
+            @return True if query success False if it fails
443
+        """
444
+        if tname not in self.meta.tables: #Useless ?
445
+            raise NameError("The table '"+tname+"' dont exist")
446
+        table = self.Table(tname)
447
+        newcol = self.createColumn(name=colname, type_ = coltype)
448
+
449
+        ddl = AddColumn(table, newcol)
450
+        sql = ddl.compile(dialect=self.w_engine.dialect)
451
+        sql = str(sql)
452
+        logger.debug("Executing SQL  : '"+sql+"'")
453
+        ret = bool(self.wconn.execute(sql))
454
+
455
+        self.renewMetaData()
456
+        return ret
457
+
458
+    def alterColumn(self, tname, colname, col_newtype):
459
+        """ Change the type of a column
460
+            @param tname str: The table name
461
+            @param colname str: The column name
462
+            @param col_newtype str: The column new type
463
+
464
+            @return True if query successs False if it fails
465
+        """
466
+        
467
+        if tname not in self.meta.tables: #Useless ?
468
+            raise NameError("The table '"+tname+"' dont exist")
469
+
470
+        col = self.createColumn(name=colname, type_=col_newtype)
471
+        table = self.Table(tname)
472
+
473
+        ddl = AlterColumn(table, newcol)
474
+        sql = ddl.compile(dialect=self.w_engine.dialect)
475
+        sql = str(sql)
476
+        logger.debug("Executing SQL  : '"+sql+"'")
477
+        ret = bool(self.wconn.execute(sql))
478
+
479
+        self.renewMetaData()
480
+        return ret
481
+
482
+    def _debug__printSchema(self):
483
+        """ Debug function to print the db schema """
484
+        print(self.meta)
485
+        for tname in self.meta.tables:
486
+            self._debug__printTable(tname)
487
+
488
+    def _debug__printTable(self, tname):
489
+        t = self.meta.tables[tname]
490
+        tstr = 'Table : "'+tname+'" :\n'
491
+        for c in t.c:
492
+            tstr += '\t\t"'+c.name+'"('+str(c.type)+') \n'
493
+        print(tstr)
494
+            
362 495
 

+ 0
- 0
Database/test/__init__.py 查看文件


+ 0
- 519
Database/test/test_sqlwrapper.py 查看文件

@@ -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 查看文件

@@ -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 查看文件

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

+ 56
- 25
EditorialModel/classes.py 查看文件

@@ -5,11 +5,16 @@
5 5
     @see EmClass, EmType, EmFieldGroup, EmField
6 6
 """
7 7
 
8
+import logging as logger
9
+
8 10
 from EditorialModel.components import EmComponent, EmComponentNotExistError
9
-from Database.sqlwrapper import SqlWrapper
10
-from Database.sqlobject import SqlObject
11
+from Database import sqlutils
12
+import sqlalchemy as sql
13
+
11 14
 import EditorialModel
12 15
 
16
+
17
+
13 18
 class EmClass(EmComponent):
14 19
     table = 'em_class'
15 20
 
@@ -21,21 +26,39 @@ class EmClass(EmComponent):
21 26
         @param name str: name of the new class
22 27
         @param class_type EmClasstype: type of the class
23 28
     """
24
-    @staticmethod
25
-    def create(name, class_type):
29
+    @classmethod
30
+    def create(c, name, class_type):
26 31
         try:
27
-            exists = EmClass(name)
32
+            res = EmClass(name)
33
+            logger.info("Trying to create an EmClass that allready exists")
28 34
         except EmComponentNotExistError:
29
-            uids = SqlObject('uids')
30
-            res = uids.wexec(uids.table.insert().values(table=EmClass.table))
31
-            uid = res.inserted_primary_key
35
+            res = c._createDb(name, class_type)
36
+            logger.debug("EmClass successfully created")
37
+
38
+        return res
39
+
40
+    @classmethod
41
+    def _createDb(c, name, class_type):
42
+        """ Do the db querys for EmClass::create() """
43
+
44
+        #Create a new entry in the em_class table
45
+        values = { 'name':name, 'classtype':class_type['name'] }
46
+        resclass = super(EmClass,c).create(values)
47
+
32 48
 
33
-            emclass = SqlObject(EmClass.table)
34
-            res = emclass.wexec(emclass.table.insert().values(uid=uid, name=name, classtype=class_type['name']))
35
-            SqlWrapper.wc().execute("CREATE TABLE %s (uid VARCHAR(50))" % name)
36
-            return EmClass(name)
49
+        dbe = c.getDbE()
50
+        conn = dbe.connect()
51
+
52
+        #Create a new table storing LodelObjects of this EmClass
53
+        meta = sql.MetaData()
54
+        emclasstable = sql.Table(name, meta,
55
+            sql.Column('uid', sql.VARCHAR(50), primary_key = True))
56
+        emclasstable.create(conn)
57
+
58
+        conn.close()
59
+
60
+        return resclass
37 61
 
38
-        return exists
39 62
 
40 63
     def populate(self):
41 64
         row = super(EmClass, self).populate()
@@ -60,16 +83,21 @@ class EmClass(EmComponent):
60 83
         @return field_groups [EmFieldGroup]:
61 84
     """
62 85
     def fieldgroups(self):
63
-        fieldgroups_req = SqlObject(EditorialModel.fieldgroups.EmFieldGroup.table)
64
-        select = fieldgroups_req.sel
65
-        select.where(fieldgroups_req.col.class_id == self.id)
66
-
67
-        sqlresult = fieldgroups_req.rexec(select)
68
-        records = sqlresult.fetchall()
86
+        records = self._fieldgroupsDb()
69 87
         fieldgroups = [ EditorialModel.fieldgroups.EmFieldGroup(int(record.uid)) for record in records ]
70 88
 
71 89
         return fieldgroups
72 90
 
91
+    def _fieldgroupsDb(self):
92
+        dbe = self.__class__.getDbE()
93
+        emfg = sql.Table(EditorialModel.fieldgroups.EmFieldGroup.table, sqlutils.meta(dbe))
94
+        req = emfg.select().where(emfg.c.class_id == self.id)
95
+        
96
+        conn = dbe.connect()
97
+        res = conn.execute(req)
98
+        return res.fetchall()
99
+
100
+
73 101
     """ retrieve list of fields
74 102
         @return fields [EmField]:
75 103
     """
@@ -80,16 +108,19 @@ class EmClass(EmComponent):
80 108
         @return types [EmType]:
81 109
     """
82 110
     def types(self):
83
-        emtype = SqlObject(EditorialModel.types.EmType.table)
84
-        select = emtype.sel
85
-        select.where(emtype.col.class_id == self.id)
86
-
87
-        sqlresult = emtype.rexec(select)
88
-        records = sqlresult.fetchall()
111
+        records = self._typesDb()
89 112
         types = [ EditorialModel.types.EmType(int(record.uid)) for record in records ]
90 113
 
91 114
         return types
92 115
 
116
+    def _typesDb(self):
117
+        dbe = self.__class__.getDbE()
118
+        emtype = sql.Table(EditorialModel.types.EmType.table, sqlutils.meta(dbe))
119
+        req = emtype.select().where(emtype.c.class_id == self.id)
120
+        conn = dbe.connect()
121
+        res = conn.execute(req)
122
+        return res.fetchall()
123
+    
93 124
     """ add a new EmType that can ben linked to this class
94 125
         @param  t EmType: type to link
95 126
         @return success bool: done or not

+ 105
- 24
EditorialModel/components.py 查看文件

@@ -5,23 +5,26 @@
5 5
     @see EmClass, EmType, EmFieldGroup, EmField
6 6
 """
7 7
 
8
+import datetime
9
+
8 10
 from Lodel.utils.mlstring import MlString
9
-from Database.sqlwrapper import SqlWrapper
10
-from Database.sqlobject import SqlObject
11 11
 import logging
12 12
 import sqlalchemy as sql
13
+from Database import sqlutils
13 14
 
14 15
 logger = logging.getLogger('Lodel2.EditorialModel')
15 16
 
16 17
 class EmComponent(object):
17 18
 
19
+    dbconf = 'default' #the name of the engine configuration
20
+    table = None
21
+    
18 22
     """ instaciate an EmComponent
19 23
         @param id_or_name int|str: name or id of the object
20 24
         @exception TypeError
21 25
     """
22 26
     def __init__(self, id_or_name):
23
-        SqlWrapper.start()
24
-        if self is EmComponent:
27
+        if type(self) is EmComponent:
25 28
             raise EnvironmentError('Abstract class')
26 29
         if isinstance(id_or_name, int):
27 30
             self.id = id_or_name
@@ -31,25 +34,12 @@ class EmComponent(object):
31 34
             self.name = id_or_name
32 35
             self.populate()
33 36
         else:
34
-            raise TypeError('Bad argument: expecting <int> or <str>')
37
+            raise TypeError('Bad argument: expecting <int> or <str> but got : '+str(type(id_or_name)))
35 38
 
36 39
     """ Lookup in the database properties of the object to populate the properties
37 40
     """
38 41
     def populate(self):
39
-        table = SqlObject(self.table)
40
-        select = table.sel
41
-
42
-        if self.id is None:
43
-            select.where(table.col.name == self.name)
44
-        else:
45
-            select.where(table.col.uid == self.id)
46
-
47
-        sqlresult = table.rexec(select)
48
-        records = sqlresult.fetchall()
49
-
50
-        if not records:
51
-            # could have two possible Error message for id and for name
52
-            raise EmComponentNotExistError("Bad id_or_name: could not find the component")
42
+        records = self._populateDb() #Db query
53 43
 
54 44
         for record in records:
55 45
             row = type('row', (object,), {})()
@@ -66,25 +56,97 @@ class EmComponent(object):
66 56
 
67 57
         return row
68 58
 
59
+    @classmethod
60
+    def getDbE(c):
61
+        """ Shortcut that return the sqlAlchemy engine """
62
+        return sqlutils.getEngine(c.dbconf)
63
+
64
+    def _populateDb(self):
65
+        """ Do the query on the db """
66
+        dbe = self.__class__.getDbE()
67
+        component = sql.Table(self.table, sqlutils.meta(dbe))
68
+        req = sql.sql.select([component])
69
+
70
+        if self.id == None:
71
+            req = req.where(component.c.name == self.name)
72
+        else:
73
+            req = req.where(component.c.uid == self.id)
74
+        c = dbe.connect()
75
+        res = c.execute(req)
76
+        c.close()
77
+
78
+        res = res.fetchall()
79
+
80
+        if not res or len(res) == 0:
81
+            raise EmComponentNotExistError("No component found with "+('name '+self.name if self.id == None else 'id '+self.id ))
82
+
83
+        return res
84
+    
85
+    ## Insert a new component in the database
86
+    # This function create and assign a new UID and handle the date_create value
87
+    # @param values The values of the new component
88
+    # @return An instance of the created component
89
+    #
90
+    # @todo Check that the query didn't failed
91
+    @classmethod
92
+    def create(c, values):
93
+        values['uid'] = c.newUid()
94
+        values['date_update'] = values['date_create'] = datetime.datetime.utcnow()
95
+
96
+        dbe = c.getDbE()
97
+        conn = dbe.connect()
98
+        table = sql.Table(c.table, sqlutils.meta(dbe))
99
+        req = table.insert(values)
100
+        res = conn.execute(req) #Check res?
101
+        conn.close()
102
+        return c(values['name']) #Maybe no need to check res because this would fail if the query failed
103
+        
104
+
69 105
     """ write the representation of the component in the database
70 106
         @return bool
71 107
     """
72 108
     def save(self, values):
109
+
73 110
         values['name'] = self.name
74 111
         values['rank'] = self.rank
75
-        values['date_update'] = self.date_update
76
-        values['date_create'] = self.date_create
112
+        values['date_update'] = datetime.datetime.utcnow()
77 113
         values['string'] = str(self.string)
78 114
         values['help']= str(self.help)
79 115
 
80
-        emclass = SqlObject(self.table)
81
-        update = emclass.table.update(values=values)
82
-        res = emclass.wexec(update)
116
+        #Don't allow creation date overwritting
117
+        if 'date_create' in values:
118
+            del values['date_create']
119
+            logger.warning("date_create supplied for save, but overwritting of date_create not allowed, the date will not be changed")
120
+
121
+        self._saveDb(values)
122
+
123
+    def _saveDb(self, values):
124
+        """ Do the query on the db """
125
+        dbe = self.__class__.getDbE()
126
+        component = sql.Table(self.table, sqlutils.meta(dbe))
127
+        req = sql.update(component, values = values).where(component.c.uid == self.id)
128
+
129
+        c = dbe.connect()
130
+        res = c.execute(req)
131
+        c.close()
132
+        if not res:
133
+            raise RuntimeError("Unable to save the component in the database")
134
+        
83 135
 
84 136
     """ delete this component data in the database
85 137
         @return bool
86 138
     """
87 139
     def delete(self):
140
+        #<SQL>
141
+        dbe = self.__class__.getDbE()
142
+        component = sql.Table(self.table, sqlutils.meta(dbe))
143
+        req= component.delete().where(component.c.uid == self.id)
144
+        c = dbe.connect()
145
+        res = c.execute(req)
146
+        c.close
147
+        if not res:
148
+            raise RuntimeError("Unable to delete the component in the database")
149
+        #</SQL>
88 150
         pass
89 151
 
90 152
     """ change the rank of the component
@@ -99,6 +161,25 @@ class EmComponent(object):
99 161
         else:
100 162
             return "<%s #%s, '%s'>" % (type(self).__name__, self.id, self.name)
101 163
 
164
+    @classmethod
165
+    def newUid(c):
166
+        """ This function register a new component in uids table
167
+            @return The new uid
168
+        """
169
+        dbe = c.getDbE()
170
+
171
+        uidtable = sql.Table('uids', sqlutils.meta(dbe))
172
+        conn = dbe.connect()
173
+        req = uidtable.insert(values={'table':c.table})
174
+        res = conn.execute(req)
175
+
176
+        uid = res.inserted_primary_key[0]
177
+        logger.debug("Registering a new UID '"+str(uid)+"' for '"+c.table+"' component")
178
+
179
+        conn.close()
180
+
181
+        return uid
182
+
102 183
 
103 184
 class EmComponentNotExistError(Exception):
104 185
     pass

+ 5
- 10
EditorialModel/fieldgroups.py 查看文件

@@ -1,7 +1,8 @@
1 1
 #-*- coding: utf-8 -*-
2 2
 
3 3
 from EditorialModel.components import EmComponent, EmComponentNotExistError
4
-from Database.sqlobject import SqlObject
4
+from Database import sqlutils
5
+import sqlalchemy as sql
5 6
 
6 7
 import EditorialModel
7 8
 
@@ -23,8 +24,8 @@ class EmFieldGroup(EmComponent):
23 24
         self.table = EmFieldGroup.table
24 25
         super(EmFieldGroup, self).__init__(id_or_name)
25 26
 
26
-    @staticmethod
27
-    def create(name, em_class):
27
+    @classmethod
28
+    def create(c, name, em_class):
28 29
         """ Create a new EmFieldGroup, save it and instanciate it
29 30
 
30 31
             @param name str: The name of the new fielgroup
@@ -33,13 +34,7 @@ class EmFieldGroup(EmComponent):
33 34
         try:
34 35
             exists = EmFieldGroup(name)
35 36
         except EmComponentNotExistError:
36
-            uids = SqlObject('uids')
37
-            res = uids.wexec(uids.table.insert().values(table=EmFieldGroup.table))
38
-            uid = res.inserted_primary_key
39
-
40
-            emfieldgroup = SqlObject(EmFieldGroup.table)
41
-            res = emfieldgroup.wexec(emfieldgroup.table.insert().values(uid=uid, name=name, class_id=em_class.id))
42
-            return EmFieldGroup(name)
37
+            return super(EmFieldGroup, c).create({'name': name, 'class_id':em_class.id}) #Check the return value ?
43 38
 
44 39
         return exists
45 40
 

+ 15
- 19
EditorialModel/fields.py 查看文件

@@ -1,7 +1,9 @@
1 1
 #-*- coding: utf-8 -*-
2 2
 
3 3
 from EditorialModel.components import EmComponent, EmComponentNotExistError
4
-from Database.sqlobject import SqlObject
4
+from Database import sqlutils
5
+
6
+import sqlalchemy as sql
5 7
 
6 8
 import EditorialModel
7 9
 
@@ -19,8 +21,8 @@ class EmField(EmComponent):
19 21
         self.table = EmField.table
20 22
         super(EmField, self).__init__(id_or_name)
21 23
 
22
-    @staticmethod
23
-    def create(name, em_fieldgroup, em_fieldtype, optional=True, internal=False):
24
+    @classmethod
25
+    def create(c, name, em_fieldgroup, em_fieldtype, optional=True, internal=False):
24 26
         """ Create a new EmField and instanciate it
25 27
             @static
26 28
 
@@ -40,36 +42,30 @@ class EmField(EmComponent):
40 42
         try:
41 43
             exists = EmField(name)
42 44
         except EmComponentNotExistError:
43
-            uids = SqlObject('uids')
44
-            res = uids.wexec(uids.table.insert().values(table=EmField.table))
45
-            uid = res.inserted_primary_key
46
-
47 45
             values = {
48
-                'uid' : uid,
46
+                'uid' : None,
49 47
                 'name' : name,
50 48
                 'fieldgroup_id' : em_fieldgroup.id,
51
-                'fieldtype_id' : em_fieldtype.id,
49
+                'fieldtype' : em_fieldtype.name,
52 50
                 'optional' : 1 if optional else 0,
53 51
                 'internal' : 1 if internal else 0,
54 52
             }
55
-
56
-            emfield_req = SqlObject(EmField.table)
57
-            res = emfield_req.wexec(emfield_req.table.insert(values=values))
58
-            return EmField(name)
53
+            return super(EmField,c).create(values)
59 54
 
60 55
         return exists
61 56
 
57
+
62 58
     """ Use dictionary (from database) to populate the object
63 59
     """
64 60
     def populate(self):
65 61
         row = super(EmField, self).populate()
66 62
         self.em_fieldgroup = EditorialModel.fieldgroups.EmFieldGroup(int(row.fieldgroup_id))
67
-        self.em_fieldtype = EditorialModel.fieldtypes.EmFieldType(int(row.fieldtype_id))
63
+        self.em_fieldtype = EditorialModel.fieldtypes.get_field_type(row.fieldtype)
68 64
         self.optional = True if row.optional == 1 else False;
69 65
         self.internal = True if row.internal == 1 else False;
70 66
         self.icon = row.icon
71
-        self.rel_to_type_id = EditorialModel.fieldtypes.EmFieldType(int(row.rel_to_type_id)) if row.rel_to_type_id else ''
72
-        self.rel_field_id = EmField(int(row.rel_field_id)) if row.rel_field_id else ''
67
+        self.rel_to_type_id = EditorialModel.fieldtypes.EmFieldType(int(row.rel_to_type_id)) if row.rel_to_type_id else None
68
+        self.rel_field_id = EmField(int(row.rel_field_id)) if row.rel_field_id else None
73 69
 
74 70
     def save(self):
75 71
         # should not be here, but cannot see how to do this
@@ -78,12 +74,12 @@ class EmField(EmComponent):
78 74
 
79 75
         values = {
80 76
             'fieldgroup_id' : self.em_fieldgroup.id,
81
-            'fieldtype_id' : self.em_fieldtype.id,
77
+            'fieldtype' : self.em_fieldtype.name,
82 78
             'optional' : 1 if self.optional else 0,
83 79
             'internal' : 1 if self.internal else 0,
84 80
             'icon' : self.icon,
85
-            'rel_to_type_id' : self.rel_to_type_id,
86
-            'rel_field_id' : self.rel_field_id
81
+            'rel_to_type_id' : self.rel_to_type_id.id if self.rel_to_type_id is not None else None,
82
+            'rel_field_id' : self.rel_field_id.id if self.rel_field_id is not None else None
87 83
         }
88 84
 
89 85
         return super(EmField, self).save(values)

+ 101
- 2
EditorialModel/fieldtypes.py 查看文件

@@ -1,8 +1,107 @@
1 1
 #-*- coding: utf-8 -*-
2 2
 
3 3
 from EditorialModel.components import EmComponent, EmComponentNotExistError
4
+from Lodel.utils.mlstring import MlString
5
+from sqlalchemy import Column, INTEGER, BOOLEAN, VARCHAR
6
+
7
+def get_field_type(name):
8
+    class_name = 'EmField_' + name
9
+    constructor = globals()[class_name]
10
+    instance = constructor()
11
+    return instance
4 12
 
5 13
 class EmFieldType():
6 14
 
7
-    def __init__(self, id):
8
-        self.id = id
15
+    def __init__(self, name):
16
+        self.name = name
17
+
18
+
19
+class EmField_integer(EmFieldType):
20
+
21
+    def __init__(self):
22
+        super(EmField_integer, self).__init__('integer')
23
+
24
+    def to_python(self, value):
25
+        if value == '':
26
+            self.value = 0
27
+        else:
28
+            self.value = int(value)
29
+
30
+    def to_sql(self, value = None):
31
+        if value is None:
32
+            value = self.value
33
+        return str(value)
34
+
35
+    def sql_column(self):
36
+        return "int(11) NOT NULL"
37
+
38
+    def sqlalchemy_args(self):
39
+        # TODO Ajouter la prise en charge de la taille max
40
+        return {'type_': INTEGER, 'nullable': False}
41
+
42
+class EmField_boolean(EmFieldType):
43
+
44
+    def __init__(self):
45
+        super(EmField_boolean, self).__init__('boolean')
46
+
47
+    def to_python(self, value):
48
+        if value:
49
+            self.value = True
50
+        else:
51
+            self.value = False
52
+        return self.value
53
+
54
+    def to_sql(self, value = None):
55
+        if value is None:
56
+            value = self.value
57
+        return 1 if value else 0
58
+
59
+    def sql_column(self):
60
+        return "tinyint(1) DEFAULT NULL"
61
+
62
+    def sqlalchemy_args(self):
63
+        return {'type_': BOOLEAN, 'nullable': True, 'default': None}
64
+
65
+class EmField_char(EmFieldType):
66
+
67
+    def __init__(self):
68
+        super(EmField_char, self).__init__('char')
69
+
70
+    def to_python(self, value):
71
+        if value:
72
+            self.value = value
73
+        else:
74
+            self.value = ''
75
+        return self.value
76
+
77
+    def to_sql(self, value = None):
78
+        if value is None:
79
+            value = self.value
80
+        return value
81
+
82
+    def sql_column(self):
83
+        return "varchar(250) DEFAULT NULL"
84
+
85
+    def sqlalchemy_args(self):
86
+        return {'type_': VARCHAR(250), 'nullable': True, 'default': None}
87
+
88
+
89
+class EmField_mlstring(EmFieldType):
90
+
91
+    def __init__(self):
92
+        super(EmField_mlstring, self).__init__('mlstring')
93
+
94
+    def to_python(self, value):
95
+        self.value = MlString.load(value)
96
+        return self.value
97
+
98
+    def to_sql(self, value = None):
99
+        if value is None:
100
+            value = self.value
101
+        return str(value)
102
+
103
+    def sql_column(self):
104
+        return "varchar(250) DEFAULT NULL"
105
+
106
+    def sqlalchemy_args(self):
107
+        return {'type_': VARCHAR(250), 'nullable': True, 'default': None}

+ 6
- 11
EditorialModel/types.py 查看文件

@@ -1,7 +1,8 @@
1 1
 #-*- coding: utf-8 -*-
2 2
 
3 3
 from EditorialModel.components import EmComponent, EmComponentNotExistError
4
-from Database.sqlobject import SqlObject
4
+from Database import sqlutils
5
+import sqlalchemy as sql
5 6
 
6 7
 import EditorialModel.classes
7 8
 
@@ -24,8 +25,8 @@ class EmType(EmComponent):
24 25
         self.table = EmType.table
25 26
         super(EmType, self).__init__(id_or_name)
26 27
 
27
-    @staticmethod
28
-    def create(name, em_class):
28
+    @classmethod
29
+    def create(c, name, em_class):
29 30
         """ Create a new EmType and instanciate it
30 31
 
31 32
             @param name str: The name of the new type
@@ -34,18 +35,12 @@ class EmType(EmComponent):
34 35
             @see EmComponent::__init__()
35 36
 
36 37
             @todo Change the icon param type
37
-            @todo change staticmethod to classmethod
38
+            @todo check that em_class is an EmClass object
38 39
         """
39 40
         try:
40 41
             exists = EmType(name)
41 42
         except EmComponentNotExistError:
42
-            uids = SqlObject('uids')
43
-            res = uids.wexec(uids.table.insert().values(table=EmType.table))
44
-            uid = res.inserted_primary_key
45
-
46
-            emtype = SqlObject(EmType.table)
47
-            res = emtype.wexec(emtype.table.insert().values(uid=uid, name=name, class_id=em_class.id))
48
-            return EmType(name)
43
+            return super(EmType, c).create({'name':name, 'class_id': em_class.id})
49 44
 
50 45
         return exists
51 46
 

Loading…
取消
儲存