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,120 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from sqlobject import col, events, SQLObject, AND
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Version(SQLObject):
|
|
6
|
+
def restore(self):
|
|
7
|
+
values = self.sqlmeta.asDict()
|
|
8
|
+
del values['id']
|
|
9
|
+
del values['masterID']
|
|
10
|
+
del values['dateArchived']
|
|
11
|
+
for _col in self.extraCols:
|
|
12
|
+
del values[_col]
|
|
13
|
+
self.masterClass.get(self.masterID).set(**values)
|
|
14
|
+
|
|
15
|
+
def nextVersion(self):
|
|
16
|
+
version = self.select(
|
|
17
|
+
AND(self.q.masterID == self.masterID, self.q.id > self.id),
|
|
18
|
+
orderBy=self.q.id)
|
|
19
|
+
if version.count():
|
|
20
|
+
return version[0]
|
|
21
|
+
else:
|
|
22
|
+
return self.master
|
|
23
|
+
|
|
24
|
+
def getChangedFields(self):
|
|
25
|
+
next = self.nextVersion()
|
|
26
|
+
columns = self.masterClass.sqlmeta.columns
|
|
27
|
+
fields = []
|
|
28
|
+
for column in columns:
|
|
29
|
+
if column not in ["dateArchived", "id", "masterID"]:
|
|
30
|
+
if getattr(self, column) != getattr(next, column):
|
|
31
|
+
fields.append(column.title())
|
|
32
|
+
|
|
33
|
+
return fields
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def select(cls, clause=None, *args, **kw):
|
|
37
|
+
if not getattr(cls, '_connection', None):
|
|
38
|
+
cls._connection = cls.masterClass._connection
|
|
39
|
+
return super(Version, cls).select(clause, *args, **kw)
|
|
40
|
+
|
|
41
|
+
def __getattr__(self, attr):
|
|
42
|
+
if attr in self.__dict__:
|
|
43
|
+
return self.__dict__[attr]
|
|
44
|
+
else:
|
|
45
|
+
return getattr(self.master, attr)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def getColumns(columns, cls):
|
|
49
|
+
for column, defi in cls.sqlmeta.columnDefinitions.items():
|
|
50
|
+
if column.endswith("ID") and isinstance(defi, col.ForeignKey):
|
|
51
|
+
column = column[:-2]
|
|
52
|
+
|
|
53
|
+
# remove incompatible constraints
|
|
54
|
+
kwds = dict(defi._kw)
|
|
55
|
+
for kw in ["alternateID", "unique"]:
|
|
56
|
+
if kw in kwds:
|
|
57
|
+
del kwds[kw]
|
|
58
|
+
columns[column] = defi.__class__(**kwds)
|
|
59
|
+
|
|
60
|
+
# ascend heirarchy
|
|
61
|
+
if cls.sqlmeta.parentClass:
|
|
62
|
+
getColumns(columns, cls.sqlmeta.parentClass)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Versioning(object):
|
|
66
|
+
def __init__(self, extraCols=None):
|
|
67
|
+
if extraCols:
|
|
68
|
+
self.extraCols = extraCols
|
|
69
|
+
else:
|
|
70
|
+
self.extraCols = {}
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
def __addtoclass__(self, soClass, name):
|
|
74
|
+
self.name = name
|
|
75
|
+
self.soClass = soClass
|
|
76
|
+
|
|
77
|
+
attrs = {'dateArchived': col.DateTimeCol(default=datetime.now),
|
|
78
|
+
'master': col.ForeignKey(self.soClass.__name__),
|
|
79
|
+
'masterClass': self.soClass,
|
|
80
|
+
'extraCols': self.extraCols
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getColumns(attrs, self.soClass)
|
|
84
|
+
|
|
85
|
+
attrs.update(self.extraCols)
|
|
86
|
+
|
|
87
|
+
self.versionClass = type(self.soClass.__name__ + 'Versions',
|
|
88
|
+
(Version,),
|
|
89
|
+
attrs)
|
|
90
|
+
|
|
91
|
+
if '_connection' in self.soClass.__dict__:
|
|
92
|
+
self.versionClass._connection = \
|
|
93
|
+
self.soClass.__dict__['_connection']
|
|
94
|
+
|
|
95
|
+
events.listen(self.createTable,
|
|
96
|
+
soClass, events.CreateTableSignal)
|
|
97
|
+
events.listen(self.rowUpdate, soClass,
|
|
98
|
+
events.RowUpdateSignal)
|
|
99
|
+
|
|
100
|
+
def createVersionTable(self, cls, conn):
|
|
101
|
+
self.versionClass.createTable(ifNotExists=True, connection=conn)
|
|
102
|
+
|
|
103
|
+
def createTable(self, soClass, connection, extra_sql, post_funcs):
|
|
104
|
+
assert soClass is self.soClass
|
|
105
|
+
post_funcs.append(self.createVersionTable)
|
|
106
|
+
|
|
107
|
+
def rowUpdate(self, instance, kwargs):
|
|
108
|
+
if instance.childName and instance.childName != self.soClass.__name__:
|
|
109
|
+
return # if you want your child class versioned, version it
|
|
110
|
+
|
|
111
|
+
values = instance.sqlmeta.asDict()
|
|
112
|
+
del values['id']
|
|
113
|
+
values['masterID'] = instance.id
|
|
114
|
+
self.versionClass(connection=instance._connection, **values)
|
|
115
|
+
|
|
116
|
+
def __get__(self, obj, type=None):
|
|
117
|
+
if obj is None:
|
|
118
|
+
return self
|
|
119
|
+
return self.versionClass.select(
|
|
120
|
+
self.versionClass.q.masterID == obj.id, connection=obj._connection)
|
|
File without changes
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from sqlobject import ForeignKey, IntCol, SQLObject, StringCol
|
|
2
|
+
from sqlobject.inheritance import InheritableSQLObject
|
|
3
|
+
from sqlobject.versioning import Versioning
|
|
4
|
+
from sqlobject.tests.dbtest import setupClass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MyClass(SQLObject):
|
|
8
|
+
name = StringCol()
|
|
9
|
+
versions = Versioning()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Base(InheritableSQLObject):
|
|
13
|
+
name = StringCol()
|
|
14
|
+
so_value = IntCol(default=0)
|
|
15
|
+
versions = Versioning()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Child(Base):
|
|
19
|
+
toy = StringCol()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Government(InheritableSQLObject):
|
|
23
|
+
name = StringCol()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Monarchy(Government):
|
|
27
|
+
monarch = StringCol()
|
|
28
|
+
versions = Versioning()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class VChild(Base):
|
|
32
|
+
weapon = StringCol()
|
|
33
|
+
versions = Versioning()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class HasForeign(SQLObject):
|
|
37
|
+
foreign = ForeignKey("Base")
|
|
38
|
+
versions = Versioning()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _set_extra():
|
|
42
|
+
return "read all about it"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Extra(SQLObject):
|
|
46
|
+
name = StringCol()
|
|
47
|
+
versions = Versioning(
|
|
48
|
+
extraCols={'extra': StringCol(default=_set_extra())})
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class HasAltId(SQLObject):
|
|
52
|
+
name = StringCol()
|
|
53
|
+
altid = IntCol(alternateID=True)
|
|
54
|
+
versions = Versioning()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def setup():
|
|
58
|
+
classes = [MyClass, Base, Child, Government,
|
|
59
|
+
Monarchy, VChild, Extra, HasAltId]
|
|
60
|
+
if hasattr(HasForeign, "_connection"):
|
|
61
|
+
classes.insert(0, HasForeign)
|
|
62
|
+
else:
|
|
63
|
+
classes.append(HasForeign)
|
|
64
|
+
for cls in classes:
|
|
65
|
+
if hasattr(cls, 'versions') and getattr(cls, "_connection", None) and \
|
|
66
|
+
cls._connection.tableExists(cls.sqlmeta.table):
|
|
67
|
+
setupClass(cls.versions.versionClass)
|
|
68
|
+
setupClass(cls)
|
|
69
|
+
if hasattr(cls, 'versions'):
|
|
70
|
+
setupClass(cls.versions.versionClass)
|
|
71
|
+
for version in cls.versions.versionClass.select():
|
|
72
|
+
version.destroySelf()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_versioning():
|
|
76
|
+
# the simple case
|
|
77
|
+
setup()
|
|
78
|
+
mc = MyClass(name='fleem')
|
|
79
|
+
mc.set(name='morx')
|
|
80
|
+
assert len(list(mc.versions)) == 1
|
|
81
|
+
assert mc.versions[0].name == "fleem"
|
|
82
|
+
|
|
83
|
+
assert len(list(MyClass.select())) == 1
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_inheritable_versioning():
|
|
87
|
+
setup()
|
|
88
|
+
|
|
89
|
+
# base versioned, child unversioned
|
|
90
|
+
base = Base(name='fleem')
|
|
91
|
+
base.set(name='morx')
|
|
92
|
+
assert len(list(base.versions)) == 1
|
|
93
|
+
assert base.versions[0].name == "fleem"
|
|
94
|
+
assert len(list(Base.select())) == 1
|
|
95
|
+
|
|
96
|
+
child = Child(name='child', toy='nintendo')
|
|
97
|
+
child.set(name='teenager', toy='guitar')
|
|
98
|
+
assert len(list(child.versions)) == 0
|
|
99
|
+
|
|
100
|
+
# child versioned, base unversioned
|
|
101
|
+
government = Government(name='canada')
|
|
102
|
+
assert not hasattr(government, 'versions')
|
|
103
|
+
|
|
104
|
+
monarchy = Monarchy(name='UK', monarch='king george iv')
|
|
105
|
+
assert len(list(monarchy.versions)) == 0
|
|
106
|
+
monarchy.set(name='queen elisabeth ii')
|
|
107
|
+
assert len(list(monarchy.versions)) == 1
|
|
108
|
+
assert monarchy.versions[0].name == "UK"
|
|
109
|
+
assert len(list(Monarchy.select())) == 1
|
|
110
|
+
|
|
111
|
+
# both parent and child versioned
|
|
112
|
+
num_base_versions = len(list(base.versions))
|
|
113
|
+
vchild = VChild(name='kid', weapon='slingshot')
|
|
114
|
+
vchild.set(name='toon', weapon='dynamite')
|
|
115
|
+
assert len(list(base.versions)) == num_base_versions
|
|
116
|
+
assert len(list(vchild.versions)) == 1
|
|
117
|
+
# test setting using setattr directly rather than .set
|
|
118
|
+
vchild.name = "newname"
|
|
119
|
+
assert len(list(vchild.versions)) == 2
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_restore():
|
|
123
|
+
setup()
|
|
124
|
+
base = Base(name='fleem')
|
|
125
|
+
base.set(name='morx')
|
|
126
|
+
assert base.name == "morx"
|
|
127
|
+
base.versions[0].restore()
|
|
128
|
+
assert base.name == "fleem"
|
|
129
|
+
|
|
130
|
+
monarchy = Monarchy(name='USA', monarch='Emperor Norton I')
|
|
131
|
+
monarchy.set(name='morx')
|
|
132
|
+
assert monarchy.name == "morx"
|
|
133
|
+
monarchy.versions[0].restore()
|
|
134
|
+
assert monarchy.name == "USA"
|
|
135
|
+
assert monarchy.monarch == "Emperor Norton I"
|
|
136
|
+
|
|
137
|
+
extra = Extra(name='fleem')
|
|
138
|
+
extra.set(name='morx')
|
|
139
|
+
assert extra.name == "morx"
|
|
140
|
+
extra.versions[0].restore()
|
|
141
|
+
assert extra.name == "fleem"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_next():
|
|
145
|
+
setup()
|
|
146
|
+
base = Base(name='first', so_value=1)
|
|
147
|
+
base.set(name='second')
|
|
148
|
+
base.set(name='third', so_value=2)
|
|
149
|
+
version = base.versions[0]
|
|
150
|
+
assert version.nextVersion() == base.versions[1]
|
|
151
|
+
assert version.nextVersion().nextVersion() == base
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def test_get_changed():
|
|
155
|
+
setup()
|
|
156
|
+
base = Base(name='first', so_value=1)
|
|
157
|
+
base.set(name='second')
|
|
158
|
+
base.set(name='third', so_value=2)
|
|
159
|
+
assert base.versions[0].getChangedFields() == ['Name']
|
|
160
|
+
assert sorted(base.versions[1].getChangedFields()) == ['Name', 'So_Value']
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_foreign_keys():
|
|
164
|
+
setup()
|
|
165
|
+
base1 = Base(name='first', so_value=1)
|
|
166
|
+
base2 = Base(name='first', so_value=1)
|
|
167
|
+
has_foreign = HasForeign(foreign=base1)
|
|
168
|
+
has_foreign.foreign = base2
|
|
169
|
+
assert has_foreign.versions[0].foreign == base1
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_extra():
|
|
173
|
+
setup()
|
|
174
|
+
extra = Extra(name='title')
|
|
175
|
+
extra.name = 'new'
|
|
176
|
+
assert extra.versions[0].extra == 'read all about it'
|
|
177
|
+
assert sorted(extra.versions[0].getChangedFields()) == ['Name']
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_altid():
|
|
181
|
+
setup()
|
|
182
|
+
extra = HasAltId(name="fleem", altid=5)
|
|
183
|
+
extra.name = "morx"
|
sqlobject/views.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from .main import SQLObject
|
|
2
|
+
from .sqlbuilder import AND, Alias, ColumnAS, LEFTJOINOn, \
|
|
3
|
+
NoDefault, SQLCall, SQLConstant, SQLObjectField, SQLObjectTable, SQLOp, \
|
|
4
|
+
Select, sqlrepr
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ViewSQLObjectField(SQLObjectField):
|
|
8
|
+
def __init__(self, alias, *arg):
|
|
9
|
+
SQLObjectField.__init__(self, *arg)
|
|
10
|
+
self.alias = alias
|
|
11
|
+
|
|
12
|
+
def __sqlrepr__(self, db):
|
|
13
|
+
return self.alias + "." + self.fieldName
|
|
14
|
+
|
|
15
|
+
def tablesUsedImmediate(self):
|
|
16
|
+
return [self.tableName]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ViewSQLObjectTable(SQLObjectTable):
|
|
20
|
+
FieldClass = ViewSQLObjectField
|
|
21
|
+
|
|
22
|
+
def __getattr__(self, attr):
|
|
23
|
+
if attr == 'sqlmeta':
|
|
24
|
+
raise AttributeError
|
|
25
|
+
return SQLObjectTable.__getattr__(self, attr)
|
|
26
|
+
|
|
27
|
+
def _getattrFromID(self, attr):
|
|
28
|
+
return self.FieldClass(self.soClass.sqlmeta.alias, self.tableName,
|
|
29
|
+
'id', attr, self.soClass, None)
|
|
30
|
+
|
|
31
|
+
def _getattrFromColumn(self, column, attr):
|
|
32
|
+
return self.FieldClass(self.soClass.sqlmeta.alias, self.tableName,
|
|
33
|
+
column.name, attr, self.soClass, column)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ViewSQLObject(SQLObject):
|
|
37
|
+
"""
|
|
38
|
+
A SQLObject class that derives all it's values from other SQLObject
|
|
39
|
+
classes. Columns on subclasses should use SQLBuilder constructs for dbName,
|
|
40
|
+
and sqlmeta should specify:
|
|
41
|
+
|
|
42
|
+
* idName as a SQLBuilder construction
|
|
43
|
+
* clause as SQLBuilder clause for specifying join conditions
|
|
44
|
+
or other restrictions
|
|
45
|
+
* table as an optional alternate name for the class alias
|
|
46
|
+
|
|
47
|
+
See test_views.py for simple examples.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __classinit__(cls, new_attrs):
|
|
51
|
+
SQLObject.__classinit__(cls, new_attrs)
|
|
52
|
+
# like is_base
|
|
53
|
+
if cls.__name__ != 'ViewSQLObject':
|
|
54
|
+
dbName = hasattr(cls, '_connection') and \
|
|
55
|
+
(cls._connection and cls._connection.dbName) or None
|
|
56
|
+
|
|
57
|
+
if getattr(cls.sqlmeta, 'table', None):
|
|
58
|
+
cls.sqlmeta.alias = cls.sqlmeta.table
|
|
59
|
+
else:
|
|
60
|
+
cls.sqlmeta.alias = \
|
|
61
|
+
cls.sqlmeta.style.pythonClassToDBTable(cls.__name__)
|
|
62
|
+
alias = cls.sqlmeta.alias
|
|
63
|
+
columns = [ColumnAS(cls.sqlmeta.idName, 'id')]
|
|
64
|
+
# {sqlrepr-key: [restriction, *aggregate-column]}
|
|
65
|
+
aggregates = {'': [None]}
|
|
66
|
+
inverseColumns = dict(
|
|
67
|
+
[(y, x) for x, y in cls.sqlmeta.columns.items()])
|
|
68
|
+
for col in cls.sqlmeta.columnList:
|
|
69
|
+
n = inverseColumns[col]
|
|
70
|
+
ascol = ColumnAS(col.dbName, n)
|
|
71
|
+
if isAggregate(col.dbName):
|
|
72
|
+
restriction = getattr(col, 'aggregateClause', None)
|
|
73
|
+
if restriction:
|
|
74
|
+
restrictkey = sqlrepr(restriction, dbName)
|
|
75
|
+
aggregates[restrictkey] = \
|
|
76
|
+
aggregates.get(restrictkey, [restriction]) + \
|
|
77
|
+
[ascol]
|
|
78
|
+
else:
|
|
79
|
+
aggregates[''].append(ascol)
|
|
80
|
+
else:
|
|
81
|
+
columns.append(ascol)
|
|
82
|
+
|
|
83
|
+
metajoin = getattr(cls.sqlmeta, 'join', NoDefault)
|
|
84
|
+
clause = getattr(cls.sqlmeta, 'clause', NoDefault)
|
|
85
|
+
select = Select(columns,
|
|
86
|
+
distinct=True,
|
|
87
|
+
# @@ LDO check if this really mattered
|
|
88
|
+
# for performance
|
|
89
|
+
# @@ Postgres (and MySQL?) extension!
|
|
90
|
+
# distinctOn=cls.sqlmeta.idName,
|
|
91
|
+
join=metajoin,
|
|
92
|
+
clause=clause)
|
|
93
|
+
|
|
94
|
+
aggregates = aggregates.values()
|
|
95
|
+
|
|
96
|
+
if aggregates != [[None]]:
|
|
97
|
+
join = []
|
|
98
|
+
last_alias = "%s_base" % alias
|
|
99
|
+
last_id = "id"
|
|
100
|
+
last = Alias(select, last_alias)
|
|
101
|
+
columns = [
|
|
102
|
+
ColumnAS(SQLConstant("%s.%s" % (last_alias, x.expr2)),
|
|
103
|
+
x.expr2) for x in columns]
|
|
104
|
+
|
|
105
|
+
for i, agg in enumerate(aggregates):
|
|
106
|
+
restriction = agg[0]
|
|
107
|
+
if restriction is None:
|
|
108
|
+
restriction = clause
|
|
109
|
+
else:
|
|
110
|
+
restriction = AND(clause, restriction)
|
|
111
|
+
agg = agg[1:]
|
|
112
|
+
agg_alias = "%s_%s" % (alias, i)
|
|
113
|
+
agg_id = '%s_id' % agg_alias
|
|
114
|
+
if not last.q.alias.endswith('base'):
|
|
115
|
+
last = None
|
|
116
|
+
new_alias = Alias(Select(
|
|
117
|
+
[ColumnAS(cls.sqlmeta.idName, agg_id)] + agg,
|
|
118
|
+
groupBy=cls.sqlmeta.idName,
|
|
119
|
+
join=metajoin,
|
|
120
|
+
clause=restriction),
|
|
121
|
+
agg_alias)
|
|
122
|
+
agg_join = LEFTJOINOn(last, new_alias,
|
|
123
|
+
"%s.%s = %s.%s" % (
|
|
124
|
+
last_alias, last_id,
|
|
125
|
+
agg_alias, agg_id))
|
|
126
|
+
|
|
127
|
+
join.append(agg_join)
|
|
128
|
+
for col in agg:
|
|
129
|
+
columns.append(
|
|
130
|
+
ColumnAS(SQLConstant(
|
|
131
|
+
"%s.%s" % (agg_alias, col.expr2)),
|
|
132
|
+
col.expr2))
|
|
133
|
+
|
|
134
|
+
last = new_alias
|
|
135
|
+
last_alias = agg_alias
|
|
136
|
+
last_id = agg_id
|
|
137
|
+
select = Select(columns,
|
|
138
|
+
join=join)
|
|
139
|
+
|
|
140
|
+
cls.sqlmeta.table = Alias(select, alias)
|
|
141
|
+
cls.q = ViewSQLObjectTable(cls)
|
|
142
|
+
for n, col in cls.sqlmeta.columns.items():
|
|
143
|
+
col.dbName = n
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def isAggregate(expr):
|
|
147
|
+
if isinstance(expr, SQLCall):
|
|
148
|
+
return True
|
|
149
|
+
if isinstance(expr, SQLOp):
|
|
150
|
+
return isAggregate(expr.expr1) or isAggregate(expr.expr2)
|
|
151
|
+
return False
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import sqlobject
|
|
3
|
+
from sqlobject.compat import string_type
|
|
4
|
+
|
|
5
|
+
# The module was imported during documentation building
|
|
6
|
+
if 'sphinx' not in sys.modules:
|
|
7
|
+
from paste.deploy.converters import asbool
|
|
8
|
+
from paste.wsgilib import catch_errors
|
|
9
|
+
from paste.util import import_string
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def make_middleware(app, global_conf, database=None, use_transaction=False,
|
|
13
|
+
hub=None):
|
|
14
|
+
"""
|
|
15
|
+
WSGI middleware that sets the connection for the request (using
|
|
16
|
+
the database URI or connection object) and the given hub (or
|
|
17
|
+
``sqlobject.sqlhub`` if not given).
|
|
18
|
+
|
|
19
|
+
If ``use_transaction`` is true, then the request will be run in a
|
|
20
|
+
transaction.
|
|
21
|
+
|
|
22
|
+
Applications can use the keys (which are all no-argument functions):
|
|
23
|
+
|
|
24
|
+
``sqlobject.get_connection()``:
|
|
25
|
+
Returns the connection object
|
|
26
|
+
|
|
27
|
+
``sqlobject.abort()``:
|
|
28
|
+
Aborts the transaction. Does not raise an error, but at the *end*
|
|
29
|
+
of the request there will be a rollback.
|
|
30
|
+
|
|
31
|
+
``sqlobject.begin()``:
|
|
32
|
+
Starts a transaction. First commits (or rolls back if aborted) if
|
|
33
|
+
this is run in a transaction.
|
|
34
|
+
|
|
35
|
+
``sqlobject.in_transaction()``:
|
|
36
|
+
Returns true or false, depending if we are currently in a
|
|
37
|
+
transaction.
|
|
38
|
+
"""
|
|
39
|
+
use_transaction = asbool(use_transaction)
|
|
40
|
+
if database is None:
|
|
41
|
+
database = global_conf.get('database')
|
|
42
|
+
if not database:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
"You must provide a 'database' configuration value")
|
|
45
|
+
if isinstance(hub, string_type):
|
|
46
|
+
hub = import_string.eval_import(hub)
|
|
47
|
+
if not hub:
|
|
48
|
+
hub = sqlobject.sqlhub
|
|
49
|
+
if isinstance(database, string_type):
|
|
50
|
+
database = sqlobject.connectionForURI(database)
|
|
51
|
+
return SQLObjectMiddleware(app, database, use_transaction, hub)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SQLObjectMiddleware(object):
|
|
55
|
+
|
|
56
|
+
def __init__(self, app, conn, use_transaction, hub):
|
|
57
|
+
self.app = app
|
|
58
|
+
self.conn = conn
|
|
59
|
+
self.use_transaction = use_transaction
|
|
60
|
+
self.hub = hub
|
|
61
|
+
|
|
62
|
+
def __call__(self, environ, start_response):
|
|
63
|
+
conn = [self.conn]
|
|
64
|
+
if self.use_transaction:
|
|
65
|
+
conn[0] = conn[0].transaction()
|
|
66
|
+
any_errors = []
|
|
67
|
+
use_transaction = [self.use_transaction]
|
|
68
|
+
self.hub.threadConnection = conn[0]
|
|
69
|
+
|
|
70
|
+
def abort():
|
|
71
|
+
assert use_transaction[0], (
|
|
72
|
+
"You cannot abort, because a transaction is not being used")
|
|
73
|
+
any_errors.append(None)
|
|
74
|
+
|
|
75
|
+
def begin():
|
|
76
|
+
if use_transaction[0]:
|
|
77
|
+
if any_errors:
|
|
78
|
+
conn[0].rollback()
|
|
79
|
+
else:
|
|
80
|
+
conn[0].commit()
|
|
81
|
+
any_errors[:] = []
|
|
82
|
+
use_transaction[0] = True
|
|
83
|
+
conn[0] = self.conn.transaction()
|
|
84
|
+
self.hub.threadConnection = conn[0]
|
|
85
|
+
|
|
86
|
+
def error(exc_info=None):
|
|
87
|
+
any_errors.append(None)
|
|
88
|
+
ok()
|
|
89
|
+
|
|
90
|
+
def ok():
|
|
91
|
+
if use_transaction[0]:
|
|
92
|
+
if any_errors:
|
|
93
|
+
conn[0].rollback()
|
|
94
|
+
else:
|
|
95
|
+
conn[0].commit(close=True)
|
|
96
|
+
self.hub.threadConnection = None
|
|
97
|
+
|
|
98
|
+
def in_transaction():
|
|
99
|
+
return use_transaction[0]
|
|
100
|
+
|
|
101
|
+
def get_connection():
|
|
102
|
+
return conn[0]
|
|
103
|
+
|
|
104
|
+
environ['sqlobject.get_connection'] = get_connection
|
|
105
|
+
environ['sqlobject.abort'] = abort
|
|
106
|
+
environ['sqlobject.begin'] = begin
|
|
107
|
+
environ['sqlobject.in_transaction'] = in_transaction
|
|
108
|
+
return catch_errors(self.app, environ, start_response,
|
|
109
|
+
error_callback=error, ok_callback=ok)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from __future__ import print_function
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
import pkg_resources
|
|
8
|
+
pkg_resources.require('SQLObject>0.6.1')
|
|
9
|
+
except (ImportError, pkg_resources.DistributionNotFound):
|
|
10
|
+
# Oh well, we tried...
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import sqlobject.manager
|
|
15
|
+
except ImportError:
|
|
16
|
+
try:
|
|
17
|
+
here = __file__
|
|
18
|
+
except NameError:
|
|
19
|
+
here = sys.argv[0]
|
|
20
|
+
updir = os.path.join(
|
|
21
|
+
os.path.dirname(os.path.dirname(os.path.abspath(here))),
|
|
22
|
+
'sqlobject')
|
|
23
|
+
if os.path.exists(updir):
|
|
24
|
+
sys.path.insert(0, os.path.dirname(updir))
|
|
25
|
+
else:
|
|
26
|
+
print('I cannot find the sqlobject module')
|
|
27
|
+
print('If SQLObject is installed, you may need to set $PYTHONPATH')
|
|
28
|
+
sys.exit(3)
|
|
29
|
+
# Now we have to get rid of possibly stale modules from that import
|
|
30
|
+
# up there
|
|
31
|
+
for name, value in sys.modules.items():
|
|
32
|
+
if name.startswith('sqlobject'):
|
|
33
|
+
del sys.modules[name]
|
|
34
|
+
|
|
35
|
+
from sqlobject.manager import command
|
|
36
|
+
command.the_runner.run(sys.argv)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from __future__ import print_function
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
uri = sys.argv[1]
|
|
8
|
+
except IndexError:
|
|
9
|
+
sys.exit("Usage: %s old-style-URI" % sys.argv[0])
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import pkg_resources
|
|
13
|
+
pkg_resources.require('SQLObject>=1.0.0')
|
|
14
|
+
except (ImportError, pkg_resources.DistributionNotFound):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
from sqlobject import connectionForURI
|
|
18
|
+
conn = connectionForURI(uri, oldUri=True)
|
|
19
|
+
print(conn.uri())
|