sqlobject 3.13.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlobject/__init__.py +15 -0
- sqlobject/__version__.py +8 -0
- sqlobject/boundattributes.py +124 -0
- sqlobject/cache.py +376 -0
- sqlobject/classregistry.py +138 -0
- sqlobject/col.py +2187 -0
- sqlobject/compat.py +59 -0
- sqlobject/conftest.py +64 -0
- sqlobject/constraints.py +77 -0
- sqlobject/converters.py +278 -0
- sqlobject/dbconnection.py +1137 -0
- sqlobject/dberrors.py +54 -0
- sqlobject/declarative.py +211 -0
- sqlobject/events.py +365 -0
- sqlobject/firebird/__init__.py +8 -0
- sqlobject/firebird/firebirdconnection.py +463 -0
- sqlobject/include/__init__.py +1 -0
- sqlobject/include/hashcol.py +109 -0
- sqlobject/include/tests/__init__.py +1 -0
- sqlobject/include/tests/test_hashcol.py +84 -0
- sqlobject/index.py +189 -0
- sqlobject/inheritance/__init__.py +551 -0
- sqlobject/inheritance/iteration.py +106 -0
- sqlobject/inheritance/tests/__init__.py +1 -0
- sqlobject/inheritance/tests/test_aggregates.py +23 -0
- sqlobject/inheritance/tests/test_asdict.py +48 -0
- sqlobject/inheritance/tests/test_deep_inheritance.py +114 -0
- sqlobject/inheritance/tests/test_destroy_cascade.py +23 -0
- sqlobject/inheritance/tests/test_foreignKey.py +94 -0
- sqlobject/inheritance/tests/test_indexes.py +42 -0
- sqlobject/inheritance/tests/test_inheritance.py +164 -0
- sqlobject/inheritance/tests/test_inheritance_tree.py +50 -0
- sqlobject/joins.py +656 -0
- sqlobject/main.py +1880 -0
- sqlobject/manager/__init__.py +1 -0
- sqlobject/manager/command.py +1309 -0
- sqlobject/maxdb/__init__.py +8 -0
- sqlobject/maxdb/maxdbconnection.py +320 -0
- sqlobject/maxdb/readme.txt +27 -0
- sqlobject/mssql/__init__.py +8 -0
- sqlobject/mssql/mssqlconnection.py +417 -0
- sqlobject/mysql/__init__.py +8 -0
- sqlobject/mysql/mysqlconnection.py +562 -0
- sqlobject/postgres/__init__.py +8 -0
- sqlobject/postgres/pgconnection.py +619 -0
- sqlobject/sqlbuilder.py +1655 -0
- sqlobject/sqlite/__init__.py +8 -0
- sqlobject/sqlite/sqliteconnection.py +418 -0
- sqlobject/sresults.py +377 -0
- sqlobject/styles.py +174 -0
- sqlobject/sybase/__init__.py +8 -0
- sqlobject/sybase/sybaseconnection.py +183 -0
- sqlobject/tests/__init__.py +1 -0
- sqlobject/tests/dbtest.py +363 -0
- sqlobject/tests/test_ForeignKey.py +133 -0
- sqlobject/tests/test_ForeignKey_cascade.py +150 -0
- sqlobject/tests/test_NoneValuedResultItem.py +33 -0
- sqlobject/tests/test_SQLMultipleJoin.py +65 -0
- sqlobject/tests/test_SQLRelatedJoin.py +114 -0
- sqlobject/tests/test_SingleJoin.py +30 -0
- sqlobject/tests/test_aggregates.py +75 -0
- sqlobject/tests/test_aliases.py +49 -0
- sqlobject/tests/test_asdict.py +18 -0
- sqlobject/tests/test_auto.py +218 -0
- sqlobject/tests/test_basic.py +359 -0
- sqlobject/tests/test_blob.py +34 -0
- sqlobject/tests/test_boundattributes.py +67 -0
- sqlobject/tests/test_cache.py +57 -0
- sqlobject/tests/test_class_hash.py +22 -0
- sqlobject/tests/test_columns_order.py +18 -0
- sqlobject/tests/test_combining_joins.py +47 -0
- sqlobject/tests/test_comparison.py +24 -0
- sqlobject/tests/test_compat.py +10 -0
- sqlobject/tests/test_complex_sorting.py +77 -0
- sqlobject/tests/test_constraints.py +43 -0
- sqlobject/tests/test_converters.py +276 -0
- sqlobject/tests/test_create_drop.py +29 -0
- sqlobject/tests/test_csvexport.py +101 -0
- sqlobject/tests/test_csvimport.py +21 -0
- sqlobject/tests/test_cyclic_reference.py +55 -0
- sqlobject/tests/test_datetime.py +171 -0
- sqlobject/tests/test_dbconnection.py +17 -0
- sqlobject/tests/test_decimal.py +63 -0
- sqlobject/tests/test_declarative.py +83 -0
- sqlobject/tests/test_default_style.py +65 -0
- sqlobject/tests/test_delete.py +74 -0
- sqlobject/tests/test_distinct.py +36 -0
- sqlobject/tests/test_empty.py +22 -0
- sqlobject/tests/test_enum.py +75 -0
- sqlobject/tests/test_events.py +142 -0
- sqlobject/tests/test_exceptions.py +40 -0
- sqlobject/tests/test_expire.py +29 -0
- sqlobject/tests/test_groupBy.py +45 -0
- sqlobject/tests/test_identity.py +29 -0
- sqlobject/tests/test_indexes.py +100 -0
- sqlobject/tests/test_inheritance.py +35 -0
- sqlobject/tests/test_joins.py +160 -0
- sqlobject/tests/test_joins_conditional.py +143 -0
- sqlobject/tests/test_jsonbcol.py +53 -0
- sqlobject/tests/test_jsoncol.py +56 -0
- sqlobject/tests/test_lazy.py +165 -0
- sqlobject/tests/test_md5.py +13 -0
- sqlobject/tests/test_mysql.py +50 -0
- sqlobject/tests/test_new_joins.py +165 -0
- sqlobject/tests/test_parse_uri.py +147 -0
- sqlobject/tests/test_paste.py +92 -0
- sqlobject/tests/test_perConnection.py +20 -0
- sqlobject/tests/test_pickle.py +42 -0
- sqlobject/tests/test_picklecol.py +44 -0
- sqlobject/tests/test_postgres.py +82 -0
- sqlobject/tests/test_reparent_sqlmeta.py +33 -0
- sqlobject/tests/test_schema.py +30 -0
- sqlobject/tests/test_select.py +218 -0
- sqlobject/tests/test_select_through.py +63 -0
- sqlobject/tests/test_setters.py +23 -0
- sqlobject/tests/test_slice.py +59 -0
- sqlobject/tests/test_sorting.py +80 -0
- sqlobject/tests/test_sqlbuilder.py +134 -0
- sqlobject/tests/test_sqlbuilder_dbspecific.py +66 -0
- sqlobject/tests/test_sqlbuilder_importproxy.py +55 -0
- sqlobject/tests/test_sqlbuilder_joins_instances.py +90 -0
- sqlobject/tests/test_sqlite.py +160 -0
- sqlobject/tests/test_sqlmeta_idName.py +52 -0
- sqlobject/tests/test_sqlobject_admin.py +38 -0
- sqlobject/tests/test_string_id.py +79 -0
- sqlobject/tests/test_style.py +36 -0
- sqlobject/tests/test_subqueries.py +142 -0
- sqlobject/tests/test_transactions.py +122 -0
- sqlobject/tests/test_unicode.py +84 -0
- sqlobject/tests/test_uuidcol.py +40 -0
- sqlobject/tests/test_validation.py +125 -0
- sqlobject/tests/test_views.py +159 -0
- sqlobject/util/__init__.py +1 -0
- sqlobject/util/csvexport.py +210 -0
- sqlobject/util/csvimport.py +366 -0
- sqlobject/util/moduleloader.py +35 -0
- sqlobject/util/threadinglocal.py +6 -0
- sqlobject/versioning/__init__.py +120 -0
- sqlobject/versioning/test/__init__.py +0 -0
- sqlobject/versioning/test/test_version.py +183 -0
- sqlobject/views.py +151 -0
- sqlobject/wsgi_middleware.py +109 -0
- sqlobject-3.13.1.data/scripts/sqlobject-admin +36 -0
- sqlobject-3.13.1.data/scripts/sqlobject-convertOldURI +19 -0
- sqlobject-3.13.1.dist-info/METADATA +177 -0
- sqlobject-3.13.1.dist-info/RECORD +149 -0
- sqlobject-3.13.1.dist-info/WHEEL +6 -0
- sqlobject-3.13.1.dist-info/entry_points.txt +4 -0
- sqlobject-3.13.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from sqlobject.dbconnection import DBAPI
|
|
2
|
+
from sqlobject import col
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SybaseConnection(DBAPI):
|
|
6
|
+
|
|
7
|
+
supportTransactions = False
|
|
8
|
+
dbName = 'sybase'
|
|
9
|
+
schemes = [dbName]
|
|
10
|
+
NumericType = None
|
|
11
|
+
|
|
12
|
+
def __init__(self, db, user, password='', host='localhost', port=None,
|
|
13
|
+
locking=1, **kw):
|
|
14
|
+
db = db.strip('/')
|
|
15
|
+
import Sybase
|
|
16
|
+
Sybase._ctx.debug = 0
|
|
17
|
+
if SybaseConnection.NumericType is None:
|
|
18
|
+
from Sybase import NumericType
|
|
19
|
+
SybaseConnection.NumericType = NumericType
|
|
20
|
+
from sqlobject.converters import registerConverter, IntConverter
|
|
21
|
+
registerConverter(NumericType, IntConverter)
|
|
22
|
+
self.module = Sybase
|
|
23
|
+
self.locking = int(locking)
|
|
24
|
+
self.host = host
|
|
25
|
+
self.port = port
|
|
26
|
+
self.db = db
|
|
27
|
+
self.user = user
|
|
28
|
+
self.password = password
|
|
29
|
+
autoCommit = kw.get('autoCommit')
|
|
30
|
+
if autoCommit:
|
|
31
|
+
autoCommit = int(autoCommit)
|
|
32
|
+
else:
|
|
33
|
+
autoCommit = None
|
|
34
|
+
kw['autoCommit'] = autoCommit
|
|
35
|
+
DBAPI.__init__(self, **kw)
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def _connectionFromParams(cls, user, password, host, port, path, args):
|
|
39
|
+
return cls(user=user, password=password,
|
|
40
|
+
host=host or 'localhost', port=port, db=path, **args)
|
|
41
|
+
|
|
42
|
+
def insert_id(self, conn):
|
|
43
|
+
"""
|
|
44
|
+
Sybase adapter/cursor does not support the
|
|
45
|
+
insert_id method.
|
|
46
|
+
"""
|
|
47
|
+
c = conn.cursor()
|
|
48
|
+
c.execute('SELECT @@IDENTITY')
|
|
49
|
+
result = c.fetchone()[0]
|
|
50
|
+
c.close()
|
|
51
|
+
return result
|
|
52
|
+
|
|
53
|
+
def makeConnection(self):
|
|
54
|
+
return self.module.connect(self.host, self.user, self.password,
|
|
55
|
+
database=self.db,
|
|
56
|
+
auto_commit=self.autoCommit,
|
|
57
|
+
locking=self.locking)
|
|
58
|
+
|
|
59
|
+
HAS_IDENTITY = """
|
|
60
|
+
SELECT col.name, col.status, obj.name
|
|
61
|
+
FROM syscolumns col
|
|
62
|
+
JOIN sysobjects obj
|
|
63
|
+
ON obj.id = col.id
|
|
64
|
+
WHERE obj.name = '%s'
|
|
65
|
+
AND (col.status & 0x80) = 0x80
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def _hasIdentity(self, conn, table):
|
|
69
|
+
query = self.HAS_IDENTITY % table
|
|
70
|
+
c = conn.cursor()
|
|
71
|
+
c.execute(query)
|
|
72
|
+
r = c.fetchone()
|
|
73
|
+
c.close()
|
|
74
|
+
return r is not None
|
|
75
|
+
|
|
76
|
+
def _queryInsertID(self, conn, soInstance, id, names, values):
|
|
77
|
+
table = soInstance.sqlmeta.table
|
|
78
|
+
idName = soInstance.sqlmeta.idName
|
|
79
|
+
c = conn.cursor()
|
|
80
|
+
if id is not None:
|
|
81
|
+
names = [idName] + names
|
|
82
|
+
values = [id] + values
|
|
83
|
+
|
|
84
|
+
has_identity = self._hasIdentity(conn, table)
|
|
85
|
+
identity_insert_on = False
|
|
86
|
+
if has_identity and (id is not None):
|
|
87
|
+
identity_insert_on = True
|
|
88
|
+
c.execute('SET IDENTITY_INSERT %s ON' % table)
|
|
89
|
+
|
|
90
|
+
if names and values:
|
|
91
|
+
q = self._insertSQL(table, names, values)
|
|
92
|
+
else:
|
|
93
|
+
q = "INSERT INTO %s DEFAULT VALUES" % table
|
|
94
|
+
if self.debug:
|
|
95
|
+
self.printDebug(conn, q, 'QueryIns')
|
|
96
|
+
c.execute(q)
|
|
97
|
+
if has_identity and identity_insert_on:
|
|
98
|
+
c.execute('SET IDENTITY_INSERT %s OFF' % table)
|
|
99
|
+
c.close()
|
|
100
|
+
if id is None:
|
|
101
|
+
id = self.insert_id(conn)
|
|
102
|
+
if self.debugOutput:
|
|
103
|
+
self.printDebug(conn, id, 'QueryIns', 'result')
|
|
104
|
+
return id
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def _queryAddLimitOffset(cls, query, start, end):
|
|
108
|
+
# XXX Sybase doesn't support OFFSET
|
|
109
|
+
if end:
|
|
110
|
+
return "SET ROWCOUNT %i %s SET ROWCOUNT 0" % (end, query)
|
|
111
|
+
return query
|
|
112
|
+
|
|
113
|
+
def createReferenceConstraint(self, soClass, col):
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
def createColumn(self, soClass, col):
|
|
117
|
+
return col.sybaseCreateSQL()
|
|
118
|
+
|
|
119
|
+
def createIDColumn(self, soClass):
|
|
120
|
+
key_type = {int: "NUMERIC(18,0)", str: "TEXT"}[soClass.sqlmeta.idType]
|
|
121
|
+
return '%s %s IDENTITY UNIQUE' % (soClass.sqlmeta.idName, key_type)
|
|
122
|
+
|
|
123
|
+
def createIndexSQL(self, soClass, index):
|
|
124
|
+
return index.sybaseCreateIndexSQL(soClass)
|
|
125
|
+
|
|
126
|
+
def joinSQLType(self, join):
|
|
127
|
+
return 'NUMERIC(18,0) NOT NULL'
|
|
128
|
+
|
|
129
|
+
SHOW_TABLES = "SELECT name FROM sysobjects WHERE type='U'"
|
|
130
|
+
|
|
131
|
+
def tableExists(self, tableName):
|
|
132
|
+
for (table,) in self.queryAll(self.SHOW_TABLES):
|
|
133
|
+
if table.lower() == tableName.lower():
|
|
134
|
+
return True
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
def addColumn(self, tableName, column):
|
|
138
|
+
self.query('ALTER TABLE %s ADD COLUMN %s' %
|
|
139
|
+
(tableName,
|
|
140
|
+
column.sybaseCreateSQL()))
|
|
141
|
+
|
|
142
|
+
def delColumn(self, sqlmeta, column):
|
|
143
|
+
self.query(
|
|
144
|
+
'ALTER TABLE %s DROP COLUMN %s' % (sqlmeta.table, column.dbName))
|
|
145
|
+
|
|
146
|
+
SHOW_COLUMNS = ('SELECT '
|
|
147
|
+
'COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT '
|
|
148
|
+
'FROM INFORMATION_SCHEMA.COLUMNS '
|
|
149
|
+
'WHERE TABLE_NAME = \'%s\'')
|
|
150
|
+
|
|
151
|
+
def columnsFromSchema(self, tableName, soClass):
|
|
152
|
+
colData = self.queryAll(self.SHOW_COLUMNS
|
|
153
|
+
% tableName)
|
|
154
|
+
results = []
|
|
155
|
+
for field, t, nullAllowed, default in colData:
|
|
156
|
+
if field == soClass.sqlmeta.idName:
|
|
157
|
+
continue
|
|
158
|
+
colClass, kw = self.guessClass(t)
|
|
159
|
+
kw['name'] = soClass.sqlmeta.style.dbColumnToPythonAttr(field)
|
|
160
|
+
kw['dbName'] = field
|
|
161
|
+
kw['notNone'] = not nullAllowed
|
|
162
|
+
kw['default'] = default
|
|
163
|
+
# @@ skip key...
|
|
164
|
+
# @@ skip extra...
|
|
165
|
+
kw['forceDBName'] = True
|
|
166
|
+
results.append(colClass(**kw))
|
|
167
|
+
return results
|
|
168
|
+
|
|
169
|
+
def _setAutoCommit(self, conn, auto):
|
|
170
|
+
conn.auto_commit = auto
|
|
171
|
+
|
|
172
|
+
def guessClass(self, t):
|
|
173
|
+
if t.startswith('int'):
|
|
174
|
+
return col.IntCol, {}
|
|
175
|
+
elif t.startswith('varchar'):
|
|
176
|
+
return col.StringCol, {'length': int(t[8:-1])}
|
|
177
|
+
elif t.startswith('char'):
|
|
178
|
+
return col.StringCol, {'length': int(t[5:-1]),
|
|
179
|
+
'varchar': False}
|
|
180
|
+
elif t.startswith('datetime'):
|
|
181
|
+
return col.DateTimeCol, {}
|
|
182
|
+
else:
|
|
183
|
+
return col.Col, {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The framework for making database tests.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import print_function
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from pytest import raises, skip
|
|
10
|
+
import sqlobject
|
|
11
|
+
from sqlobject.col import use_microseconds
|
|
12
|
+
import sqlobject.conftest as conftest
|
|
13
|
+
|
|
14
|
+
if sys.platform[:3] == "win":
|
|
15
|
+
def getcwd():
|
|
16
|
+
return os.getcwd().replace('\\', '/')
|
|
17
|
+
else:
|
|
18
|
+
getcwd = os.getcwd
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
supportsMatrix defines what database backends support what features.
|
|
22
|
+
Each feature has a name, if you see a key like '+featureName' then
|
|
23
|
+
only the databases listed support the feature. Conversely,
|
|
24
|
+
'-featureName' means all databases *except* the ones listed support
|
|
25
|
+
the feature. The databases are given by their SQLObject string name,
|
|
26
|
+
separated by spaces.
|
|
27
|
+
|
|
28
|
+
The function supports(featureName) returns True or False based on this,
|
|
29
|
+
and you can use it like::
|
|
30
|
+
|
|
31
|
+
def test_featureX():
|
|
32
|
+
if not supports('featureX'):
|
|
33
|
+
pytest.skip("Doesn't support featureX")
|
|
34
|
+
"""
|
|
35
|
+
supportsMatrix = {
|
|
36
|
+
'-blobData': 'mssql',
|
|
37
|
+
'-decimalColumn': 'mssql',
|
|
38
|
+
'-dropTableCascade': 'sybase mssql mysql',
|
|
39
|
+
'-emptyTable': 'mssql',
|
|
40
|
+
'+exceptions': 'mysql postgres sqlite',
|
|
41
|
+
'-expressionIndex': 'mysql sqlite firebird mssql',
|
|
42
|
+
'-limitSelect': 'mssql',
|
|
43
|
+
'+memorydb': 'sqlite',
|
|
44
|
+
'+rlike': 'mysql postgres sqlite',
|
|
45
|
+
'+schema': 'postgres',
|
|
46
|
+
'-transactions': ' ',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def setupClass(soClasses, force=False):
|
|
51
|
+
"""
|
|
52
|
+
Makes sure the classes have a corresponding and correct table.
|
|
53
|
+
This won't recreate the table if it already exists. It will check
|
|
54
|
+
that the table is properly defined (in case you change your table
|
|
55
|
+
definition).
|
|
56
|
+
|
|
57
|
+
You can provide a single class or a list of classes; if a list
|
|
58
|
+
then classes will be created in the order you provide, and
|
|
59
|
+
destroyed in the opposite order. So if class A depends on class
|
|
60
|
+
B, then do setupClass([B, A]) and B won't be destroyed or cleared
|
|
61
|
+
until after A is destroyed or cleared.
|
|
62
|
+
|
|
63
|
+
If force is true, then the database will be recreated no matter
|
|
64
|
+
what.
|
|
65
|
+
"""
|
|
66
|
+
# global hub
|
|
67
|
+
if not isinstance(soClasses, (list, tuple)):
|
|
68
|
+
soClasses = [soClasses]
|
|
69
|
+
connection = getConnection()
|
|
70
|
+
for soClass in soClasses:
|
|
71
|
+
# This would be an alternate way to register connections:
|
|
72
|
+
# try:
|
|
73
|
+
# hub
|
|
74
|
+
# except NameError:
|
|
75
|
+
# hub = sqlobject.dbconnection.ConnectionHub()
|
|
76
|
+
# soClass._connection = hub
|
|
77
|
+
# hub.threadConnection = connection
|
|
78
|
+
# hub.processConnection = connection
|
|
79
|
+
soClass._connection = connection
|
|
80
|
+
installOrClear(soClasses, force=force)
|
|
81
|
+
return soClasses
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def speedupSQLiteConnection(connection):
|
|
85
|
+
connection.query("PRAGMA synchronous=OFF")
|
|
86
|
+
connection.query("PRAGMA count_changes=OFF")
|
|
87
|
+
connection.query("PRAGMA journal_mode=MEMORY")
|
|
88
|
+
connection.query("PRAGMA temp_store=MEMORY")
|
|
89
|
+
|
|
90
|
+
installedDBFilename = os.path.join(getcwd(), 'dbs_data.tmp')
|
|
91
|
+
|
|
92
|
+
installedDBTracker = sqlobject.connectionForURI(
|
|
93
|
+
'sqlite:///' + installedDBFilename)
|
|
94
|
+
speedupSQLiteConnection(installedDBTracker)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def getConnection(**kw):
|
|
98
|
+
name = getConnectionURI()
|
|
99
|
+
conn = sqlobject.connectionForURI(name, **kw)
|
|
100
|
+
if conftest.option.show_sql:
|
|
101
|
+
conn.debug = True
|
|
102
|
+
if conftest.option.show_sql_output:
|
|
103
|
+
conn.debugOutput = True
|
|
104
|
+
if (conn.dbName == 'postgres') and (conn.driver == 'pg8000') \
|
|
105
|
+
and conn._pool is not None:
|
|
106
|
+
conn._pool = None
|
|
107
|
+
if (conn.dbName == 'sqlite') and not conn._memory:
|
|
108
|
+
speedupSQLiteConnection(conn)
|
|
109
|
+
return conn
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def getConnectionURI():
|
|
113
|
+
name = conftest.option.Database
|
|
114
|
+
if name in conftest.connectionShortcuts:
|
|
115
|
+
name = conftest.connectionShortcuts[name]
|
|
116
|
+
return name
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
connection = getConnection()
|
|
120
|
+
except Exception as e:
|
|
121
|
+
# At least this module should be importable...
|
|
122
|
+
# The module was imported during documentation building
|
|
123
|
+
if 'sphinx' not in sys.modules:
|
|
124
|
+
print("Could not open database: %s" % e, file=sys.stderr)
|
|
125
|
+
else:
|
|
126
|
+
if (connection.dbName == 'firebird'):
|
|
127
|
+
use_microseconds(False)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class InstalledTestDatabase(sqlobject.SQLObject):
|
|
131
|
+
"""
|
|
132
|
+
This table is set up in SQLite (always, regardless of --Database) and
|
|
133
|
+
tracks what tables have been set up in the 'real' database. This
|
|
134
|
+
way we don't keep recreating the tables over and over when there
|
|
135
|
+
are multiple tests that use a table.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
_connection = installedDBTracker
|
|
139
|
+
table_name = sqlobject.StringCol(notNull=True)
|
|
140
|
+
createSQL = sqlobject.StringCol(notNull=True)
|
|
141
|
+
connectionURI = sqlobject.StringCol(notNull=True)
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def installOrClear(cls, soClasses, force=False):
|
|
145
|
+
cls.setup()
|
|
146
|
+
reversed = list(soClasses)[:]
|
|
147
|
+
reversed.reverse()
|
|
148
|
+
# If anything needs to be dropped, they all must be dropped
|
|
149
|
+
# But if we're forcing it, then we'll always drop
|
|
150
|
+
if force:
|
|
151
|
+
any_drops = True
|
|
152
|
+
else:
|
|
153
|
+
any_drops = False
|
|
154
|
+
for soClass in reversed:
|
|
155
|
+
table = soClass.sqlmeta.table
|
|
156
|
+
if not soClass._connection.tableExists(table):
|
|
157
|
+
continue
|
|
158
|
+
items = list(cls.selectBy(
|
|
159
|
+
table_name=table,
|
|
160
|
+
connectionURI=soClass._connection.uri()))
|
|
161
|
+
if items:
|
|
162
|
+
instance = items[0]
|
|
163
|
+
sql = instance.createSQL
|
|
164
|
+
else:
|
|
165
|
+
sql = None
|
|
166
|
+
newSQL, constraints = soClass.createTableSQL()
|
|
167
|
+
if sql != newSQL:
|
|
168
|
+
if sql is not None:
|
|
169
|
+
instance.destroySelf()
|
|
170
|
+
any_drops = True
|
|
171
|
+
break
|
|
172
|
+
for soClass in reversed:
|
|
173
|
+
if soClass._connection.tableExists(soClass.sqlmeta.table):
|
|
174
|
+
if any_drops:
|
|
175
|
+
cls.drop(soClass)
|
|
176
|
+
else:
|
|
177
|
+
cls.clear(soClass)
|
|
178
|
+
for soClass in soClasses:
|
|
179
|
+
table = soClass.sqlmeta.table
|
|
180
|
+
if not soClass._connection.tableExists(table):
|
|
181
|
+
cls.install(soClass)
|
|
182
|
+
|
|
183
|
+
@classmethod
|
|
184
|
+
def install(cls, soClass):
|
|
185
|
+
"""
|
|
186
|
+
Creates the given table in its database.
|
|
187
|
+
"""
|
|
188
|
+
sql = getattr(soClass, soClass._connection.dbName + 'Create',
|
|
189
|
+
None)
|
|
190
|
+
all_extra = []
|
|
191
|
+
if sql:
|
|
192
|
+
soClass._connection.query(sql)
|
|
193
|
+
else:
|
|
194
|
+
sql, extra_sql = soClass.createTableSQL()
|
|
195
|
+
soClass.createTable(applyConstraints=False)
|
|
196
|
+
all_extra.extend(extra_sql)
|
|
197
|
+
cls(table_name=soClass.sqlmeta.table,
|
|
198
|
+
createSQL=sql,
|
|
199
|
+
connectionURI=soClass._connection.uri())
|
|
200
|
+
for extra_sql in all_extra:
|
|
201
|
+
soClass._connection.query(extra_sql)
|
|
202
|
+
|
|
203
|
+
@classmethod
|
|
204
|
+
def drop(cls, soClass):
|
|
205
|
+
"""
|
|
206
|
+
Drops a the given table from its database
|
|
207
|
+
"""
|
|
208
|
+
sql = getattr(soClass, soClass._connection.dbName + 'Drop', None)
|
|
209
|
+
if sql:
|
|
210
|
+
soClass._connection.query(sql)
|
|
211
|
+
else:
|
|
212
|
+
soClass.dropTable()
|
|
213
|
+
|
|
214
|
+
@classmethod
|
|
215
|
+
def clear(cls, soClass):
|
|
216
|
+
"""
|
|
217
|
+
Removes all the rows from a table.
|
|
218
|
+
"""
|
|
219
|
+
soClass.clearTable()
|
|
220
|
+
|
|
221
|
+
@classmethod
|
|
222
|
+
def setup(cls):
|
|
223
|
+
"""
|
|
224
|
+
This sets up *this* table.
|
|
225
|
+
"""
|
|
226
|
+
if not cls._connection.tableExists(cls.sqlmeta.table):
|
|
227
|
+
cls.createTable()
|
|
228
|
+
|
|
229
|
+
installOrClear = InstalledTestDatabase.installOrClear
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class Dummy(object):
|
|
233
|
+
|
|
234
|
+
"""
|
|
235
|
+
Used for creating fake objects; a really poor 'mock object'.
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
def __init__(self, **kw):
|
|
239
|
+
for name, value in kw.items():
|
|
240
|
+
setattr(self, name, value)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def inserts(cls, data, schema=None):
|
|
244
|
+
"""
|
|
245
|
+
Creates a bunch of rows.
|
|
246
|
+
|
|
247
|
+
You can use it like::
|
|
248
|
+
|
|
249
|
+
inserts(Person, [{'fname': 'blah', 'lname': 'doe'}, ...])
|
|
250
|
+
|
|
251
|
+
Or::
|
|
252
|
+
|
|
253
|
+
inserts(Person, [('blah', 'doe')], schema=
|
|
254
|
+
['fname', 'lname'])
|
|
255
|
+
|
|
256
|
+
If you give a single string for the `schema` then it'll split
|
|
257
|
+
that string to get the list of column names.
|
|
258
|
+
"""
|
|
259
|
+
if schema:
|
|
260
|
+
if isinstance(schema, str):
|
|
261
|
+
schema = schema.split()
|
|
262
|
+
keywordData = []
|
|
263
|
+
for item in data:
|
|
264
|
+
itemDict = {}
|
|
265
|
+
for name, value in zip(schema, item):
|
|
266
|
+
itemDict[name] = value
|
|
267
|
+
keywordData.append(itemDict)
|
|
268
|
+
data = keywordData
|
|
269
|
+
results = []
|
|
270
|
+
for args in data:
|
|
271
|
+
results.append(cls(**args))
|
|
272
|
+
return results
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def supports(feature):
|
|
276
|
+
dbName = connection.dbName
|
|
277
|
+
support = supportsMatrix.get('+' + feature, None)
|
|
278
|
+
notSupport = supportsMatrix.get('-' + feature, None)
|
|
279
|
+
if support is not None and dbName in support.split():
|
|
280
|
+
return True
|
|
281
|
+
elif support:
|
|
282
|
+
return False
|
|
283
|
+
if notSupport is not None and dbName in notSupport.split():
|
|
284
|
+
return False
|
|
285
|
+
elif notSupport:
|
|
286
|
+
return True
|
|
287
|
+
assert notSupport is not None or support is not None, (
|
|
288
|
+
"The supportMatrix does not list this feature: %r"
|
|
289
|
+
% feature)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# To avoid name clashes:
|
|
293
|
+
_inserts = inserts
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def setSQLiteConnectionFactory(TableClass, factory):
|
|
297
|
+
from sqlobject.sqlite.sqliteconnection import SQLiteConnection
|
|
298
|
+
conn = TableClass._connection
|
|
299
|
+
TableClass._connection = SQLiteConnection(
|
|
300
|
+
filename=conn.filename,
|
|
301
|
+
name=conn.name, debug=conn.debug, debugOutput=conn.debugOutput,
|
|
302
|
+
cache=conn.cache, style=conn.style, autoCommit=conn.autoCommit,
|
|
303
|
+
debugThreading=conn.debugThreading, registry=conn.registry,
|
|
304
|
+
factory=factory
|
|
305
|
+
)
|
|
306
|
+
speedupSQLiteConnection(TableClass._connection)
|
|
307
|
+
installOrClear([TableClass])
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def deprecated_module():
|
|
311
|
+
sqlobject.main.warnings_level = None
|
|
312
|
+
sqlobject.main.exception_level = None
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def setup_module(mod):
|
|
316
|
+
# modules with '_old' test backward compatible methods, so they
|
|
317
|
+
# don't get warnings or errors.
|
|
318
|
+
mod_name = str(mod.__name__)
|
|
319
|
+
if mod_name.endswith('/py'):
|
|
320
|
+
mod_name = mod_name[:-3]
|
|
321
|
+
if mod_name.endswith('_old'):
|
|
322
|
+
sqlobject.main.warnings_level = None
|
|
323
|
+
sqlobject.main.exception_level = None
|
|
324
|
+
else:
|
|
325
|
+
sqlobject.main.warnings_level = None
|
|
326
|
+
sqlobject.main.exception_level = 0
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def teardown_module(mod=None):
|
|
330
|
+
sqlobject.main.warnings_level = None
|
|
331
|
+
sqlobject.main.exception_level = 0
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def setupLogging():
|
|
335
|
+
fmt = '[%(asctime)s] %(name)s %(levelname)s: %(message)s'
|
|
336
|
+
formatter = logging.Formatter(fmt)
|
|
337
|
+
hdlr = logging.StreamHandler(sys.stderr)
|
|
338
|
+
hdlr.setFormatter(formatter)
|
|
339
|
+
hdlr.setLevel(logging.NOTSET)
|
|
340
|
+
logger = logging.getLogger()
|
|
341
|
+
logger.addHandler(hdlr)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def setupCyclicClasses(*classes):
|
|
345
|
+
if not supports('dropTableCascade'):
|
|
346
|
+
skip("dropTableCascade isn't supported")
|
|
347
|
+
conn = getConnection()
|
|
348
|
+
for soClass in classes:
|
|
349
|
+
soClass.setConnection(conn)
|
|
350
|
+
soClass.dropTable(ifExists=True, cascade=True)
|
|
351
|
+
|
|
352
|
+
constraints = []
|
|
353
|
+
for soClass in classes:
|
|
354
|
+
constraints += soClass.createTable(ifNotExists=True,
|
|
355
|
+
applyConstraints=False)
|
|
356
|
+
for constraint in constraints:
|
|
357
|
+
conn.query(constraint)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
__all__ = ['Dummy', 'deprecated_module', 'getConnection', 'getConnectionURI',
|
|
361
|
+
'inserts', 'raises', 'setupClass', 'setupCyclicClasses',
|
|
362
|
+
'setupLogging', 'setup_module', 'supports', 'teardown_module',
|
|
363
|
+
]
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from formencode import validators
|
|
2
|
+
from sqlobject import ForeignKey, IntCol, SQLObject, StringCol
|
|
3
|
+
from sqlobject.tests.dbtest import getConnection, InstalledTestDatabase, \
|
|
4
|
+
raises, setupClass, setupCyclicClasses
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SOTestComposerKey(SQLObject):
|
|
8
|
+
name = StringCol()
|
|
9
|
+
id2 = IntCol(default=None, unique=True)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SOTestWorkKey(SQLObject):
|
|
13
|
+
class sqlmeta:
|
|
14
|
+
idName = "work_id"
|
|
15
|
+
|
|
16
|
+
composer = ForeignKey('SOTestComposerKey', cascade=True)
|
|
17
|
+
title = StringCol()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SOTestWorkKey2(SQLObject):
|
|
21
|
+
title = StringCol()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SOTestOtherColumn(SQLObject):
|
|
25
|
+
key1 = ForeignKey('SOTestComposerKey', default=None)
|
|
26
|
+
key2 = ForeignKey('SOTestComposerKey', refColumn='id2', default=None)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test1():
|
|
30
|
+
setupClass([SOTestComposerKey, SOTestWorkKey])
|
|
31
|
+
|
|
32
|
+
c = SOTestComposerKey(name='Mahler, Gustav')
|
|
33
|
+
w1 = SOTestWorkKey(composer=c, title='Symphony No. 9')
|
|
34
|
+
w2 = SOTestWorkKey(composer=None, title=None)
|
|
35
|
+
|
|
36
|
+
# Select by usual way
|
|
37
|
+
s = SOTestWorkKey.selectBy(composerID=c.id, title='Symphony No. 9')
|
|
38
|
+
assert s.count() == 1
|
|
39
|
+
assert s[0] == w1
|
|
40
|
+
# selectBy object.id
|
|
41
|
+
s = SOTestWorkKey.selectBy(composer=c.id, title='Symphony No. 9')
|
|
42
|
+
assert s.count() == 1
|
|
43
|
+
assert s[0] == w1
|
|
44
|
+
# selectBy object
|
|
45
|
+
s = SOTestWorkKey.selectBy(composer=c, title='Symphony No. 9')
|
|
46
|
+
assert s.count() == 1
|
|
47
|
+
assert s[0] == w1
|
|
48
|
+
# selectBy id
|
|
49
|
+
s = SOTestWorkKey.selectBy(id=w1.id)
|
|
50
|
+
assert s.count() == 1
|
|
51
|
+
assert s[0] == w1
|
|
52
|
+
# is None handled correctly?
|
|
53
|
+
s = SOTestWorkKey.selectBy(composer=None, title=None)
|
|
54
|
+
assert s.count() == 1
|
|
55
|
+
assert s[0] == w2
|
|
56
|
+
|
|
57
|
+
s = SOTestWorkKey.selectBy()
|
|
58
|
+
assert s.count() == 2
|
|
59
|
+
|
|
60
|
+
# select with objects
|
|
61
|
+
s = SOTestWorkKey.select(SOTestWorkKey.q.composerID == c.id)
|
|
62
|
+
assert s.count() == 1
|
|
63
|
+
assert s[0] == w1
|
|
64
|
+
s = SOTestWorkKey.select(SOTestWorkKey.q.composer == c.id)
|
|
65
|
+
assert s.count() == 1
|
|
66
|
+
assert s[0] == w1
|
|
67
|
+
s = SOTestWorkKey.select(SOTestWorkKey.q.composerID == c)
|
|
68
|
+
assert s.count() == 1
|
|
69
|
+
assert s[0] == w1
|
|
70
|
+
s = SOTestWorkKey.select(SOTestWorkKey.q.composer == c)
|
|
71
|
+
assert s.count() == 1
|
|
72
|
+
assert s[0] == w1
|
|
73
|
+
s = SOTestWorkKey.select(
|
|
74
|
+
(SOTestWorkKey.q.composer == c)
|
|
75
|
+
& (SOTestWorkKey.q.title == 'Symphony No. 9'))
|
|
76
|
+
assert s.count() == 1
|
|
77
|
+
assert s[0] == w1
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test2():
|
|
81
|
+
SOTestWorkKey._connection = getConnection()
|
|
82
|
+
InstalledTestDatabase.drop(SOTestWorkKey)
|
|
83
|
+
setupClass([SOTestComposerKey, SOTestWorkKey2], force=True)
|
|
84
|
+
SOTestWorkKey2.sqlmeta.addColumn(ForeignKey('SOTestComposerKey'),
|
|
85
|
+
changeSchema=True)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_otherColumn():
|
|
89
|
+
setupClass([SOTestComposerKey, SOTestOtherColumn])
|
|
90
|
+
test_composer1 = SOTestComposerKey(name='Test1')
|
|
91
|
+
test_composer2 = SOTestComposerKey(name='Test2', id2=2)
|
|
92
|
+
test_fkey = SOTestOtherColumn(key1=test_composer1)
|
|
93
|
+
test_other = SOTestOtherColumn(key2=test_composer2.id2)
|
|
94
|
+
getConnection().cache.clear()
|
|
95
|
+
assert test_fkey.key1 == test_composer1
|
|
96
|
+
assert test_other.key2 == test_composer2
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class SOTestFKValidationA(SQLObject):
|
|
100
|
+
name = StringCol()
|
|
101
|
+
bfk = ForeignKey("SOTestFKValidationB")
|
|
102
|
+
cfk = ForeignKey("SOTestFKValidationC", default=None)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class SOTestFKValidationB(SQLObject):
|
|
106
|
+
name = StringCol()
|
|
107
|
+
afk = ForeignKey("SOTestFKValidationA")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class SOTestFKValidationC(SQLObject):
|
|
111
|
+
class sqlmeta:
|
|
112
|
+
idType = str
|
|
113
|
+
name = StringCol()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_foreignkey_validation():
|
|
117
|
+
setupCyclicClasses(SOTestFKValidationA, SOTestFKValidationB,
|
|
118
|
+
SOTestFKValidationC)
|
|
119
|
+
a = SOTestFKValidationA(name="testa", bfk=None)
|
|
120
|
+
b = SOTestFKValidationB(name="testb", afk=a)
|
|
121
|
+
c = SOTestFKValidationC(id='testc', name="testc")
|
|
122
|
+
a.bfk = b
|
|
123
|
+
a.cfk = c
|
|
124
|
+
assert a.bfk == b
|
|
125
|
+
assert a.cfk == c
|
|
126
|
+
assert b.afk == a
|
|
127
|
+
|
|
128
|
+
raises(validators.Invalid,
|
|
129
|
+
SOTestFKValidationA, name="testa", bfk='testb', cfk='testc')
|
|
130
|
+
|
|
131
|
+
a = SOTestFKValidationA(name="testa", bfk=1, cfk='testc')
|
|
132
|
+
assert a.bfkID == 1
|
|
133
|
+
assert a.cfkID == 'testc'
|