sqlobject-cvs Mailing List for SQLObject (Page 169)
SQLObject is a Python ORM.
Brought to you by:
ianbicking,
phd
You can subscribe to this list here.
| 2003 |
Jan
|
Feb
|
Mar
(9) |
Apr
(74) |
May
(29) |
Jun
(16) |
Jul
(28) |
Aug
(10) |
Sep
(57) |
Oct
(9) |
Nov
(29) |
Dec
(12) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2004 |
Jan
(7) |
Feb
(14) |
Mar
(6) |
Apr
(3) |
May
(12) |
Jun
(34) |
Jul
(9) |
Aug
(29) |
Sep
(22) |
Oct
(2) |
Nov
(15) |
Dec
(52) |
| 2005 |
Jan
(47) |
Feb
(78) |
Mar
(14) |
Apr
(35) |
May
(33) |
Jun
(16) |
Jul
(26) |
Aug
(63) |
Sep
(40) |
Oct
(96) |
Nov
(96) |
Dec
(123) |
| 2006 |
Jan
(159) |
Feb
(144) |
Mar
(64) |
Apr
(31) |
May
(88) |
Jun
(48) |
Jul
(16) |
Aug
(64) |
Sep
(87) |
Oct
(92) |
Nov
(56) |
Dec
(76) |
| 2007 |
Jan
(94) |
Feb
(103) |
Mar
(126) |
Apr
(123) |
May
(85) |
Jun
(11) |
Jul
(130) |
Aug
(47) |
Sep
(65) |
Oct
(70) |
Nov
(12) |
Dec
(11) |
| 2008 |
Jan
(30) |
Feb
(55) |
Mar
(88) |
Apr
(20) |
May
(50) |
Jun
|
Jul
(38) |
Aug
(1) |
Sep
(9) |
Oct
(5) |
Nov
(6) |
Dec
(39) |
| 2009 |
Jan
(8) |
Feb
(16) |
Mar
(3) |
Apr
(33) |
May
(44) |
Jun
(1) |
Jul
(10) |
Aug
(33) |
Sep
(74) |
Oct
(22) |
Nov
|
Dec
(15) |
| 2010 |
Jan
(28) |
Feb
(22) |
Mar
(46) |
Apr
(29) |
May
(1) |
Jun
(1) |
Jul
(27) |
Aug
(8) |
Sep
(5) |
Oct
(33) |
Nov
(24) |
Dec
(41) |
| 2011 |
Jan
(4) |
Feb
(12) |
Mar
(35) |
Apr
(29) |
May
(19) |
Jun
(16) |
Jul
(32) |
Aug
(25) |
Sep
(5) |
Oct
(11) |
Nov
(21) |
Dec
(12) |
| 2012 |
Jan
(3) |
Feb
(4) |
Mar
(20) |
Apr
(4) |
May
(25) |
Jun
(13) |
Jul
|
Aug
|
Sep
(2) |
Oct
(25) |
Nov
(9) |
Dec
(1) |
| 2013 |
Jan
(6) |
Feb
(8) |
Mar
|
Apr
(10) |
May
(31) |
Jun
(7) |
Jul
(18) |
Aug
(33) |
Sep
(4) |
Oct
(16) |
Nov
|
Dec
(27) |
| 2014 |
Jan
(2) |
Feb
|
Mar
|
Apr
(11) |
May
(39) |
Jun
(8) |
Jul
(11) |
Aug
(4) |
Sep
|
Oct
(27) |
Nov
|
Dec
(71) |
| 2015 |
Jan
(17) |
Feb
(47) |
Mar
(33) |
Apr
|
May
|
Jun
(9) |
Jul
(7) |
Aug
|
Sep
|
Oct
|
Nov
|
Dec
(8) |
| 2016 |
Jan
(4) |
Feb
(4) |
Mar
|
Apr
|
May
(12) |
Jun
(7) |
Jul
(9) |
Aug
(31) |
Sep
(8) |
Oct
(3) |
Nov
(15) |
Dec
(1) |
| 2017 |
Jan
(13) |
Feb
(7) |
Mar
(14) |
Apr
(8) |
May
(10) |
Jun
(4) |
Jul
(2) |
Aug
(1) |
Sep
|
Oct
(8) |
Nov
(4) |
Dec
(5) |
| 2018 |
Jan
(2) |
Feb
(8) |
Mar
|
Apr
(4) |
May
|
Jun
(6) |
Jul
|
Aug
(1) |
Sep
|
Oct
|
Nov
(1) |
Dec
|
| 2019 |
Jan
(1) |
Feb
(16) |
Mar
(1) |
Apr
(3) |
May
(5) |
Jun
(1) |
Jul
|
Aug
|
Sep
(2) |
Oct
|
Nov
(1) |
Dec
(3) |
| 2020 |
Jan
|
Feb
|
Mar
|
Apr
(1) |
May
(1) |
Jun
|
Jul
|
Aug
(1) |
Sep
|
Oct
(2) |
Nov
|
Dec
(2) |
| 2021 |
Jan
|
Feb
(2) |
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
(1) |
Nov
(1) |
Dec
|
| 2022 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
(6) |
Oct
(1) |
Nov
(1) |
Dec
(4) |
| 2023 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
(3) |
Sep
(2) |
Oct
(2) |
Nov
(4) |
Dec
|
| 2024 |
Jan
|
Feb
(2) |
Mar
|
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
|
Sep
(1) |
Oct
|
Nov
|
Dec
(9) |
| 2025 |
Jan
|
Feb
(4) |
Mar
(2) |
Apr
|
May
|
Jun
|
Jul
|
Aug
(1) |
Sep
|
Oct
|
Nov
(2) |
Dec
(2) |
|
From: <sub...@co...> - 2005-02-21 12:32:59
|
Author: phd
Date: 2005-02-21 12:32:50 +0000 (Mon, 21 Feb 2005)
New Revision: 628
Modified:
trunk/SQLObject/sqlobject/main.py
Log:
Minor code cleanup - removed trailing spaces.
Added connection parameter to method .delete().
Modified: trunk/SQLObject/sqlobject/main.py
===================================================================
--- trunk/SQLObject/sqlobject/main.py 2005-02-20 09:33:10 UTC (rev 627)
+++ trunk/SQLObject/sqlobject/main.py 2005-02-21 12:32:50 UTC (rev 628)
@@ -337,9 +337,9 @@
# Magic values shouldn't be passed through:
del values[key]
cls.sqlmeta = type('sqlmeta', (superclass,), values)
-
+
cls.sqlmeta.setClass(cls)
-
+
implicitColumns = []
implicitJoins = []
implicitIndexes = []
@@ -358,7 +358,7 @@
value.setName(attr)
implicitIndexes.append(value)
delattr(cls, attr)
- continue
+ continue
if not is_base:
cls._cleanDeprecatedAttrs(new_attrs)
@@ -516,7 +516,7 @@
def get(cls, id, connection=None, selectResults=None):
assert id is not None, 'None is not a possible id for %s' % cls.__name
-
+
id = cls._idType(id)
if connection is None:
@@ -1291,8 +1291,8 @@
self._connection._SO_delete(self)
self._connection.cache.expire(self.id, self.__class__)
- def delete(cls, id):
- obj = cls.get(id)
+ def delete(cls, id, connection=None):
+ obj = cls.get(id, connection=connection)
obj.destroySelf()
delete = classmethod(delete)
|
|
From: <sub...@co...> - 2005-02-20 09:33:18
|
Author: ianb
Date: 2005-02-20 09:33:10 +0000 (Sun, 20 Feb 2005)
New Revision: 627
Modified:
trunk/SQLObject/sqlobject/dbconnection.py
trunk/SQLObject/sqlobject/index.py
trunk/SQLObject/sqlobject/inheritance/iteration.py
trunk/SQLObject/sqlobject/main.py
trunk/SQLObject/sqlobject/sqlbuilder.py
trunk/SQLObject/sqlobject/sresults.py
trunk/SQLObject/sqlobject/tests/test_basic.py
trunk/SQLObject/sqlobject/tests/test_style.py
trunk/SQLObject/sqlobject/tests/test_style_old.py
Log:
Moved more attributes into sqlmeta, including attributes that are
per-class and private (like _SO_columns); these get set in
sqlmeta.setClass
Added a test for .set() usage (apparently I was only ever doing tests
for setting individual attributes).
Modified: trunk/SQLObject/sqlobject/dbconnection.py
===================================================================
--- trunk/SQLObject/sqlobject/dbconnection.py 2005-02-20 04:55:21 UTC (rev 626)
+++ trunk/SQLObject/sqlobject/dbconnection.py 2005-02-20 09:33:10 UTC (rev 627)
@@ -275,7 +275,7 @@
", ".join(select.tables))
else:
columns = ", ".join(["%s.%s" % (cls.sqlmeta.table, col.dbName)
- for col in cls._SO_columns])
+ for col in cls.sqlmeta._columns])
if columns:
q += "%s.%s, %s FROM %s WHERE " % \
(cls.sqlmeta.table, cls.sqlmeta.idName, columns,
@@ -310,8 +310,8 @@
else:
desc = False
assert sqlbuilder.sqlIdentifier(s), "Strings in clauses are expected to be column identifiers. I got: %r" % s
- if select.sourceClass._SO_columnDict.has_key(s):
- s = select.sourceClass._SO_columnDict[s].dbName
+ if select.sourceClass.sqlmeta._columnDict.has_key(s):
+ s = select.sourceClass.sqlmeta._columnDict[s].dbName
if desc:
return sqlbuilder.DESC(sqlbuilder.SQLConstant(s))
else:
@@ -365,7 +365,7 @@
def createColumns(self, soClass):
columnDefs = [self.createIDColumn(soClass)] \
+ [self.createColumn(soClass, col)
- for col in soClass._SO_columns]
+ for col in soClass.sqlmeta._columns]
return ",\n".join([" %s" % c for c in columnDefs])
def createColumn(self, soClass, col):
@@ -464,27 +464,12 @@
self.sqlrepr(secondValue)))
def _SO_columnClause(self, soClass, kw):
- return ' AND '.join(['%s = %s' %
- (soClass._SO_columnDict[key].dbName,
- self.sqlrepr(value))
- for key, value
- in kw.items()])
- terms = []
- for key, value in kw.items():
- if hasattr(value, '_SO_joinDict'): #handle an object value
- # find the joinColumn
- for join in value._SO_joinDict.values():
- if join.otherClass is soClass:
- dbName = join.joinColumn
- break
- else: #if nothing found
- raise TypeError, "Cannot selectBy(%s=%r)" % (key, value)
- value = value.id
- else:
- dbName = soClass._SO_columnDict[key].dbName
- term = '%s = %s' % (dbName, self.sqlrepr(value))
- terms.append(term)
- return ' AND '.join(terms)
+ return ' AND '.join(
+ ['%s = %s' %
+ (soClass.sqlmeta._columnDict[key].dbName,
+ self.sqlrepr(value))
+ for key, value
+ in kw.items()])
def sqlrepr(self, v):
return sqlrepr(v, self.dbName)
Modified: trunk/SQLObject/sqlobject/index.py
===================================================================
--- trunk/SQLObject/sqlobject/index.py 2005-02-20 04:55:21 UTC (rev 626)
+++ trunk/SQLObject/sqlobject/index.py 2005-02-20 09:33:10 UTC (rev 627)
@@ -37,7 +37,7 @@
columnName = desc['column']
if not isinstance(columnName, str):
columnName = columnName.name
- colDict = self.soClass._SO_columnDict
+ colDict = self.soClass.sqlmeta._columnDict
if not colDict.has_key(columnName):
for possible in colDict.values():
if possible.origName == columnName:
Modified: trunk/SQLObject/sqlobject/inheritance/iteration.py
===================================================================
--- trunk/SQLObject/sqlobject/inheritance/iteration.py 2005-02-20 04:55:21 UTC (rev 626)
+++ trunk/SQLObject/sqlobject/inheritance/iteration.py 2005-02-20 09:33:10 UTC (rev 627)
@@ -12,7 +12,7 @@
self._results = []
#phd: find the index of the childName column
childNameIdx = None
- columns = select.sourceClass._SO_columns
+ columns = select.sourceClass.sqlmeta._columns
for i in range(len(columns)): #phd: enumerate() is unavailable python 2.2
if columns[i].name == "childName":
childNameIdx = i
Modified: trunk/SQLObject/sqlobject/main.py
===================================================================
--- trunk/SQLObject/sqlobject/main.py 2005-02-20 04:55:21 UTC (rev 626)
+++ trunk/SQLObject/sqlobject/main.py 2005-02-20 09:33:10 UTC (rev 627)
@@ -127,7 +127,7 @@
def findDependantColumns(name, klass):
depends = []
- for col in klass._SO_columns:
+ for col in klass.sqlmeta._columns:
if col.foreignKey == name and col.cascade is not None:
depends.append(col)
return depends
@@ -159,8 +159,23 @@
__metaclass__ = declarative.DeclarativeMeta
# These attributes shouldn't be shared with superclasses:
- _unshared_attributes = ['table']
+ _unshared_attributes = ['table', 'idName']
+ # These are internal bookkeeping attributes; the class-level
+ # definition is a default for the instances, instances will
+ # reset these values.
+
+ # When an object is being created, it has an instance
+ # variable _creating, which is true. This way all the
+ # setters can be captured until the object is complete,
+ # and then the row is inserted into the database. Once
+ # that happens, _creating is deleted from the instance,
+ # and only the class variable (which is always false) is
+ # left.
+ _creating = False
+ _obsolete = False
+ _perConnection = False
+
def __classinit__(cls, new_attrs):
for attr in cls._unshared_attributes:
if not new_attrs.has_key(attr):
@@ -180,6 +195,30 @@
cls.table = cls.style.pythonClassToDBTable(cls.soClass.__name__)
if cls.idName is None:
cls.idName = cls.style.idForTable(cls.table)
+
+ # plainSetters are columns that haven't been overridden by the
+ # user, so we can contact the database directly to set them.
+ # Note that these can't set these in the SQLObject class
+ # itself, because they specific to this subclass of SQLObject,
+ # and cannot be shared among classes.
+ cls._plainSetters = {}
+ cls._plainGetters = {}
+ cls._plainForeignSetters = {}
+ cls._plainForeignGetters = {}
+ cls._plainJoinGetters = {}
+ cls._plainJoinAdders = {}
+ cls._plainJoinRemovers = {}
+
+ # This is a dictionary of columnName: columnObject
+ cls._columnDict = {}
+ cls._columns = []
+
+ # We keep track of the different joins by index,
+ # putting them in this list.
+ cls._joinList = []
+ cls._joinDict = {}
+ cls._indexList = []
+
setClass = classmethod(setClass)
class _sqlmeta_attr(object):
@@ -223,16 +262,6 @@
__metaclass__ = declarative.DeclarativeMeta
- # When an object is being created, it has an instance
- # variable _SO_creating, which is true. This way all the
- # setters can be captured until the object is complete,
- # and then the row is inserted into the database. Once
- # that happens, _SO_creating is deleted from the instance,
- # and only the class variable (which is always false) is
- # left.
- _SO_creating = False
- _SO_obsolete = False
-
# Sometimes an intance is attached to a connection, not
# globally available. In that case, self._SO_perConnection
# will be true. It's false by default:
@@ -417,23 +446,6 @@
if connection or not hasattr(cls, '_connection'):
cls.setConnection(connection)
- # plainSetters are columns that haven't been overridden by the
- # user, so we can contact the database directly to set them.
- # Note that these can't set these in the SQLObject class
- # itself, because they specific to this subclass of SQLObject,
- # and cannot be shared among classes.
- cls._SO_plainSetters = {}
- cls._SO_plainGetters = {}
- cls._SO_plainForeignSetters = {}
- cls._SO_plainForeignGetters = {}
- cls._SO_plainJoinGetters = {}
- cls._SO_plainJoinAdders = {}
- cls._SO_plainJoinRemovers = {}
-
- # This is a dictionary of columnName: columnObject
- cls._SO_columnDict = {}
- cls._SO_columns = []
-
# We have to check if there are columns in the inherited
# _columns where the attribute has been set to None in this
# class. If so, then we need to remove that column from
@@ -451,11 +463,6 @@
########################################
# Now we do the joins:
- # We keep track of the different joins by index,
- # putting them in this list.
- cls._SO_joinList = []
- cls._SO_joinDict = {}
-
for join in cls._joins:
cls.addJoin(join)
@@ -464,7 +471,6 @@
cls._SO_finishedClassCreation = True
makeProperties(cls)
- cls._SO_indexList = []
for idx in cls._indexes:
cls.addIndex(idx)
@@ -535,15 +541,15 @@
def addIndex(cls, indexDef):
index = indexDef.withClass(cls)
- cls._SO_indexList.append(index)
+ cls.sqlmeta._indexList.append(index)
addIndex = classmethod(addIndex)
def addColumn(cls, columnDef, changeSchema=False, connection=None):
column = columnDef.withClass(cls)
name = column.name
assert name != 'id', "The 'id' column is implicit, and should not be defined as a column"
- cls._SO_columnDict[name] = column
- cls._SO_columns.append(column)
+ cls.sqlmeta._columnDict[name] = column
+ cls.sqlmeta._columns.append(column)
if columnDef not in cls._columns:
cls._columns.append(columnDef)
@@ -572,7 +578,7 @@
# _SO_get_columnName definition.
if not hasattr(cls, getterName(name)) or (name == 'childName'):
setattr(cls, getterName(name), getter)
- cls._SO_plainGetters[name] = 1
+ cls.sqlmeta._plainGetters[name] = 1
#################################################
# Create the setter function(s)
@@ -593,7 +599,7 @@
# We keep track of setters that haven't been
# overridden, because we can combine these
# set columns into one SQL UPDATE query.
- cls._SO_plainSetters[name] = 1
+ cls.sqlmeta._plainSetters[name] = 1
##################################################
# Here we check if the column is a foreign key, in
@@ -623,7 +629,7 @@
# (sans ID ending)
if not hasattr(cls, getterName(name)[:-2]):
setattr(cls, getterName(name)[:-2], getter)
- cls._SO_plainForeignGetters[name[:-2]] = 1
+ cls.sqlmeta._plainForeignGetters[name[:-2]] = 1
if not column.immutable:
# The setter just gets the ID of the object,
@@ -632,7 +638,7 @@
setattr(cls, rawSetterName(name)[:-2], setter)
if not hasattr(cls, setterName(name)[:-2]):
setattr(cls, setterName(name)[:-2], setter)
- cls._SO_plainForeignSetters[name[:-2]] = 1
+ cls.sqlmeta._plainForeignSetters[name[:-2]] = 1
# We'll need to put in a real reference at
# some point. See needSet at the top of the
@@ -670,28 +676,28 @@
def delColumn(cls, column, changeSchema=False, connection=None):
if isinstance(column, str):
- column = cls._SO_columnDict[column]
+ column = cls.sqlmeta._columnDict[column]
if isinstance(column, col.Col):
- for c in cls._SO_columns:
+ for c in cls.sqlmeta._columns:
if column is c.columnDef:
column = c
break
- cls._SO_columns.remove(column)
+ cls.sqlmeta._columns.remove(column)
cls._columns.remove(column.columnDef)
name = column.name
- del cls._SO_columnDict[name]
+ del cls.sqlmeta._columnDict[name]
delattr(cls, rawGetterName(name))
- if cls._SO_plainGetters.has_key(name):
+ if cls.sqlmeta._plainGetters.has_key(name):
delattr(cls, getterName(name))
delattr(cls, rawSetterName(name))
- if cls._SO_plainSetters.has_key(name):
+ if cls.sqlmeta._plainSetters.has_key(name):
delattr(cls, setterName(name))
if column.foreignKey:
delattr(cls, rawGetterName(name)[:-2])
- if cls._SO_plainForeignGetters.has_key(name[:-2]):
+ if cls.sqlmeta._plainForeignGetters.has_key(name[:-2]):
delattr(cls, getterName(name)[:-2])
delattr(cls, rawSetterName(name)[:-2])
- if cls._SO_plainForeignSetters.has_key(name[:-2]):
+ if cls.sqlmeta._plainForeignSetters.has_key(name[:-2]):
delattr(cls, setterName(name)[:-2])
if column.alternateMethodName:
delattr(cls, column.alternateMethodName)
@@ -711,23 +717,23 @@
# join class.
join = joinDef.withClass(cls)
meth = join.joinMethodName
- cls._SO_joinDict[joinDef] = join
+ cls.sqlmeta._joinDict[joinDef] = join
- cls._SO_joinList.append(join)
- index = len(cls._SO_joinList)-1
+ cls.sqlmeta._joinList.append(join)
+ index = len(cls.sqlmeta._joinList)-1
if joinDef not in cls._joins:
cls._joins.append(joinDef)
# The function fetches the join by index, and
# then lets the join object do the rest of the
# work:
- func = eval('lambda self: self._SO_joinList[%i].performJoin(self)' % index)
+ func = eval('lambda self: self.sqlmeta._joinList[%i].performJoin(self)' % index)
# And we do the standard _SO_get_... _get_... deal
setattr(cls, rawGetterName(meth), func)
if not hasattr(cls, getterName(meth)):
setattr(cls, getterName(meth), func)
- cls._SO_plainJoinGetters[meth] = 1
+ cls.sqlmeta._plainJoinGetters[meth] = 1
# Some joins allow you to remove objects from the
# join.
@@ -735,21 +741,21 @@
# Again, we let it do the remove, and we do the
# standard naming trick.
- func = eval('lambda self, obj: self._SO_joinList[%i].remove(self, obj)' % index)
+ func = eval('lambda self, obj: self.sqlmeta._joinList[%i].remove(self, obj)' % index)
setattr(cls, '_SO_remove' + join.addRemoveName, func)
if not hasattr(cls, 'remove' + join.addRemoveName):
setattr(cls, 'remove' + join.addRemoveName, func)
- cls._SO_plainJoinRemovers[meth] = 1
+ cls.sqlmeta._plainJoinRemovers[meth] = 1
# Some joins allow you to add objects.
if hasattr(join, 'add'):
# And again...
- func = eval('lambda self, obj: self._SO_joinList[%i].add(self, obj)' % (len(cls._SO_joinList)-1))
+ func = eval('lambda self, obj: self.sqlmeta._joinList[%i].add(self, obj)' % (len(cls.sqlmeta._joinList)-1))
setattr(cls, '_SO_add' + join.addRemoveName, func)
if not hasattr(cls, 'add' + join.addRemoveName):
setattr(cls, 'add' + join.addRemoveName, func)
- cls._SO_plainJoinAdders[meth] = 1
+ cls.sqlmeta._plainJoinAdders[meth] = 1
if cls._SO_finishedClassCreation:
makeProperties(cls)
@@ -757,25 +763,25 @@
addJoin = classmethod(addJoin)
def delJoin(cls, joinDef):
- join = cls._SO_joinDict[joinDef]
+ join = cls.sqlmeta._joinDict[joinDef]
meth = join.joinMethodName
cls._joins.remove(joinDef)
- del cls._SO_joinDict[joinDef]
- for i in range(len(cls._SO_joinList)):
- if cls._SO_joinList[i] is joinDef:
+ del cls.sqlmeta._joinDict[joinDef]
+ for i in range(len(cls.sqlmeta._joinList)):
+ if cls.sqlmeta._joinList[i] is joinDef:
# Have to leave None, because we refer to joins
# by index.
- cls._SO_joinList[i] = None
+ cls.sqlmeta._joinList[i] = None
delattr(cls, rawGetterName(meth))
- if cls._SO_plainJoinGetters.has_key(meth):
+ if cls.sqlmeta._plainJoinGetters.has_key(meth):
delattr(cls, getterName(meth))
if hasattr(join, 'remove'):
delattr(cls, '_SO_remove' + join.addRemovePrefix)
- if cls._SO_plainJoinRemovers.has_key(meth):
+ if cls.sqlmeta._plainJoinRemovers.has_key(meth):
delattr(cls, 'remove' + join.addRemovePrefix)
if hasattr(join, 'add'):
delattr(cls, '_SO_add' + join.addRemovePrefix)
- if cls._SO_plainJoinAdders.has_key(meth):
+ if cls.sqlmeta._plainJoinAdders.has_key(meth):
delattr(cls, 'add' + join.addRemovePrefix)
if cls._SO_finishedClassCreation:
@@ -806,7 +812,7 @@
self._SO_perConnection = True
if not selectResults:
- dbNames = [col.dbName for col in self._SO_columns]
+ dbNames = [col.dbName for col in self.sqlmeta._columns]
selectResults = self._connection._SO_selectOne(self, dbNames)
if not selectResults:
raise SQLObjectNotFound, "The object %s by the ID %s does not exist" % (self.__class__.__name__, self.id)
@@ -833,7 +839,7 @@
else:
return result
self._expired = False
- dbNames = [col.dbName for col in self._SO_columns]
+ dbNames = [col.dbName for col in self.sqlmeta._columns]
selectResults = self._connection._SO_selectOne(self, dbNames)
if not selectResults:
raise SQLObjectNotFound, "The object %s by the ID %s has been deleted" % (self.__class__.__name__, self.id)
@@ -848,7 +854,7 @@
self.syncUpdate()
self._SO_writeLock.acquire()
try:
- dbNames = [col.dbName for col in self._SO_columns]
+ dbNames = [col.dbName for col in self.sqlmeta._columns]
selectResults = self._connection._SO_selectOne(self, dbNames)
if not selectResults:
raise SQLObjectNotFound, "The object %s by the ID %s has been deleted" % (self.__class__.__name__, self.id)
@@ -862,8 +868,9 @@
return
self._SO_writeLock.acquire()
try:
- if self._SO_columnDict:
- values = [(self._SO_columnDict[v[0]].dbName, v[1]) for v in self._SO_createValues.items()]
+ if self.sqlmeta._columnDict:
+ values = [(self.sqlmeta._columnDict[v[0]].dbName, v[1])
+ for v in self._SO_createValues.items()]
self._connection._SO_update(self, values)
self.dirty = False
self._SO_createValues = {}
@@ -877,7 +884,7 @@
try:
if self._expired:
return
- for column in self._SO_columns:
+ for column in self.sqlmeta._columns:
delattr(self, instanceName(column.name))
self._expired = True
self._connection.cache.expire(self.id, self.__class__)
@@ -888,7 +895,7 @@
# This is the place where we actually update the
# database.
- # If we are _SO_creating, the object doesn't yet exist
+ # If we are _creating, the object doesn't yet exist
# in the database, and we can't insert it until all
# the parts are set. So we just keep them in a
# dictionary until later:
@@ -898,15 +905,15 @@
dbValue = value
if toPython:
value = toPython(dbValue, self._SO_validatorState)
- if self._SO_creating or self.sqlmeta.lazyUpdate:
+ if self.sqlmeta._creating or self.sqlmeta.lazyUpdate:
self.dirty = True
self._SO_createValues[name] = dbValue
setattr(self, instanceName(name), value)
return
- self._connection._SO_update(self,
- [(self._SO_columnDict[name].dbName,
- dbValue)])
+ self._connection._SO_update(
+ self, [(self.sqlmeta._columnDict[name].dbName,
+ dbValue)])
if self.sqlmeta.cacheValues:
setattr(self, instanceName(name), value)
@@ -918,15 +925,15 @@
# Filter out items that don't map to column names.
# Those will be set directly on the object using
# setattr(obj, name, value).
- is_column = self._SO_plainSetters.has_key
+ is_column = self.sqlmeta._plainSetters.has_key
f_is_column = lambda item: is_column(item[0])
f_not_column = lambda item: not is_column(item[0])
items = kw.items()
extra = dict(filter(f_not_column, items))
kw = dict(filter(f_is_column, items))
- # _SO_creating is special, see _SO_setValue
- if self._SO_creating or self.sqlmeta.lazyUpdate:
+ # _creating is special, see _SO_setValue
+ if self.sqlmeta._creating or self.sqlmeta.lazyUpdate:
for name, value in kw.items():
fromPython = getattr(self, '_SO_fromPython_%s' % name, None)
if fromPython:
@@ -941,7 +948,7 @@
try:
getattr(self.__class__, name)
except AttributeError:
- if name not in self._SO_columnDict:
+ if name not in self.sqlmeta._columnDict:
raise TypeError, "%s.set() got an unexpected keyword argument %s" % (self.__class__.__name__, name)
try:
setattr(self, name, value)
@@ -973,14 +980,14 @@
toPython = getattr(self, '_SO_toPython_%s' % name, None)
if toPython:
value = toPython(dbValue, self._SO_validatorState)
- if self._cacheValues:
+ if self.sqlmeta.cacheValues:
setattr(self, instanceName(name), value)
toUpdate[name] = dbValue
for name, value in extra.items():
try:
getattr(self.__class__, name)
except AttributeError:
- if name not in self._SO_columnDict:
+ if name not in self.sqlmeta._columnDict:
raise TypeError, "%s.set() got an unexpected keyword argument %s" % (self.__class__.__name__, name)
try:
setattr(self, name, value)
@@ -988,25 +995,26 @@
raise AttributeError, '%s (with attribute %r)' % (e, name)
if toUpdate:
- args = [(self._SO_columnDict[name].dbName, value)
+ args = [(self.sqlmeta._columnDict[name].dbName, value)
for name, value in toUpdate.items()]
self._connection._SO_update(self, args)
finally:
self._SO_writeLock.release()
def _SO_selectInit(self, row):
- for col, colValue in zip(self._SO_columns, row):
+ for col, colValue in zip(self.sqlmeta._columns, row):
if col.toPython:
colValue = col.toPython(colValue, self._SO_validatorState)
setattr(self, instanceName(col.name), colValue)
def _SO_getValue(self, name):
# Retrieves a single value from the database. Simple.
- assert not self._SO_obsolete, "%s with id %s has become obsolete" \
- % (self.__class__.__name__, self.id)
+ assert not self.sqlmeta._obsolete, (
+ "%s with id %s has become obsolete" \
+ % (self.__class__.__name__, self.id))
# @@: do we really need this lock?
#self._SO_writeLock.acquire()
- column = self._SO_columnDict[name]
+ column = self.sqlmeta._columnDict[name]
results = self._connection._SO_selectOne(self, [column.dbName])
#self._SO_writeLock.release()
assert results != None, "%s with id %s is not in the database" \
@@ -1050,13 +1058,13 @@
def _create(self, id, **kw):
- self._SO_creating = True
+ self.sqlmeta._creating = True
self._SO_createValues = {}
self._SO_validatorState = SQLObjectState(self)
# First we do a little fix-up on the keywords we were
# passed:
- for column in self._SO_columns:
+ for column in self.sqlmeta._columns:
# If a foreign key is given, we get the ID of the object
# and put that in instead
@@ -1087,7 +1095,7 @@
# to be set, but were delayed until now:
setters = self._SO_createValues.items()
# Here's their database names:
- names = [self._SO_columnDict[v[0]].dbName for v in setters]
+ names = [self.sqlmeta._columnDict[v[0]].dbName for v in setters]
values = [v[1] for v in setters]
# Get rid of _SO_create*, we aren't creating anymore.
# Doesn't have to be threadsafe because we're still in
@@ -1097,7 +1105,7 @@
del self._SO_createValues
else:
self._SO_createValues = {}
- del self._SO_creating
+ del self.sqlmeta._creating
# Do the insert -- most of the SQL in this case is left
# up to DBConnection, since getting a new ID is
@@ -1115,7 +1123,7 @@
result = (connection or cls._connection)._SO_selectOneAlt(
cls,
[cls.sqlmeta.idName] +
- [col.dbName for col in cls._SO_columns],
+ [col.dbName for col in cls.sqlmeta._columns],
dbIDName,
value)
if not result:
@@ -1212,7 +1220,7 @@
def createIndexes(cls, ifNotExists=False, connection=None):
conn = connection or cls._connection
- for index in cls._SO_indexList:
+ for index in cls.sqlmeta._indexList:
if not index:
continue
conn._SO_createIndex(cls, index)
@@ -1220,7 +1228,7 @@
def _getJoinsToCreate(cls):
joins = []
- for join in cls._SO_joinList:
+ for join in cls.sqlmeta._joinList:
if not join:
continue
if not join.hasIntermediateTable():
@@ -1233,7 +1241,7 @@
def dropJoinTables(cls, ifExists=False, connection=None):
conn = connection or cls._connection
- for join in cls._SO_joinList:
+ for join in cls.sqlmeta._joinList:
if not join:
continue
if not join.hasIntermediateTable():
@@ -1279,7 +1287,7 @@
(klass.__name__, self.id, k.__name__))
for row in results:
row.destroySelf()
- self._SO_obsolete = True
+ self.sqlmeta._obsolete = True
self._connection._SO_delete(self)
self._connection.cache.expire(self.id, self.__class__)
@@ -1310,7 +1318,7 @@
def _reprItems(self):
items = []
- for col in self._SO_columns:
+ for col in self.sqlmeta._columns:
value = getattr(self, col.name)
r = repr(value)
if len(r) > 20:
Modified: trunk/SQLObject/sqlobject/sqlbuilder.py
===================================================================
--- trunk/SQLObject/sqlobject/sqlbuilder.py 2005-02-20 04:55:21 UTC (rev 626)
+++ trunk/SQLObject/sqlobject/sqlbuilder.py 2005-02-20 09:33:10 UTC (rev 627)
@@ -341,7 +341,7 @@
return SQLObjectField(self.tableName, self.soClass.sqlmeta.idName, attr)
else:
return SQLObjectField(self.tableName,
- self.soClass._SO_columnDict[attr].dbName,
+ self.soClass.sqlmeta._columnDict[attr].dbName,
attr)
class Field(SQLExpression):
Modified: trunk/SQLObject/sqlobject/sresults.py
===================================================================
--- trunk/SQLObject/sqlobject/sresults.py 2005-02-20 04:55:21 UTC (rev 626)
+++ trunk/SQLObject/sqlobject/sresults.py 2005-02-20 09:33:10 UTC (rev 627)
@@ -36,8 +36,8 @@
else:
desc = False
if isinstance(orderBy, (str, unicode)):
- if self.sourceClass._SO_columnDict.has_key(orderBy):
- val = self.sourceClass._SO_columnDict[orderBy].dbName
+ if self.sourceClass.sqlmeta._columnDict.has_key(orderBy):
+ val = self.sourceClass.sqlmeta._columnDict[orderBy].dbName
if desc:
return '-' + val
else:
Modified: trunk/SQLObject/sqlobject/tests/test_basic.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-20 04:55:21 UTC (rev 626)
+++ trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-20 09:33:10 UTC (rev 627)
@@ -44,6 +44,8 @@
assert bob.name == 'bob'
bob.name = 'joe'
assert bob.name == 'joe'
+ bob.set(name='joebob', passwd='testtest')
+ assert bob.name == 'joebob'
class TestSO2(SQLObject):
name = StringCol(length=50, dbName='name_col')
Modified: trunk/SQLObject/sqlobject/tests/test_style.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_style.py 2005-02-20 04:55:21 UTC (rev 626)
+++ trunk/SQLObject/sqlobject/tests/test_style.py 2005-02-20 09:33:10 UTC (rev 627)
@@ -25,5 +25,5 @@
st1 = SOStyleTest1(a='something', st2=None)
st2 = SOStyleTest2(b='whatever')
st1.st2 = st2
- assert st1._SO_columnDict['st2ID'].dbName == 'idst2'
+ assert st1.sqlmeta._columnDict['st2ID'].dbName == 'idst2'
assert st1.st2 == st2
Modified: trunk/SQLObject/sqlobject/tests/test_style_old.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_style_old.py 2005-02-20 04:55:21 UTC (rev 626)
+++ trunk/SQLObject/sqlobject/tests/test_style_old.py 2005-02-20 09:33:10 UTC (rev 627)
@@ -25,7 +25,7 @@
st1 = OldSOStyleTest1(a='something', st2=None)
st2 = OldSOStyleTest2(b='whatever')
st1.st2 = st2
- assert st1._SO_columnDict['st2ID'].dbName == 'idst2'
+ assert st1.sqlmeta._columnDict['st2ID'].dbName == 'idst2'
assert st1.st2 == st2
teardown_module()
|
|
From: <sub...@co...> - 2005-02-20 04:55:24
|
Author: ianb
Date: 2005-02-20 04:55:21 +0000 (Sun, 20 Feb 2005)
New Revision: 626
Modified:
trunk/SQLObject/sqlobject/inheritance/__init__.py
trunk/SQLObject/sqlobject/inheritance/iteration.py
trunk/SQLObject/sqlobject/joins.py
trunk/SQLObject/sqlobject/main.py
trunk/SQLObject/sqlobject/tests/test_auto.py
Log:
Moved _registry into sqlmeta
Modified: trunk/SQLObject/sqlobject/inheritance/__init__.py
===================================================================
--- trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-20 04:49:42 UTC (rev 625)
+++ trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-20 04:55:21 UTC (rev 626)
@@ -31,7 +31,8 @@
#DSM: and will do himself the relationship between classes.
if type(clause) is not str:
tableRegistry = {}
- allClasses = classregistry.registry(sourceClass._registry).allClasses()
+ allClasses = classregistry.registry(
+ sourceClass.sqlmeta.registry).allClasses()
for registryClass in allClasses:
if registryClass.sqlmeta.table in tablesDict:
#DSM: By default, no parents are needed for the clauses
Modified: trunk/SQLObject/sqlobject/inheritance/iteration.py
===================================================================
--- trunk/SQLObject/sqlobject/inheritance/iteration.py 2005-02-20 04:49:42 UTC (rev 625)
+++ trunk/SQLObject/sqlobject/inheritance/iteration.py 2005-02-20 04:55:21 UTC (rev 626)
@@ -64,7 +64,7 @@
dbconn = self.dbconn
rawconn = self.rawconn
cursor = rawconn.cursor()
- registry = self.select.sourceClass._registry
+ registry = self.select.sourceClass.sqlmeta.registry
for childName, ids in childIdsNames.items():
klass = findClass(childName, registry)
select = klass.select(sqlbuilder.IN(sqlbuilder.SQLConstant("id"), ids))
Modified: trunk/SQLObject/sqlobject/joins.py
===================================================================
--- trunk/SQLObject/sqlobject/joins.py 2005-02-20 04:49:42 UTC (rev 625)
+++ trunk/SQLObject/sqlobject/joins.py 2005-02-20 04:55:21 UTC (rev 626)
@@ -54,7 +54,7 @@
joinDef=None):
self.soClass = soClass
self.otherClassName = otherClass
- classregistry.registry(soClass._registry).addClassCallback(
+ classregistry.registry(soClass.sqlmeta.registry).addClassCallback(
otherClass, self._setOtherClass)
self.joinColumn = joinColumn
self.joinMethodName = joinMethodName
@@ -145,7 +145,8 @@
self.intermediateTable = intermediateTable
self.otherColumn = otherColumn
SOMultipleJoin.__init__(self, **kw)
- classregistry.registry(self.soClass._registry).addClassCallback(
+ classregistry.registry(
+ self.soClass.sqlmeta.registry).addClassCallback(
self.otherClassName, self._setOtherRelatedClass)
def _setOtherRelatedClass(self, otherClass):
Modified: trunk/SQLObject/sqlobject/main.py
===================================================================
--- trunk/SQLObject/sqlobject/main.py 2005-02-20 04:49:42 UTC (rev 625)
+++ trunk/SQLObject/sqlobject/main.py 2005-02-20 04:55:21 UTC (rev 626)
@@ -154,6 +154,7 @@
lazyUpdate = False
defaultOrder = None
cacheValues = True
+ registry = None
__metaclass__ = declarative.DeclarativeMeta
@@ -247,8 +248,6 @@
_fromDatabase = False
- _registry = None
-
# Default is false, but we set it to true for the *instance*
# when necessary: (bad clever? maybe)
_expired = False
@@ -484,7 +483,7 @@
setattr(cls.q, column.name,
getattr(currentClass.q, column.name))
- classregistry.registry(cls._registry).addClass(cls)
+ classregistry.registry(cls.sqlmeta.registry).addClass(cls)
_style = _sqlmeta_attr('style', 2)
_table = _sqlmeta_attr('table', 2)
@@ -492,10 +491,11 @@
_lazyUpdate = _sqlmeta_attr('lazyUpdate', 2)
_defaultOrder = _sqlmeta_attr('defaultOrder', 2)
_cacheValues = _sqlmeta_attr('cacheValues', 2)
+ _registry = _sqlmeta_attr('registry', 2)
def _cleanDeprecatedAttrs(cls, new_attrs):
for attr in ['_table', '_lazyUpdate', '_style', '_idName',
- '_defaultOrder', '_cacheValues']:
+ '_defaultOrder', '_cacheValues', '_registry']:
if new_attrs.has_key(attr):
new_name = attr[1:]
deprecated("'%s' is deprecated; please set the '%s' "
@@ -637,7 +637,7 @@
# We'll need to put in a real reference at
# some point. See needSet at the top of the
# file for more on this.
- classregistry.registry(cls._registry).addClassCallback(
+ classregistry.registry(cls.sqlmeta.registry).addClassCallback(
column.foreignKey,
lambda foreign, me, attr: setattr(me, attr, foreign),
cls, '_SO_class_%s' % column.foreignKey)
@@ -1134,7 +1134,7 @@
_SO_fetchAlternateID = classmethod(_SO_fetchAlternateID)
def _SO_depends(cls):
- return findDependencies(cls.__name__, cls._registry)
+ return findDependencies(cls.__name__, cls.sqlmeta.registry)
_SO_depends = classmethod(_SO_depends)
def select(cls, clause=None, clauseTables=None,
Modified: trunk/SQLObject/sqlobject/tests/test_auto.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_auto.py 2005-02-20 04:49:42 UTC (rev 625)
+++ trunk/SQLObject/sqlobject/tests/test_auto.py 2005-02-20 04:55:21 UTC (rev 626)
@@ -160,4 +160,5 @@
assert jane.wannahavefun
assert john.longField == 'x'*1000
assert jane.longField == 'x'*1000
- del classregistry.registry(AutoTest._registry).classes['AutoTest']
+ del classregistry.registry(
+ AutoTest.sqlmeta.registry).classes['AutoTest']
|
|
From: <sub...@co...> - 2005-02-20 04:49:47
|
Author: ianb
Date: 2005-02-20 04:49:42 +0000 (Sun, 20 Feb 2005)
New Revision: 625
Modified:
trunk/SQLObject/sqlobject/main.py
trunk/SQLObject/sqlobject/tests/test_basic.py
Log:
Moved cacheValues into sqlmeta
Modified: trunk/SQLObject/sqlobject/main.py
===================================================================
--- trunk/SQLObject/sqlobject/main.py 2005-02-20 04:45:59 UTC (rev 624)
+++ trunk/SQLObject/sqlobject/main.py 2005-02-20 04:49:42 UTC (rev 625)
@@ -153,6 +153,7 @@
style = None
lazyUpdate = False
defaultOrder = None
+ cacheValues = True
__metaclass__ = declarative.DeclarativeMeta
@@ -236,11 +237,6 @@
# will be true. It's false by default:
_SO_perConnection = False
- # The _cacheValues attribute controls if you cache
- # values fetched from the database. We make sure
- # it's set (default 1).
- _cacheValues = True
-
_connection = None
_columns = []
@@ -495,10 +491,11 @@
_idName = _sqlmeta_attr('idName', 2)
_lazyUpdate = _sqlmeta_attr('lazyUpdate', 2)
_defaultOrder = _sqlmeta_attr('defaultOrder', 2)
+ _cacheValues = _sqlmeta_attr('cacheValues', 2)
def _cleanDeprecatedAttrs(cls, new_attrs):
for attr in ['_table', '_lazyUpdate', '_style', '_idName',
- '_defaultOrder']:
+ '_defaultOrder', '_cacheValues']:
if new_attrs.has_key(attr):
new_name = attr[1:]
deprecated("'%s' is deprecated; please set the '%s' "
@@ -558,7 +555,7 @@
# we'll alias that to _SO_get_columnName. This
# allows a sort of super call, even though there's
# no superclass that defines the database access.
- if cls._cacheValues:
+ if cls.sqlmeta.cacheValues:
# We create a method here, which is just a function
# that takes "self" as the first argument.
getter = eval('lambda self: self._SO_loadValue(%s)' % repr(instanceName(name)))
@@ -609,7 +606,7 @@
# deal, except chopping off the "ID" ending since
# we're giving the object, not the ID of the
# object this time:
- if cls._cacheValues:
+ if cls.sqlmeta.cacheValues:
# self._SO_class_className is a reference
# to the class in question.
getter = eval('lambda self: self._SO_foreignKey(self._SO_loadValue(%r), self._SO_class_%s)' % (instanceName(name), column.foreignKey))
@@ -911,7 +908,7 @@
[(self._SO_columnDict[name].dbName,
dbValue)])
- if self._cacheValues:
+ if self.sqlmeta.cacheValues:
setattr(self, instanceName(name), value)
def set(self, **kw):
@@ -1127,7 +1124,7 @@
obj = cls.get(result[0], connection=connection)
else:
obj = cls.get(result[0])
- if not obj._cacheValues:
+ if not obj.sqlmeta.cacheValues:
obj._SO_writeLock.acquire()
try:
obj._SO_selectInit(result[1:])
Modified: trunk/SQLObject/sqlobject/tests/test_basic.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-20 04:45:59 UTC (rev 624)
+++ trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-20 04:49:42 UTC (rev 625)
@@ -4,9 +4,11 @@
class TestSO1(SQLObject):
name = StringCol(length=50, dbName='name_col')
- _cacheValues = False
passwd = StringCol(length=10)
+ class sqlmeta:
+ cacheValues = False
+
def _set_passwd(self, passwd):
self._SO_set_passwd(passwd.encode('rot13'))
|
|
From: <sub...@co...> - 2005-02-20 04:46:03
|
Author: ianb
Date: 2005-02-20 04:45:59 +0000 (Sun, 20 Feb 2005)
New Revision: 624
Modified:
trunk/SQLObject/sqlobject/joins.py
trunk/SQLObject/sqlobject/main.py
trunk/SQLObject/sqlobject/sresults.py
trunk/SQLObject/sqlobject/tests/test_auto.py
trunk/SQLObject/sqlobject/tests/test_joins.py
trunk/SQLObject/sqlobject/tests/test_sorting.py
trunk/SQLObject/sqlobject/tests/test_transactions.py
Log:
Moved defaultOrder into sqlmeta
Modified: trunk/SQLObject/sqlobject/joins.py
===================================================================
--- trunk/SQLObject/sqlobject/joins.py 2005-02-20 04:45:46 UTC (rev 623)
+++ trunk/SQLObject/sqlobject/joins.py 2005-02-20 04:45:59 UTC (rev 624)
@@ -74,7 +74,7 @@
def _applyOrderBy(self, results, defaultSortClass):
if self.orderBy is NoDefault:
- self.orderBy = defaultSortClass._defaultOrder
+ self.orderBy = defaultSortClass.sqlmeta.defaultOrder
if self.orderBy is not None:
results.sort(sorter(self.orderBy))
return results
Modified: trunk/SQLObject/sqlobject/main.py
===================================================================
--- trunk/SQLObject/sqlobject/main.py 2005-02-20 04:45:46 UTC (rev 623)
+++ trunk/SQLObject/sqlobject/main.py 2005-02-20 04:45:59 UTC (rev 624)
@@ -152,6 +152,7 @@
idName = None
style = None
lazyUpdate = False
+ defaultOrder = None
__metaclass__ = declarative.DeclarativeMeta
@@ -204,11 +205,11 @@
# 2) Deprecated after 1 (0.8?)
# 3) Deprecated after 2 (0.9?)
-def deprecated(message, level=1):
+def deprecated(message, level=1, stacklevel=2):
if exception_level is not None and exception_level <= level:
raise NotImplementedError(message)
if warnings_level is not None and warnings_level <= level:
- warnings.warn(message, DeprecationWarning, stacklevel=2)
+ warnings.warn(message, DeprecationWarning, stacklevel=stacklevel)
# SQLObject is the superclass for all SQLObject classes, of
# course. All the deeper magic is done in MetaSQLObject, and
@@ -240,9 +241,6 @@
# it's set (default 1).
_cacheValues = True
- # The _defaultOrder is used by SelectResults
- _defaultOrder = None
-
_connection = None
_columns = []
@@ -496,14 +494,17 @@
_table = _sqlmeta_attr('table', 2)
_idName = _sqlmeta_attr('idName', 2)
_lazyUpdate = _sqlmeta_attr('lazyUpdate', 2)
+ _defaultOrder = _sqlmeta_attr('defaultOrder', 2)
def _cleanDeprecatedAttrs(cls, new_attrs):
- for attr in ['_table', '_lazyUpdate', '_style', '_idName']:
+ for attr in ['_table', '_lazyUpdate', '_style', '_idName',
+ '_defaultOrder']:
if new_attrs.has_key(attr):
new_name = attr[1:]
deprecated("'%s' is deprecated; please set the '%s' "
"attribute in sqlmeta instead" %
- (attr, new_name), level=2)
+ (attr, new_name), level=2,
+ stacklevel=5)
setattr(cls.sqlmeta, new_name, new_attrs[attr])
delattr(cls, attr)
Modified: trunk/SQLObject/sqlobject/sresults.py
===================================================================
--- trunk/SQLObject/sqlobject/sresults.py 2005-02-20 04:45:46 UTC (rev 623)
+++ trunk/SQLObject/sqlobject/sresults.py 2005-02-20 04:45:59 UTC (rev 624)
@@ -19,7 +19,7 @@
self.tables = tablesDict.keys()
self.ops = ops
if self.ops.get('orderBy', sqlbuilder.NoDefault) is sqlbuilder.NoDefault:
- self.ops['orderBy'] = sourceClass._defaultOrder
+ self.ops['orderBy'] = sourceClass.sqlmeta.defaultOrder
orderBy = self.ops['orderBy']
if isinstance(orderBy, list) or isinstance(orderBy, tuple):
orderBy = map(self._mungeOrderBy, orderBy)
Modified: trunk/SQLObject/sqlobject/tests/test_auto.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_auto.py 2005-02-20 04:45:46 UTC (rev 623)
+++ trunk/SQLObject/sqlobject/tests/test_auto.py 2005-02-20 04:45:59 UTC (rev 624)
@@ -13,13 +13,15 @@
class Person(SQLObject):
+ class sqlmeta:
+ defaultOrder = 'name'
_columns = [StringCol('name', length=100, dbName='name_col')]
- _defaultOrder = 'name'
class Phone(SQLObject):
+ class sqlmeta:
+ defaultOrder = 'phone'
_columns = [StringCol('phone', length=12)]
- _defaultOrder = 'phone'
class TestPeople:
@@ -34,7 +36,7 @@
def test_defaultOrder(self):
assert (list(Person.select('all')) ==
- list(Person.select('all', orderBy=Person._defaultOrder)))
+ list(Person.select('all', orderBy=Person.sqlmeta.defaultOrder)))
def test_dynamicColumn(self):
if not supports('dynamicColumn'):
Modified: trunk/SQLObject/sqlobject/tests/test_joins.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_joins.py 2005-02-20 04:45:46 UTC (rev 623)
+++ trunk/SQLObject/sqlobject/tests/test_joins.py 2005-02-20 04:45:59 UTC (rev 624)
@@ -68,10 +68,12 @@
class AddressJoiner2(SQLObject):
+ class sqlmeta:
+ defaultOrder = ['-zip', 'plus4']
+
_columns = [StringCol('zip', length=5),
StringCol('plus4', length=4, default=None),
ForeignKey('PersonJoiner2')]
- _defaultOrder = ['-zip', 'plus4']
class TestJoin2:
Modified: trunk/SQLObject/sqlobject/tests/test_sorting.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_sorting.py 2005-02-20 04:45:46 UTC (rev 623)
+++ trunk/SQLObject/sqlobject/tests/test_sorting.py 2005-02-20 04:45:59 UTC (rev 624)
@@ -5,11 +5,11 @@
class sqlmeta(sqlmeta):
table = 'names_table'
+ defaultOrder = ['lastName', 'firstName']
firstName = StringCol(length=30)
lastName = StringCol(length=30)
- _defaultOrder = ['lastName', 'firstName']
def setupNames():
setupClass(Names)
Modified: trunk/SQLObject/sqlobject/tests/test_transactions.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_transactions.py 2005-02-20 04:45:46 UTC (rev 623)
+++ trunk/SQLObject/sqlobject/tests/test_transactions.py 2005-02-20 04:45:59 UTC (rev 624)
@@ -7,8 +7,9 @@
class TestSOTrans(SQLObject):
#_cacheValues = False
+ class sqlmeta:
+ defaultOrder = 'name'
name = StringCol(length=10, alternateID=True, dbName='name_col')
- _defaultOrderBy = 'name'
def test_transaction():
if not supports('transactions'):
|
|
From: <sub...@co...> - 2005-02-20 04:45:51
|
Author: ianb Date: 2005-02-20 04:45:46 +0000 (Sun, 20 Feb 2005) New Revision: 623 Added: trunk/SQLObject/sqlobject/tests/test_reparent_sqlmeta.py Log: Added test for the new softer inheritance requirements for sqlmeta Added: trunk/SQLObject/sqlobject/tests/test_reparent_sqlmeta.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_reparent_sqlmeta.py 2005-02-20 04:35:41 UTC (rev 622) +++ trunk/SQLObject/sqlobject/tests/test_reparent_sqlmeta.py 2005-02-20 04:45:46 UTC (rev 623) @@ -0,0 +1,30 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +real_sqlmeta = sqlmeta + +class Reparented1(SQLObject): + + class sqlmeta: + table = 'reparented1' + + dummy = StringCol() + +class Reparented2(SQLObject): + class sqlmeta(object): + def setClass(cls, soClass): + # Well, it's pretty hard to call the superclass method + # when it's a classmethod and it's not actually your + # *current* superclass. Sigh + real_sqlmeta.setClass.im_func(cls, soClass) + cls.worked = True + setClass = classmethod(setClass) + + dummy = StringCol() + +def test_reparented(): + setupClass([Reparented1, Reparented2]) + assert Reparented1.sqlmeta.table == 'reparented1' + assert issubclass(Reparented1.sqlmeta, real_sqlmeta) + assert issubclass(Reparented2.sqlmeta, real_sqlmeta) + assert Reparented2.sqlmeta.worked |
|
From: <sub...@co...> - 2005-02-20 04:35:47
|
Author: ianb
Date: 2005-02-20 04:35:41 +0000 (Sun, 20 Feb 2005)
New Revision: 622
Modified:
trunk/SQLObject/sqlobject/main.py
Log:
Simplified the cleanDeprecatedAttrs method
Modified: trunk/SQLObject/sqlobject/main.py
===================================================================
--- trunk/SQLObject/sqlobject/main.py 2005-02-20 04:29:35 UTC (rev 621)
+++ trunk/SQLObject/sqlobject/main.py 2005-02-20 04:35:41 UTC (rev 622)
@@ -498,31 +498,15 @@
_lazyUpdate = _sqlmeta_attr('lazyUpdate', 2)
def _cleanDeprecatedAttrs(cls, new_attrs):
- if new_attrs.has_key('_table'):
- deprecated("'_table' is deprecated; please set the 'table' "
- "attribute in sqlmeta instead", level=2)
- cls.sqlmeta.table = cls._table
- del cls._table
+ for attr in ['_table', '_lazyUpdate', '_style', '_idName']:
+ if new_attrs.has_key(attr):
+ new_name = attr[1:]
+ deprecated("'%s' is deprecated; please set the '%s' "
+ "attribute in sqlmeta instead" %
+ (attr, new_name), level=2)
+ setattr(cls.sqlmeta, new_name, new_attrs[attr])
+ delattr(cls, attr)
- if new_attrs.has_key('_lazyUpdate'):
- deprecated("'_lazyUpdate' is deprecated; please set the "
- "'lazyUpdate' attribute in sqlmeta instead",
- level=2)
- cls.sqlmeta.lazyUpdate = cls._lazyUpdate
- del cls._lazyUpdate
-
- if new_attrs.has_key('_style'):
- deprecated("'_style' is deprecated; please set the 'style' "
- "attribute in sqlmeta instead", level=2)
- cls.sqlmeta.style = cls._style
- del cls._style
-
- if new_attrs.has_key('_idName'):
- deprecated("'_idName' is deprecated; please set the 'idName' "
- "attribute in sqlmeta instead", level=2)
- cls.sqlmeta.idName = cls._idName
- del cls._idName
-
_cleanDeprecatedAttrs = classmethod(_cleanDeprecatedAttrs)
def get(cls, id, connection=None, selectResults=None):
|
|
From: <sub...@co...> - 2005-02-20 04:29:41
|
Author: ianb
Date: 2005-02-20 04:29:35 +0000 (Sun, 20 Feb 2005)
New Revision: 621
Modified:
trunk/SQLObject/sqlobject/main.py
trunk/SQLObject/sqlobject/tests/test_lazy.py
Log:
* Moved lazyUpdate to sqlmeta
* Moved the checking-and-moving of deprecated attributes into a
separate method (out of __classinit__)
Modified: trunk/SQLObject/sqlobject/main.py
===================================================================
--- trunk/SQLObject/sqlobject/main.py 2005-02-19 21:06:27 UTC (rev 620)
+++ trunk/SQLObject/sqlobject/main.py 2005-02-20 04:29:35 UTC (rev 621)
@@ -151,6 +151,7 @@
table = None
idName = None
style = None
+ lazyUpdate = False
__metaclass__ = declarative.DeclarativeMeta
@@ -258,8 +259,6 @@
# when necessary: (bad clever? maybe)
_expired = False
- _lazyUpdate = False
-
# This function is used to coerce IDs into the proper format,
# so you should replace it with str, or another function, if you
# aren't using integer IDs
@@ -283,6 +282,7 @@
# This is true if we're initializing the SQLObject class,
# instead of a subclass:
is_base = cls.__bases__ == (object,)
+ #assert cls.__name__ != 'Reparented1'
if (not new_attrs.has_key('sqlmeta')
and not is_base):
@@ -291,6 +291,31 @@
# subclass:
#cls.sqlmeta = cls.sqlmeta.clone()
cls.sqlmeta = type('sqlmeta', (cls.sqlmeta,), {})
+ if not issubclass(cls.sqlmeta, sqlmeta):
+ # We allow no superclass and an object superclass, instead
+ # of inheriting from sqlmeta; but in that case we replace
+ # the class and just move over its attributes:
+ assert cls.sqlmeta.__bases__ in ((), (object,)), (
+ "If you do not inherit your sqlmeta class from "
+ "sqlobject.sqlmeta, it must not inherit from any other "
+ "class (your sqlmeta inherits from: %s)"
+ % cls.sqlmeta.__bases__)
+ for base in cls.__bases__:
+ superclass = getattr(base, 'sqlmeta', None)
+ if superclass:
+ break
+ else:
+ assert 0, (
+ "No sqlmeta class could be found in any superclass "
+ "(while fixing up sqlmeta %r inheritance)"
+ % cls.sqlmeta)
+ values = dict(cls.sqlmeta.__dict__)
+ for key in values.keys():
+ if key.startswith('__') and key.endswith('__'):
+ # Magic values shouldn't be passed through:
+ del values[key]
+ cls.sqlmeta = type('sqlmeta', (superclass,), values)
+
cls.sqlmeta.setClass(cls)
implicitColumns = []
@@ -313,11 +338,8 @@
delattr(cls, attr)
continue
- if (new_attrs.has_key('_table') and not is_base):
- deprecated("'_table' is deprecated; please set the 'table' "
- "attribute in sqlmeta instead", level=2)
- cls.sqlmeta.table = cls._table
- del cls._table
+ if not is_base:
+ cls._cleanDeprecatedAttrs(new_attrs)
if new_attrs.has_key('_connection'):
connection = new_attrs['_connection']
@@ -402,12 +424,6 @@
if connection or not hasattr(cls, '_connection'):
cls.setConnection(connection)
- if (new_attrs.has_key('_style') and not is_base):
- deprecated("'_style' is deprecated; please set the 'style' "
- "attribute in sqlmeta instead", level=2)
- cls.sqlmeta.style = cls._style
- del cls._style
-
# plainSetters are columns that haven't been overridden by the
# user, so we can contact the database directly to set them.
# Note that these can't set these in the SQLObject class
@@ -425,12 +441,6 @@
cls._SO_columnDict = {}
cls._SO_columns = []
- if (new_attrs.has_key('_idName') and not is_base):
- deprecated("'_idName' is deprecated; please set the 'idName' "
- "attribute in sqlmeta instead", level=2)
- cls.sqlmeta.idName = cls._idName
- del cls._idName
-
# We have to check if there are columns in the inherited
# _columns where the attribute has been set to None in this
# class. If so, then we need to remove that column from
@@ -485,7 +495,36 @@
_style = _sqlmeta_attr('style', 2)
_table = _sqlmeta_attr('table', 2)
_idName = _sqlmeta_attr('idName', 2)
+ _lazyUpdate = _sqlmeta_attr('lazyUpdate', 2)
+ def _cleanDeprecatedAttrs(cls, new_attrs):
+ if new_attrs.has_key('_table'):
+ deprecated("'_table' is deprecated; please set the 'table' "
+ "attribute in sqlmeta instead", level=2)
+ cls.sqlmeta.table = cls._table
+ del cls._table
+
+ if new_attrs.has_key('_lazyUpdate'):
+ deprecated("'_lazyUpdate' is deprecated; please set the "
+ "'lazyUpdate' attribute in sqlmeta instead",
+ level=2)
+ cls.sqlmeta.lazyUpdate = cls._lazyUpdate
+ del cls._lazyUpdate
+
+ if new_attrs.has_key('_style'):
+ deprecated("'_style' is deprecated; please set the 'style' "
+ "attribute in sqlmeta instead", level=2)
+ cls.sqlmeta.style = cls._style
+ del cls._style
+
+ if new_attrs.has_key('_idName'):
+ deprecated("'_idName' is deprecated; please set the 'idName' "
+ "attribute in sqlmeta instead", level=2)
+ cls.sqlmeta.idName = cls._idName
+ del cls._idName
+
+ _cleanDeprecatedAttrs = classmethod(_cleanDeprecatedAttrs)
+
def get(cls, id, connection=None, selectResults=None):
assert id is not None, 'None is not a possible id for %s' % cls.__name
@@ -823,7 +862,7 @@
self._SO_writeLock.release()
def sync(self):
- if self._lazyUpdate and self._SO_createValues:
+ if self.sqlmeta.lazyUpdate and self._SO_createValues:
self.syncUpdate()
self._SO_writeLock.acquire()
try:
@@ -877,7 +916,7 @@
dbValue = value
if toPython:
value = toPython(dbValue, self._SO_validatorState)
- if self._SO_creating or self._lazyUpdate:
+ if self._SO_creating or self.sqlmeta.lazyUpdate:
self.dirty = True
self._SO_createValues[name] = dbValue
setattr(self, instanceName(name), value)
@@ -905,7 +944,7 @@
kw = dict(filter(f_is_column, items))
# _SO_creating is special, see _SO_setValue
- if self._SO_creating or self._lazyUpdate:
+ if self._SO_creating or self.sqlmeta.lazyUpdate:
for name, value in kw.items():
fromPython = getattr(self, '_SO_fromPython_%s' % name, None)
if fromPython:
@@ -1072,7 +1111,7 @@
# Doesn't have to be threadsafe because we're still in
# new(), which doesn't need to be threadsafe.
self.dirty = False
- if not self._lazyUpdate:
+ if not self.sqlmeta.lazyUpdate:
del self._SO_createValues
else:
self._SO_createValues = {}
Modified: trunk/SQLObject/sqlobject/tests/test_lazy.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_lazy.py 2005-02-19 21:06:27 UTC (rev 620)
+++ trunk/SQLObject/sqlobject/tests/test_lazy.py 2005-02-20 04:29:35 UTC (rev 621)
@@ -7,7 +7,8 @@
class Lazy(SQLObject):
- _lazyUpdate = True
+ class sqlmeta:
+ lazyUpdate = True
name = StringCol()
other = StringCol(default='nothing')
third = StringCol(default='third')
|
|
From: <sub...@co...> - 2005-02-19 21:06:33
|
Author: ianb
Date: 2005-02-19 21:06:27 +0000 (Sat, 19 Feb 2005)
New Revision: 620
Modified:
trunk/SQLObject/sqlobject/main.py
Log:
Removed a redundant attribute
Modified: trunk/SQLObject/sqlobject/main.py
===================================================================
--- trunk/SQLObject/sqlobject/main.py 2005-02-18 19:43:15 UTC (rev 619)
+++ trunk/SQLObject/sqlobject/main.py 2005-02-19 21:06:27 UTC (rev 620)
@@ -252,8 +252,6 @@
_fromDatabase = False
- _style = None
-
_registry = None
# Default is false, but we set it to true for the *instance*
|
|
From: <sub...@co...> - 2005-02-17 16:00:29
|
Author: phd Date: 2005-02-17 16:00:19 +0000 (Thu, 17 Feb 2005) New Revision: 615 Modified: trunk/SQLObject/docs/FAQ.txt Log: Mentioned BLOBCol and PickleCol in section "Binary values". Modified: trunk/SQLObject/docs/FAQ.txt =================================================================== --- trunk/SQLObject/docs/FAQ.txt 2005-02-17 08:12:15 UTC (rev 614) +++ trunk/SQLObject/docs/FAQ.txt 2005-02-17 16:00:19 UTC (rev 615) @@ -107,9 +107,9 @@ anyway). -There is also another kind of inheritance. See Inheritance.txt_ +There is also another kind of inheritance. See Inheritance.html_ -.. _Inheritance.txt: Inheritance.txt +.. _Inheritance.html: Inheritance.html Composite/Compound Attributes @@ -188,7 +188,14 @@ have a widely-implemented way to express binaries as literals, and there's differing support in database. -A possible way to keep this data in a database is by using encoding. +The module sqlobject.col defines validators and column classes that +to some extent support binary values. There is BLOBCol that extends +StringCol and allow to store binary values; currently it works only +with PostgreSQL and MySQL. PickleCol extends BLOBCol and allows to store +any object in the column; the column, naturally, pickles the object upon +assignment and unpickles it upon retrieving the data from the DB. + +Another possible way to keep binary data in a database is by using encoding. Base 64 is a good encoding, reasonably compact but also safe. As an example, imagine you want to store images in the database: |
|
From: <sub...@co...> - 2005-02-17 08:12:18
|
Author: ianb Date: 2005-02-17 08:12:15 +0000 (Thu, 17 Feb 2005) New Revision: 614 Added: trunk/SQLObject/docs/SQLObjectClasses.txt trunk/SQLObject/docs/SQLObjectCol.txt trunk/SQLObject/docs/SQLObjectComparison.txt trunk/SQLObject/docs/SQLObjectCustomization.txt trunk/SQLObject/docs/SQLObjectDBConnection.txt trunk/SQLObject/docs/SQLObjectDeclaration.txt trunk/SQLObject/docs/SQLObjectDynamic.txt trunk/SQLObject/docs/SQLObjectExported.txt trunk/SQLObject/docs/SQLObjectFuture.txt trunk/SQLObject/docs/SQLObjectGeneration.txt trunk/SQLObject/docs/SQLObjectIntro.txt trunk/SQLObject/docs/SQLObjectLazy.txt trunk/SQLObject/docs/SQLObjectLegacy.txt trunk/SQLObject/docs/SQLObjectManyToMany.txt trunk/SQLObject/docs/SQLObjectOneToMany.txt trunk/SQLObject/docs/SQLObjectRelationships.txt trunk/SQLObject/docs/SQLObjectRequirements.txt trunk/SQLObject/docs/SQLObjectSelect.txt trunk/SQLObject/docs/SQLObjectTransactions.txt trunk/SQLObject/docs/SQLObjectUse.txt Modified: trunk/SQLObject/docs/SQLObject.txt Log: Split SQLObject.txt into bite-sized chunks. These chunks will become documents in the Wiki. Modified: trunk/SQLObject/docs/SQLObject.txt =================================================================== --- trunk/SQLObject/docs/SQLObject.txt 2005-02-17 08:11:27 UTC (rev 613) +++ trunk/SQLObject/docs/SQLObject.txt 2005-02-17 08:12:15 UTC (rev 614) @@ -4,573 +4,24 @@ .. contents:: Contents: -Author, Site, and License -========================= +.. include:: SQLObjectIntro.txt +.. include:: SQLObjectRequirements.txt +.. include:: SQLObjectComparison.txt +.. include:: SQLObjectFuture.txt -SQLObject is by Ian Bicking (ia...@co...). The website -is sqlobject.org__. - -__ http://sqlobject.org - -The code is licensed under the `Lesser General Public License`_ -(LGPL). - -.. _`Lesser General Public License`: http://www.gnu.org/copyleft/lesser.html - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -Introduction -============ - -SQLObject is an *object-relational mapper*. It allows you to -translate RDBMS table rows into Python objects, and manipulate those -objects to transparently manipulate the database. - -In using SQLObject, you will create a class definition that will -describe how the object translates to the database table. SQLObject -will produce the code to access the database, and update the database -with your changes. The generated interface looks similar to any other -interface, and callers need not be aware of the database backend. - -SQLObject also includes a novel feature to avoid generating, -textually, your SQL queries. This also allows non-SQL databases to be -used with the same query syntax. - -Requirements -============ - -Currently SQLObject supports MySQL_, PostgreSQL_ (via ``psycopg``), -SQLite_, Firebird_, Sybase_, and `MAX DB`_ (also known as SAP DB). - -.. _PostgreSQL: http://postgresql.org -.. _SQLite: http://sqlite.org -.. _Firebird: http://firebird.sourceforge.net - -Python 2.2 or higher is required. SQLObject makes extensive use of -new-style classes. - -Compared To Other Database Wrappers -=================================== - -There are several object-relational mappers (ORM) for Python. I -honestly can't comment deeply on the quality of those packages, but -I'll try to place SQLObject in perspective. - -SQLObject uses new-style classes extensively. The resultant objects -have a new-style feel as a result -- setting attributes has side -effects (it changes the database), and defining classes has side -effects (through the use of metaclasses). Attributes are generally -exposed, not marked private, knowing that they can be made dynamic -or write-only later. - -SQLObject creates objects that feel similar to normal Python objects -(with the semantics of new-style classes). An attribute attached to a -column doesn't look different than an attribute that's attached to a -file, or an attribute that is calculated. It is a specific goal that -you be able to change the database without changing the interface, -including changing the scope of the database, making it more or less -prominent as a storage mechanism. - -This is in contrast to some ORMs that provide a dictionary-like -interface to the database (for example, PyDO_). The dictionary -interface distinguishes the row from a normal Python object. I also -don't care for the use of strings where an attribute seems more -natural -- columns are limited in number and predefined, just like -attributes. (Note: newer version of PyDO apparently allow attribute -access as well) - -.. _PyDO: http://skunkweb.sourceforge.net/pydo.html - -SQLObject is, to my knowledge, unique in using metaclasses to -facilitate this seemless integration. Some other ORMs use code -generation to create an interface, expressing the schema in a CSV or -XML file (for example, MiddleKit, part of Webware_). By using -metaclasses you are able to comfortably define your schema in the -Python source code. No code generation, no weird tools, no -compilation step. - -.. _Webware: http://webware.sourceforge.net - -SQLObject provides a strong database abstraction, allowing -cross-database compatibility (so long as you don't sidestep -SQLObject). - -SQLObject has joins, one-to-many, and many-to-many, something which -many ORMs do not have. The join system is also intended to be -extensible. - -You can map between database names and Python attribute and class -names; often these two won't match, or the database style would be -inappropriate for a Python attribute. This way your database schema -does not have to be designed with SQLObject in mind, and the resulting -classes do not have to inherit the database's naming schemes. - -Future -====== - -Here are some things I plan: - -* More databases supported. There has been interest and some work in - the progress for Oracle, Sybase, and MS-SQL support. -* Better transaction support -- right now you can use transactions - for the database, but the object isn't transaction-aware, so - non-database persistence won't be able to be rolled back. -* Optimistic locking and other techniques to handle concurrency. -* Profile of SQLObject performance, so that I can identify bottlenecks. -* Increase hooks with FormEncode (unreleased) validation and form - generation package, so SQLObject classes (read: schemas) can be - published for editing more directly and easily. -* Automatic joins in select queries. -* More kinds of joins, and more powerful join results (closer to how - `select` works). - -See also the `Plan for 0.6`__. - -.. __: Plan06.html - Using SQLObject: An Introduction ================================ Let's start off quickly... -Declaring the Class -------------------- +.. include:: SQLObjectDeclaration.txt +.. include:: SQLObjectUse.txt +.. include:: SQLObjectLazy.txt +.. include:: SQLObjectOneToMany.txt +.. include:: SQLObjectManyToMany.txt +.. include:: SQLObjectSelect.txt +.. include:: SQLObjectCustomization.txt -To begin with, let's make a database connection. Choose from one of -`MySQLConnection`, `PostgresConnection`, `SQLiteConnection`, and -`FirebirdConnection`, depending on what database you use. - -.. raw:: html - :file: ../examples/snippets/connections.html - -The rest of this will be written more-or-less in a database-agnostic -manner, using the connection you define. Use `SQLite` if you don't -have another database installed or ready -- it requires PySQLite_, but -doesn't require a client/server setup. - -.. _PySQLite: http://pysqlite.sourceforge.net/ - -We'll develop a simple addressbook-like database. We could create the -tables ourselves, and just have SQLObject access those tables, but for -now we'll let SQLObject do that work. First, the class: - -.. raw:: html - :file: ../examples/snippets/simpleaddress-person1.html - -Many basic table schemas won't be any more complicated than that. -The special attribute `_connection` defines the connection we should -use (you can also set a module-level variable `__connection__` which -would automatically be picked up if you don't specify `_connection`). - -.. warning:: - The `__connection__` magic variable can be a little fragile -- it - has to be defined before the class is defined. This means it - *must* be assigned above the ``class ...:`` line. - -`firstName`, `middleInitial`, and `lastName` are all columns in the -database. The general schema implied by this class definition is: - -.. raw:: html - :file: ../examples/snippets/simpleaddress-schema-person1.html - -This is for MySQL. The schema for other databases looks slightly -different (especially the ``id`` column). You'll notice the names -were changed from mixedCase to underscore_separated -- this is done by -the `style object`_. There are a variety of ways to handle that names -that don't fit conventions (see `Irregular Naming`_). - -.. _`style object`: `Changing the Naming Style`_ - -The tables don't yet exist. We'll let SQLObject create them: - -.. raw:: html - :file: ../examples/snippets/simpleaddress-person1-create.html - -We can change the type of the various columns by using something other -than `StringCol`, or using different arguments. More about this in -`Subclasses of Col`_. - -If you don't want to do table creation (you already have tables, or -you want to create the tables yourself), you can just use the vague -`Col` class. SQLObject doesn't do much type checking, allowing the -database and the adapter to handle most of the type conversion. -Databases generally do their own type coercion on inputs. - -You'll note that the ``id`` column is not given in the class -definition, it is implied. For MySQL databases it should be defined -as ``INT PRIMARY KEY AUTO_INCREMENT``, in Postgres ``SERIAL PRIMARY -KEY``, and in SQLite as ``INTEGER PRIMARY KEY``. You can `override the -name`__, but some integer primary key must exist (though `you can use -non-integer keys`_ with some extra effort). - -__ idName_ -.. _`you can use non-integer keys`: `Non-Integer Keys`_ - -Using the Class ---------------- - -Now that you have a class, how will you use it? We'll be considering -the class defined above. - -You can use the class method `.get()` to fetch instances that -already exist. So if you wanted to fetch the Person by id 10, you'd -call ``Person.get(10)``. - -.. warning:: - This is a change from SQLObject 0.5 -- before the standard - constructor fetched rows from the database, and the `.new()` - method created new rows. Now SQLObject is more like Python, where - the class constructor creates a new object/row, and the `.get()` - method fetches a row. - -To create a new object (and row), use class instantiation. In this -case you might call ``Person.new(firstName="John", lastName="Doe")``. -If you had left out ``firstName`` or ``lastName`` you would have -gotten an error, as no default was given for these columns -(``middleInitial`` has a default, so it will be set to ``NULL``, the -SQL equivalent of ``None``). - -When you create an object, it is immediately inserted into the -database. SQLObject generally uses the database as immediate storage. - -Here's an example of using the class: - -.. raw:: html - :file: ../examples/snippets/simpleaddress-person1-use.html - -You'll note that columns are accessed like attributes. (This uses the -``property`` feature of Python 2.2, so that retrieving and setting -these attributes executes code). You'll also note that objects are -unique -- there is generally only one ``Person`` instance of a -particular id in memory at any one time. If you ask for more than one -person by a particular ID, you'll get back the same instance. This -way you can be sure of a certain amount of consistency if you have -multiple threads accessing the same data (though of course across -processes there can be no sharing of an instance). This isn't true if -you're using transactions_. - -To get an idea of what's happening behind the surface, I'll give the -same actions with the SQL that is sent, along with some commentary: - -.. raw:: html - :file: ../examples/snippets/simpleaddress-person1-use-debug.html - -Hopefully you see that the SQL that gets sent is pretty clear and -predictable. To view the SQL being sent, pass the keyword argument -``debug=1`` to your connection object -- all SQL will be printed to -the console. This can be reassuring, and I would encourage you to try -it. - -As a small optimization, instead of assigning each attribute -individually, you can assign a number of them using the ``set`` -method, like: - -.. raw:: html - :file: ../examples/snippets/simpleaddress-person1-use-set.html - -This will send only one ``UPDATE`` statement. You can also use `set` -with non-database properties (there's no benefit, but it helps hide -the difference between database and non-database attributes). - -Lazy Updates ------------- - -By default SQLObject sends an ``UPDATE`` to the database for every -attribute you set, or everytime you call ``.set()``. If you want to -avoid this many updates, add ``_lazyUpdate = True`` to your class -definition. Then updates will only be written to the database when -you call ``inst.syncUpdate()`` or ``obj.sync()``: ``.sync()`` also -refetches the data from the database, which ``.syncUpdate()`` does not -do. - -When enabled instances will have a property ``dirty``, which indicates -if there are pending updates. Inserts are still done immediately. - -One-to-Many Relationships -------------------------- - -A real address book should have people, but also addresses. These -examples are in ``personaddress.py`` - -First, let's define the new address table. People can have multiple -addresses, of course: - -.. raw:: html - :file: ../examples/snippets/address-address.html - -Note the column ``person = ForeignKey("Person")``. This is a -reference to a `Person` object. We refer to other classes by name -(with a string) to avoid circular dependencies. In the database -there will be a ``person_id`` column, type ``INT``, which points to -the ``person`` column. - -Here's the `Person` class: - -.. raw:: html - :file: ../examples/snippets/address-person.html - -We get the backreference with ``addresses = MultipleJoin('Address')``. -When we access a person's `addresses` attribute, we will get back a -list of all the `Address` objects associated with that person. An -example: - -.. raw:: html - :file: ../examples/snippets/address-use1.html - -Many-to-Many Relationships --------------------------- - -For this example we will have user and role objects. The two have a -many-to-many relationship, which is represented with the -`RelatedJoin`. - -.. raw:: html - :file: ../examples/snippets/userrole-classes.html - -And usage: - -.. raw:: html - :file: ../examples/snippets/userrole-use.html - -In the process an intermediate table is created, ``role_user``, which -references both of the other classes. This table is never exposed as -a class, and its rows do not have equivalent Python objects -- this -hides some of the nuisance of a many-to-many relationship. - -You may notice that the columns have the extra keyword argument -`alternateID`. If True, this means that the column uniquely -identifies rows -- like a username uniquely identifies a user. This -identifier is in addition to the primary key (``id``), which is always -present. - -.. note:: - SQLObject has a strong requirement that the primary key be unique - and *immutable*. You cannot change the primary key through - SQLObject, and if you change it through another mechanism you can - cause inconsistency in any running SQLObject program (and in your - data). For this reason meaningless integer IDs are encouraged -- - something like a username that could change in the future may - uniquely identify a row, but it may be changed in the future. So - long as it is not used to reference the row internally, it is also - *safe* to change it in the future. - -A alternateID column creates a class method, like ``byUsername`` for a -column named ``username`` (or you can use the `alternateMethodName` -keyword argument to override this). Its use: - -.. raw:: html - :file: ../examples/snippets/userrole-use-alternate.html - - -Selecting Multiple Objects --------------------------- - -While the full power of all the kinds of joins you can do with a -database are not revealed in SQLObject, a simple ``SELECT`` is -available. - -``select`` is a class method, and you call it like (with the SQL -that's generated): - -.. raw:: html - :file: ../examples/snippets/person-select1.html - -This example returns everyone with the first name John. An expression -could be more complicated as well, like: - -.. raw:: html - :file: ../examples/snippets/person-select2.html - -You'll note that classes have an attribute ``q``, which gives access -to special objects for constructing query clauses. All attributes -under ``q`` refer to column names and if you construct logical -statements with these it'll give you the SQL for that statement. You -can also work like this: - -.. raw:: html - :file: ../examples/snippets/person-select3.html - -You may wish to use `MyClass.sqlrepr` to quote any values you use if -you create SQL manually (quoting is automatic if you use ``q``). -Tables given in `clauseTables` will be added to the ``FROM`` portion -(again, they are automatically picked up when using ``q``). The table -you're selecting is always assumed to be included, of course. - -.. _orderBy: - -You can use the keyword arguments `orderBy` to create ``ORDER BY`` in -the select statements: `orderBy` takes a string, which should be the -*database* name of the column, or a column in the form -``Person.q.firstName``. You can use ``"-colname"`` to specify -descending order, or call ``MyClass.select().reversed()``. - -You can use the special class variable `_defaultOrder` to give a -default ordering for all selects. To get an unordered result when -`_defaultOrder` is used, use ``orderBy=None``. - -Select results are generators, which are lazily evaluated. So the SQL -is only executed when you iterate over the select results, or if you -use ``list()`` to force the result to be executed. When you iterate -over the select results, rows are fetched one at a time. This way you -can iterate over large results without keeping the entire result set -in memory. You can also do things like ``.reversed()`` without -fetching and reversing the entire result -- instead, SQLObject can -change the SQL that is sent so you get equivalent results. - -You can also slice select results. The results are used in the SQL -query, so ``peeps[:10]`` will result in ``LIMIT 10`` being added to -the end of the SQL query. If the slice cannot be performed in the SQL -(e.g., peeps[:-10]), then the select is executed, and the slice is -performed on the list of results. This will only happen when you use -negative indexes. - -In certain cases, you may get a select result with an object in it -more than once, e.g., in some joins. If you don't want this, you can -add the keyword argument ``MyClass.select(..., distinct=True)``. - -You can get the length of the result without fetching all the results -by calling ``count`` on the result object, like -``MyClass.select().count()``. A ``COUNT(*)`` query is used -- the -actual objects are not fetched from the database. Together with -slicing, this makes batched queries easy to write: - -.. raw:: html - :file: ../examples/snippets/slicing-batch.html - -.. note:: - - There are several factors when considering the efficiency of this - kind of batching, and it depends very much how the batching is - being used. Consider a web application where you are showing an - average of 100 results, 10 at a time, and the results are ordered - by the date they were added to the database. While slicing will - keep the database from returning all the results (and so save some - communication time), the database will still have to scan through - the entire result set to sort the items (so it knows which the - first ten are), and depending on your query may need to scan - through the entire table (depending on your use of indexes). - Indexes are probably the most important way to improve importance - in a case like this, and you may find caching to be more effective - than slicing. - - In this case, caching would mean retrieving the *complete* results. - You can use ``list(MyClass.select(...))`` to do this. You can save - these results for some limited period of time, as the user looks - through the results page by page. This means the first page in a - search result will be slightly more expensive, but all later pages - will be very cheap. - -For more information on the where clause in the queries, see the -`SQLBuilder documentation`_. - -.. _`SQLBuilder documentation`: SQLBuilder.html - -Select-By Method -~~~~~~~~~~~~~~~~ - -An alternative to ``.select`` is ``.selectBy``. It works like: - -.. raw:: html - :file: ../examples/snippets/person-select-by.html - -Each keyword argument is a column, and all the keyword arguments -are ANDed together. The return value is a `SelectResult`, so you -can slice it, count it, order it, etc. - -Customizing the Objects ------------------------ - -While we haven't done so in the examples, you can include your own -methods in the class definition. Writing you own methods should be -obvious enough (just do so like in any other class), but there are -some other details to be aware of. - -Initializing the Objects -~~~~~~~~~~~~~~~~~~~~~~~~ - -There are two ways SQLObject instances can come into existance: they -can be fetched from the database, or they can be inserted into the -database. In both cases a new Python object is created. This makes -the place of `__init__` a little confusing. - -In general, you should not touch `__init__`. Instead use the `_init` -method, which is called after an object is fetched or inserted. This -method has the signature ``_init(self, id, connection=None, -selectResults=None)``, though you may just want to use ``_init(self, -*args, **kw)``. **Note:** don't forget to call -``SQLObject._init(self, *args, **kw)`` if you override the method! - -Adding Magic Attributes (properties) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can use all the normal techniques for defining methods in this -new-style class, including `classmethod`, `staticmethod`, and -`property`, but you can also use a shortcut. If you have a method -that's name starts with ``_set_``, ``_get_``, ``_del_``, or ``_doc_``, -it will be used to create a property. So, for instance, say you have -images stored under the ID of the person in the ``/var/people/images`` -directory: - -.. raw:: html - :file: ../examples/snippets/person_magicmethod.html - -Later, you can use the ``.image`` property just like an attribute, and -the changes will be reflected in the filesystem by calling these -methods. This is a good technique for information that is better to -keep in files as opposed to the database (such as large, opaque data -like images). - -You can also pass an ``image`` keyword argument to the `new` class -method or the `set` method, like ``Person.new(..., image=imageText)``. - -All of the methods (``_get_``, ``_set_``, etc) are optional -- you can -use any one of them without using the others (except ``_doc_``, since -having a doc string that doesn't document anything would be silly). -So you could define just a ``_get_attr`` method so that ``attr`` was -read-only. - -Overriding Column Attributes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -It's a little more complicated if you want to override the behavior of -an database column attribute. For instance, imagine there's special -code you want to run whenever someone's name changes -- you could make -a subclass, and then use ``Person.__setattr__(self, 'lastName', -value)`` to actually do the deed, but that's obviously very awkward -- -you have to create subclasses without a real inheritance relationship, -and the whole thing feels architecturally fragile. SQLObject creates -methods like ``_set_lastName`` for each of your columns, but again you -can't use this, since there's no superclass to reference (and you -can't write ``SQLObject._set_lastName(...)``, because the SQLObject -class doesn't know about your class's columns). You want to override -that ``_set_lastName`` method yourself. - -To deal with this, SQLObject creates two methods for each getter and -setter, for example: ``_set_lastName`` and ``_SO_set_lastName``. So -to intercept all changes to ``lastName``: - -.. raw:: html - :file: ../examples/snippets/person_magicoverride.html - -Or perhaps you want to constrain a phone numbers to be actual -digits, and of proper length, and make the formatting nice: - -.. raw:: html - :file: ../examples/snippets/phonenumber_magicoverride.html - -.. note:: - - You should be a little cautious when modifying data that gets set - in an attribute. Generally someone using your class will expect - that the value they set the attribute to will be the same value - they get back. In this example we removed some of the characters - before putting it in the database, and reformatted it on the way - out. One advantage of methods (as opposed to attribute access) is - that the programmer is more likely to expect this disconnect. - Reference ========= @@ -578,637 +29,16 @@ be useful for many situations. Now we'll show how to specify the class more completely. -Col Class: Specifying Columns ------------------------------ +.. include:: SQLObjectCol.txt +.. include:: SQLObjectClasses.txt +.. include:: SQLObjectRelationships.txt +.. include:: SQLObjectTransactions.txt +.. include:: SQLObjectGeneration.txt +.. include:: SQLObjectDynamic.txt +.. include:: SQLObjectLegacy.txt +.. include:: SQLObjectDBConnection.txt +.. include:: SQLObjectExported.txt -The list of columns is a list of `Col` objects. These objects don't -have functionality in themselves, but give you a way to specify the -column. - -`dbName`: - This is the name of the column in the database. If you don't - give a name, your Pythonic name will be converted from - mixed-case to underscore-separated. -`default`: - The default value for this column. Used when creating a new row. - If you give a callable object or function, the function will be - called, and the return value will be used. So you can give - ``DateTime.now`` to make the default value be the current time. - Or you can use ``SQLBuilder.func.NOW()`` to have the database use - the ``NOW()`` function internally. If you don't give a default - there will be an exception if this column isn't specified in the - call to `new`. -`alternateID`: - This boolean (default False) indicates if the column can be used - as an ID for the field (for instance, a username), though it is - not a primary key. If so a class method will be added, like - ``byUsername`` which will return that object. Use - `alternateMethodName` if you don't like the ``by*`` name - (e.g. ``alternateMethodName="username"``). - - The column should be declared ``UNIQUE`` in your table schema. -`unique`: - If true, when SQLObject creates a table it will declare this - column to be ``UNIQUE``. -`notNone`: - If true, None/``NULL`` is not allowed for this column. Useful if - you are using SQLObject to create your tables. -`sqlType`: - The SQL type for this column (like ``INT``, ``BOOLEAN``, etc). - You can use classes (defined below) for this, but if those don't - work it's sometimes easiest just to use `sqlType`. Only necessary - if SQLObject is creating your tables. - -Subclasses of Col -~~~~~~~~~~~~~~~~~ - -The `ForeignKey` class should be used instead of `Col` when the column -is a reference to another table/class. It is generally used like -``ForeignKey('Role')``, in this instance to create a reference to a -table `Role`. This is largely equivalent to ``Col(foreignKey='Role', -sqlType='INT')``. Two attributes will generally be created, ``role``, -which returns a `Role` instance, and ``roleID``, which returns an -integer ID for the related role. - -There are some other subclasses of `Col`. These are used to indicate -different types of columns, when SQLObject creates your tables. - -`BoolCol`: - Will create a ``BOOLEAN`` column in Postgres, or ``INT`` in other - databses. It will also convert values to ``"t"/"f"`` or ``0/1`` - according to the database backend. - -`CurrencyCol`: - Equivalent to ``DecimalCol(size=10, precision=2)``. - -`DateTimeCol`: - A date and time (usually returned as an mxDateTime object). - -`DecimalCol`: - Base-10, precise number. Uses the keyword arguments `size` for - number of digits stored, and `precision` for the number of digits - after the decimal point. - -`EnumCol`: - One of several string values -- give the possible strings as a - list, with the `enumValues` keyword argument. MySQL has a native - ``ENUM`` type, but will work with other databases too (storage - just won't be as efficient). - -`FloatCol`: - Floats. - -`ForeignKey`: - A key to another table/class. Use like ``user = - ForeignKey('User')``. - -`IntCol`: - Integers. - -`StringCol`: - A string (character) column. Extra keywords: - - `length`: - If given, the type will be something like ``VARCHAR(length)``. - If not given, then ``TEXT`` is assumed (i.e., lengthless). - `varchar`: - A boolean; if you have a length, differentiates between - ``CHAR`` and ``VARCHAR``, default True, i.e., use - ``VARCHAR``. - -`UnicodeCol`: - A subclass of `StringCol`. Also accepts a dbEncoding keyword - argument, which defaults to ``"UTF-8"``. Values coming in and - out from the database will be encoded and decoded. **Note**: - parameters in queries will not be automatically encoded, so if - you do a query matching a UnicodeCol column you must apply the - encoding yourself. - - -SQLObject Class: Specifying Your Class --------------------------------------- - -In addition to the columns, there are a number of other special -attributes you can set in your class. - -`_connection`: - The connection object to use, from `DBConnection`. You can also - set the variable `__connection__` in the enclosing module and it - will be picked up (be sure to define `__connection__` before you - class). You can also pass a connection object in at instance - creation time, as described in transactions_. - -`_table`: - The database name of the table for this class. If you don't give - a name, then the standard ``MixedCase`` to ``mixed_case`` - translation is performed. - -`_joins`: - A list of `Join` objects. This is covered below. - -`_cacheValues`: - If set to ``False`` then values for attributes from the database - won't be cached. So everytime you access an attribute in the - object the database will be queried for a value. If you want to - handle concurrent access to the database from multiple processes - then this is probably the way to do so. You should also use - it with transactions_ (it is not implied). - -.. _idName: - -`_idName`: - The name of the primary key column (default ``id``). - -`_style`: - A style object -- this object allows you to use other algorithms - for translating between Python attribute and class names, and the - database's column and table names. See `Changing the Naming - Style`_ for more. - -.. Relationships_: - -Relationships Between Classes/Tables ------------------------------------- - -You can use the `ForeignKey` to handle foreign references in a table, -but for back references and many-to-many relationships you'll use -joins. - -MultipleJoin: One-to-Many -~~~~~~~~~~~~~~~~~~~~~~~~~ - -See `One-to-Many Relationships`_ for an example of one-to-many -relationships. - -Several keyword arguments are allowed to the `MultipleJoin` constructor: - -.. _`Multiple Join Keywords`: - -`joinColumn`: - The column name of the key that points to this table. So, if you have - a table ``Product``, and another table has a column ``ProductNo`` that - points to this table, then you'd use ``joinColumn="ProductNo"``. -`orderBy`: - Like the `orderBy`_ argument to `select()`, you can specify - the order that the joined objects should be returned in. `_defaultOrder` - will be used if not specified; ``None`` forces unordered results. -`joinMethodName`: - When adding joins dynamically (using the class method `addJoin`_), - you can give the name of the accessor for the join. It can also be - created automatically, and is normally implied (i.e., ``addresses = - MultipleJoin(...)`` implies ``joinMethodName="addresses"``). - -RelatedJoin: Many-to-Many -~~~~~~~~~~~~~~~~~~~~~~~~~ - -See `Many-to-Many Relationships`_ for examples of using many-to-many joins. - -`RelatedJoin` has all the keyword arguments of `MultipleJoin`__, plus: - -__ `Multiple Join Keywords`_ - -`otherColumn`: - Similar to `joinColumn`, but referring to the joined class. -`intermediateTable`: - The name of the intermediate table which references both classes. -`addRemoveName`: - In the `user/role example`__, the methods `addRole(role)` and - `removeRole(role)` are created. The ``Role`` portion of these - method names can be changed by giving a string value here. - -__ `Many-to-Many Relationships`_ - -An example schema that requires the use of `joinColumn`, `otherColumn`, -and `intermediateTable`:: - - CREATE TABLE person ( - id SERIAL, - username VARCHAR(100) NOT NULL UNIQUE - ); - - CREATE TABLE role ( - id SERIAL, - name VARCHAR(50) NOT NULL UNIQUE - ); - - CREATE TABLE assigned_roles ( - person INT NOT NULL, - role INT NOT NULL - ); - -Then the usage in a class:: - - class Person(SQLObject): - username = StringCol(length=100, alternateID=True) - roles = RelatedJoin('Role', joinColumn='person', otherColumn='role', - intermediateTable='assigned_roles') - class Role(SQLObject): - name = StringCol(length=50, alternateID=True) - roles = RelatedJoin('Person', joinColumn='role', otherColumn='person', - intermediateTable='assigned_roles') - -Transactions ------------- - -Transaction support in SQLObject is left to the database. -Transactions can be used like: - -.. raw:: html - :file: ../examples/snippets/transactions1.html - -The ``trans`` object here is essentially a wrapper around a single -database connection, and `commit` and `rollback` just pass that -message to the `psycopg` connection. - -If you want to use transactions you should also turn `_cacheValues` -off, like: - -.. raw:: html - :file: ../examples/snippets/transactions2.html - -Automatic Schema Generation ---------------------------- - -All the connections support creating and droping tables based on the -class definition. First you have to prepare your class definition, -which means including type information in your columns. - -Columns Types -~~~~~~~~~~~~~ - -A column type is indicated by using a subclass of `Col`: - -`StringCol`: - StringCol represents ``CHAR``, ``VARCHAR``, and ``TEXT``. The - `length` keyword argument indicates the ``CHAR`` or ``VARCHAR`` - length -- if not given, then ``TEXT`` is assumed. If you use - ``varchar=False`` then ``CHAR`` will be used, otherwise - ``VARCHAR`` is the default. -`IntCol`: - The ``INT`` type. -`FloatCol`: - The ``FLOAT`` type. -`DecimalCol`: - The ``DECIMAL`` SQL type, i.e., base 10 number. The keyword - arguments `size` and `precision` indicate the scope. So - ``DecimalCol(size=5, precision=2)`` is a number like ###.##, - i.e., 5 digits, two of them past the decimal point. -`CurrencyCol`: - Like ``DecimalCol(size=10, precision=2)``. -`EnumCol`: - A MySQL ``ENUM``, i.e., one of a finite number of strings. - For other databases this will be a ``VARCHAR``. -`DateTimeCol`: - A moment in time. ``TIMESTAMP`` in Postgres, and ``DATETIME`` - in MySQL. Note the names of these columns match the *Python* - type names, not the SQL names. -`ForeignKey`: - This is a reference to another table. You typically need to - only give the name of the foreign class that is referenced. - `ForeignKey` implies an ``INT`` column. - -Indexes -~~~~~~~ - -You can also define indexes for your tables, which is only meaningful -when creating your tables through SQLObject (SQLObject relies on the -database to implement the indexes). You do this again with attribute -assignment, like:: - - firstLastIndex = DatabaseIndex('firstName', 'lastName') - -This creates an index on two columns, useful if you are selecting a -particular name. Of course, you can give a single column, and you can -give the column object (``firstName``) instead of the string name. -Note that if you use ``unique`` or ``alternateID`` (which implies -``unique``) the database may make an index for you, and primary keys -are always indexed. - -If you give the keyword argument ``unique`` to `DatabaseIndex` you'll -create a unique index -- the combination of columns must be unique. - -You can also use dictionaries in place of the column names, to add -extra options. E.g.:: - - lastNameIndex = DatabaseIndex({'expression': 'lower(last_name)'}) - -In that case, the index will be on the lower-case version of the -column. It seems that only PostgreSQL supports this. You can also -do:: - - lastNameIndex = DatabaseIndex({'column': lastName, 'length': 10}) - -Which asks the database to only pay attention to the first ten -characters. Only MySQL supports this, but it is ignored in other -databases. - -Creating and Dropping Tables -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To create a table call `createTable`. It takes two arguments: - -`ifNotExists`: - If the table already exists, then don't try to create it. Default - False. -`createJoinTables`: - If you used `Many-to-Many relationships`_, then the intermediate tables - will be created (but only for one of the two involved classes). - Default True. - -`dropTable` takes arguments `ifExists` and `dropJoinTables`, -self-explanatory. - -Dynamic Classes -=============== - -SQLObject classes can be manipulated dynamically. This leaves open -the possibility of constructing SQLObject classes from an XML file, -from database introspection, or from a graphical interface. - -Automatic Class Generation ---------------------------- - -SQLObject can read the table description from the database, and fill -in the class columns (as would normally be described in the `_columns` -attribute). Do this like:: - - class Person(SQLObject): - - _fromDatabase = True - -You can still specify columns (in `_columns`), and only missing -columns will be added. - -*This is not supported in SQLite* - -Runtime Column Changes ----------------------- - -*SQLite does not support this feature* - -You can add and remove columns to your class at runtime. Such changes -will effect all instances, since changes are made inplace to the -class. There are two methods, `addColumn` and `delColumn`, both of -which take a `Col` object (or subclass) as an argument. There's also -an option argument `changeSchema` which, if True, will add or drop the -column from the database (typically with an ``ALTER`` command). - -When adding columns, you must pass the name as part of the column -constructor, like ``StringCol("username", length=20)``. When removing -columns, you can either use the Col object (as found in `_columns`, or -which you used in `addColumn`), or you can use the column name (like -``MyClass.delColumn("username")``). - -.. _addJoin: - -You can also add Joins__, like -``MyClass.addJoin(MultipleJoin("MyOtherClass"))``, and remove joins with -`delJoin`. `delJoin` does not take strings, you have to get the join -object out of the `_joins` attribute. - -__ Relationships_: - -Legacy Database Schemas -======================= - -Often you will have a database that already exists, and does not use -the naming conventions that SQLObject expects, or does not use any -naming convention at all. - -SQLObject requirements ----------------------- - -While SQLObject tries not to make too many requirements on your -schema, some assumptions are made. Some of these may be relaxed in -the future. - -All tables that you want to turn into a class need to have an integer -primary key. That key should be defined like: - -MySQL: - ``INT PRIMARY KEY AUTO_INCREMENT`` -Postgres: - ``SERIAL PRIMARY KEY`` -SQLite: - ``INTEGER PRIMARY KEY`` - -SQLObject does not support non-integer keys (that may change). It -does not support sequences in Postgres (that will change -- ``SERIAL`` -uses an implicit sequence). It does not support primary keys made up -of multiple columns (that probably won't change). It does not -generally support tables with primary keys with business meaning -- -i.e., primary keys are assumed to be immutable (that won't change). - -At the moment foreign key column names must end in ``"ID"`` -(case-insensitive). This restriction will probably be removed in the -next release. - -Changing the Naming Style -------------------------- - -By default names in SQLObject are expected to be mixed case in Python -(like ``mixedCase``), and underscore-separated in SQL (like -``mixed_case``). This applies to table and column names. The primary -key is assumed to be simply ``id``. - -Other styles exist. A typical one is mixed case column names, and a -primary key that includes the table name, like ``ProductID``. You can -use a different "Style" object to indicate a different naming -convention. For instance: - -.. raw:: html - :file: ../examples/snippets/style1.html - -If you use ``Person.createTable()``, you'll get:: - - CREATE TABLE Person ( - PersonID INT PRIMARY KEY, - FirstName Text, - LastName Text - ) - -The `MixedCaseStyle` object handles the initial capitalization of -words, but otherwise leaves them be. By using ``longID=True``, we -indicate that the primary key should look like a normal reference -(``PersonID`` for `MixedCaseStyle`, or ``person_id`` for the default -style). - -If you wish to change the style globally, assign the style to the -connection, like: - -.. raw:: html - :file: ../examples/snippets/default-style.html - -Irregular Naming ----------------- - -While naming conventions are nice, they are not always present. You -can control most of the names that SQLObject uses, independent of the -Python names (so at least you don't have to propagate the -irregularity to your brand-spanking new Python code). - -Here's a simple example: - -.. raw:: html - :file: ../examples/snippets/style-table.html - -The attribute `_table` overrides the table name. `_idName` provides -an alternative to ``id``. The ``dbName`` keyword argument gives the -column name. - -Non-Integer Keys ----------------- - -While not strictly a legacy database issue, this fits into the -category of "irregularities". If you use non-integer keys, all -primary key management is up to you. You must create the table -yourself, and when you create instances you must pass a ``id`` keyword -argument into ``new`` (like ``Person.new(id='555-55-5555', ...)``). - -DBConnection: Database Connections -================================== - -The `DBConnection` module currently has four external classes, -`MySQLConnection`, `PostgresConnection`, `SQLiteConnection`, -`SybaseConnection`, and `MaxdbConnection`. - -You can pass the keyword argument `debug` to any connector. If set to -true, then any SQL sent to the database will also be printed to the -console. - -MySQL ------ - -`MySQLConnection` takes the keyword arguments `host`, `db`, `user`, -and `passwd`, just like `MySQLdb.connect` does. - -MySQLConnection supports all the features, though MySQL only supports -transactions_ when using the InnoDB backend, and SQLObject currently -does not have support for explicitly defining the backend when using -``createTable``. - -Postgres --------- - -`PostgresConnection` takes a single connection string, like -``"dbname=something user=some_user"``, just like `psycopg.connect`. -You can also use the same keyword arguments as for `MySQLConnection`, -and a dsn string will be constructed. - -PostgresConnection supports transactions and all other features. - -SQLite ------- - -`SQLiteConnection` takes the a single string, which is the path to the -database file. - -SQLite puts all data into one file, with a journal file that is opened -in the same directory during operation (the file is deleted when the -program quits). SQLite does not restrict the types you can put in a -column -- strings can go in integer columns, dates in integers, etc. - -SQLiteConnection doesn't support `automatic class generation`_ and -SQLite does not support `runtime column changes`_. - -SQLite may have concurrency issues, depending on your usage in a -multi-threaded environment. - -Firebird --------- - -`FirebirdConnection` takes the arguments `host`, `db`, `user` (default -``"sysdba"``), `passwd` (default ``"masterkey"``). - -Firebird supports all the features. Support is still young, so there -may be some issues, especially with concurrent access, and especially -using lazy selects. Try ``list(MyClass.select())`` to avoid -concurrent cursors if you have problems (using ``list()`` will -pre-fetch all the results of a select). - -Firebird support uses the kinterbasdb_ Python library. - -.. _kinterbasdb: http://kinterbasdb.sourceforge.net/ - -If you are using indexes and get an error like *key size exceeds -implementation restriction for index*, see `this page`_ to understand -the restrictions on your indexing. - -.. _this page: http://www.volny.cz/iprenosil/interbase/ip_ib_indexcalculator.htm - -SybaseConnection ----------------- - -`SybaseConnection` takes the arguments `host`, `db`, `user`, and -`passwd`. It also takes the extra boolean argument `locking` (default -True), which is passed through when performing a connection. You may -use a False value for `locking` if you are not using multiple threads, -for a slight performance boost. - -It uses the Sybase_ module. - -.. _Sybase: http://www.object-craft.com.au/projects/sybase/ - -MAX DB ------- - -MAX DB, also known as SAP DB, is available from a partnership of SAP -and MySQL. It takes the typical arguments: `host`, `database`, -`user`, `password`. It also takes the arguments `sqlmode` (default -``"internal"``), `isolation`, and `timeout`, which are passed through -when creating the connection to the database. - -It uses the sapdb_ module. - -.. _sapdb: http://www.sapdb.org/sapdbPython.html - -Exported Symbols -================ - -You can use ``from sqlobject import *``, though you don't have to. It -exports a minimal number of symbols. The symbols exported: - -From `sqlobject.main`: - -* `NoDefault` -* `SQLObject` -* `getID` -* `getObject` - -From `sqlobject.col`: -* `Col` -* `StringCol` -* `IntCol` -* `FloatCol` -* `KeyCol` -* `ForeignKey` -* `EnumCol` -* `DateTimeCol` -* `DecimalCol` -* `CurrencyCol` - -From `sqlobject.joins`: -* `MultipleJoin` -* `RelatedJoin` - -From `sqlobject.styles`: -* `Style` -* `MixedCaseUnderscoreStyle` -* `DefaultStyle` -* `MixedCaseStyle` - -From `sqlobject.sqlbuilder`: - -* `AND` -* `OR` -* `NOT` -* `IN` -* `LIKE` -* `DESC` -* `CONTAINSSTRING` -* `const` -* `func` - For more information on SQLBuilder, read the `SQLBuilder Documentation`_. Added: trunk/SQLObject/docs/SQLObjectClasses.txt =================================================================== --- trunk/SQLObject/docs/SQLObjectClasses.txt 2005-02-17 08:11:27 UTC (rev 613) +++ trunk/SQLObject/docs/SQLObjectClasses.txt 2005-02-17 08:12:15 UTC (rev 614) @@ -0,0 +1,41 @@ +SQLObject Class: Specifying Your Class +-------------------------------------- + +In addition to the columns, there are a number of other special +attributes you can set in your class. + +`_connection`: + The connection object to use, from `DBConnection`. You can also + set the variable `__connection__` in the enclosing module and it + will be picked up (be sure to define `__connection__` before you + class). You can also pass a connection object in at instance + creation time, as described in transactions_. + +`_table`: + The database name of the table for this class. If you don't give + a name, then the standard ``MixedCase`` to ``mixed_case`` + translation is performed. + +`_joins`: + A list of `Join` objects. This is covered below. + +`_cacheValues`: + If set to ``False`` then values for attributes from the database + won't be cached. So everytime you access an attribute in the + object the database will be queried for a value. If you want to + handle concurrent access to the database from multiple processes + then this is probably the way to do so. You should also use + it with transactions_ (it is not implied). + +.. _idName: + +`_idName`: + The name of the primary key column (default ``id``). + +`_style`: + A style object -- this object allows you to use other algorithms + for translating between Python attribute and class names, and the + database's column and table names. See `Changing the Naming + Style`_ for more. + +.. Relationships_: Added: trunk/SQLObject/docs/SQLObjectCol.txt =================================================================== --- trunk/SQLObject/docs/SQLObjectCol.txt 2005-02-17 08:11:27 UTC (rev 613) +++ trunk/SQLObject/docs/SQLObjectCol.txt 2005-02-17 08:12:15 UTC (rev 614) @@ -0,0 +1,105 @@ +Col Class: Specifying Columns +----------------------------- + +The list of columns is a list of `Col` objects. These objects don't +have functionality in themselves, but give you a way to specify the +column. + +`dbName`: + This is the name of the column in the database. If you don't + give a name, your Pythonic name will be converted from + mixed-case to underscore-separated. +`default`: + The default value for this column. Used when creating a new row. + If you give a callable object or function, the function will be + called, and the return value will be used. So you can give + ``DateTime.now`` to make the default value be the current time. + Or you can use ``SQLBuilder.func.NOW()`` to have the database use + the ``NOW()`` function internally. If you don't give a default + there will be an exception if this column isn't specified in the + call to `new`. +`alternateID`: + This boolean (default False) indicates if the column can be used + as an ID for the field (for instance, a username), though it is + not a primary key. If so a class method will be added, like + ``byUsername`` which will return that object. Use + `alternateMethodName` if you don't like the ``by*`` name + (e.g. ``alternateMethodName="username"``). + + The column should be declared ``UNIQUE`` in your table schema. +`unique`: + If true, when SQLObject creates a table it will declare this + column to be ``UNIQUE``. +`notNone`: + If true, None/``NULL`` is not allowed for this column. Useful if + you are using SQLObject to create your tables. +`sqlType`: + The SQL type for this column (like ``INT``, ``BOOLEAN``, etc). + You can use classes (defined below) for this, but if those don't + work it's sometimes easiest just to use `sqlType`. Only necessary + if SQLObject is creating your tables. + +Subclasses of Col +~~~~~~~~~~~~~~~~~ + +The `ForeignKey` class should be used instead of `Col` when the column +is a reference to another table/class. It is generally used like +``ForeignKey('Role')``, in this instance to create a reference to a +table `Role`. This is largely equivalent to ``Col(foreignKey='Role', +sqlType='INT')``. Two attributes will generally be created, ``role``, +which returns a `Role` instance, and ``roleID``, which returns an +integer ID for the related role. + +There are some other subclasses of `Col`. These are used to indicate +different types of columns, when SQLObject creates your tables. + +`BoolCol`: + Will create a ``BOOLEAN`` column in Postgres, or ``INT`` in other + databses. It will also convert values to ``"t"/"f"`` or ``0/1`` + according to the database backend. + +`CurrencyCol`: + Equivalent to ``DecimalCol(size=10, precision=2)``. + +`DateTimeCol`: + A date and time (usually returned as an mxDateTime object). + +`DecimalCol`: + Base-10, precise number. Uses the keyword arguments `size` for + number of digits stored, and `precision` for the number of digits + after the decimal point. + +`EnumCol`: + One of several string values -- give the possible strings as a + list, with the `enumValues` keyword argument. MySQL has a native + ``ENUM`` type, but will work with other databases too (storage + just won't be as efficient). + +`FloatCol`: + Floats. + +`ForeignKey`: + A key to another table/class. Use like ``user = + ForeignKey('User')``. + +`IntCol`: + Integers. + +`StringCol`: + A string (character) column. Extra keywords: + + `length`: + If given, the type will be something like ``VARCHAR(length)``. + If not given, then ``TEXT`` is assumed (i.e., lengthless). + `varchar`: + A boolean; if you have a length, differentiates between + ``CHAR`` and ``VARCHAR``, default True, i.e., use + ``VARCHAR``. + +`UnicodeCol`: + A subclass of `StringCol`. Also accepts a dbEncoding keyword + argument, which defaults to ``"UTF-8"``. Values coming in and + out from the database will be encoded and decoded. **Note**: + parameters in queries will not be automatically encoded, so if + you do a query matching a UnicodeCol column you must apply the + encoding yourself. Added: trunk/SQLObject/docs/SQLObjectComparison.txt =================================================================== --- trunk/SQLObject/docs/SQLObjectComparison.txt 2005-02-17 08:11:27 UTC (rev 613) +++ trunk/SQLObject/docs/SQLObjectComparison.txt 2005-02-17 08:12:15 UTC (rev 614) @@ -0,0 +1,55 @@ +Compared To Other Database Wrappers +=================================== + +There are several object-relational mappers (ORM) for Python. I +honestly can't comment deeply on the quality of those packages, but +I'll try to place SQLObject in perspective. + +SQLObject uses new-style classes extensively. The resultant objects +have a new-style feel as a result -- setting attributes has side +effects (it changes the database), and defining classes has side +effects (through the use of metaclasses). Attributes are generally +exposed, not marked private, knowing that they can be made dynamic +or write-only later. + +SQLObject creates objects that feel similar to normal Python objects +(with the semantics of new-style classes). An attribute attached to a +column doesn't look different than an attribute that's attached to a +file, or an attribute that is calculated. It is a specific goal that +you be able to change the database without changing the interface, +including changing the scope of the database, making it more or less +prominent as a storage mechanism. + +This is in contrast to some ORMs that provide a dictionary-like +interface to the database (for example, PyDO_). The dictionary +interface distinguishes the row from a normal Python object. I also +don't care for the use of strings where an attribute seems more +natural -- columns are limited in number and predefined, just like +attributes. (Note: newer version of PyDO apparently allow attribute +access as well) + +.. _PyDO: http://skunkweb.sourceforge.net/pydo.html + +SQLObject is, to my knowledge, unique in using metaclasses to +facilitate this seemless integration. Some other ORMs use code +generation to create an interface, expressing the schema in a CSV or +XML file (for example, MiddleKit, part of Webware_). By using +metaclasses you are able to comfortably define your schema in the +Python source code. No code generation, no weird tools, no +compilation step. + +.. _Webware: http://webware.sourceforge.net + +SQLObject provides a strong database abstraction, allowing +cross-database compatibility (so long as you don't sidestep +SQLObject). + +SQLObject has joins, one-to-many, and many-to-many, something which +many ORMs do not have. The join system is also intended to be +extensible. + +You can map between database names and Python attribute and class +names; often these two won't match, or the database style would be +inappropriate for a Python attribute. This way your database schema +does not have to be designed with SQLObject in mind, and the resulting +classes do not have to inherit the database's naming schemes. Added: trunk/SQLObject/docs/SQLObjectCustomization.txt =================================================================== --- trunk/SQLObject/docs/SQLObjectCustomization.txt 2005-02-17 08:11:27 UTC (rev 613) +++ trunk/SQLObject/docs/SQLObjectCustomization.txt 2005-02-17 08:12:15 UTC (rev 614) @@ -0,0 +1,90 @@ +Customizing the Objects +----------------------- + +While we haven't done so in the examples, you can include your own +methods in the class definition. Writing you own methods should be +obvious enough (just do so like in any other class), but there are +some other details to be aware of. + +Initializing the Objects +~~~~~~~~~~~~~~~~~~~~~~~~ + +There are two ways SQLObject instances can come into existance: they +can be fetched from the database, or they can be inserted into the +database. In both cases a new Python object is created. This makes +the place of `__init__` a little confusing. + +In general, you should not touch `__init__`. Instead use the `_init` +method, which is called after an object is fetched or inserted. This +method has the signature ``_init(self, id, connection=None, +selectResults=None)``, though you may just want to use ``_init(self, +*args, **kw)``. **Note:** don't forget to call +``SQLObject._init(self, *args, **kw)`` if you override the method! + +Adding Magic Attributes (properties) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use all the normal techniques for defining methods in this +new-style class, including `classmethod`, `staticmethod`, and +`property`, but you can also use a shortcut. If you have a method +that's name starts with ``_set_``, ``_get_``, ``_del_``, or ``_doc_``, +it will be used to create a property. So, for instance, say you have +images stored under the ID of the person in the ``/var/people/images`` +directory: + +.. raw:: html + :file: ../examples/snippets/person_magicmethod.html + +Later, you can use the ``.image`` property just like an attribute, and +the changes will be reflected in the filesystem by calling these +methods. This is a good technique for information that is better to +keep in files as opposed to the database (such as large, opaque data +like images). + +You can also pass an ``image`` keyword argument to the `new` class +method or the `set` method, like ``Person.new(..., image=imageText)``. + +All of the methods (``_get_``, ``_set_``, etc) are optional -- you can +use any one of them without using the others (except ``_doc_``, since +having a doc string that doesn't document anything would be silly). +So you could define just a ``_get_attr`` method so that ``attr`` was +read-only. + +Overriding Column Attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It's a little more complicated if you want to override the behavior of +an database column attribute. For instance, imagine there's special +code you want to run whenever someone's name changes -- you could make +a subclass, and then use ``Person.__setattr__(self, 'lastName', +value)`` to actually do the deed, but that's obviously very awkward -- +you have to create subclasses without a real inheritance relationship, +and the whole thing feels architecturally fragile. SQLObject creates +methods like ``_set_lastName`` for each of your columns, but again you +can't use this, since there's no superclass to reference (and you +can't write ``SQLObject._set_lastName(...)``, because the SQLObject +class doesn't know about your class's columns). You want to override +that... [truncated message content] |
|
From: <sub...@co...> - 2005-02-17 08:11:39
|
Author: ianb
Date: 2005-02-17 08:11:27 +0000 (Thu, 17 Feb 2005)
New Revision: 613
Modified:
trunk/SQLObject/docs/default.css
Log:
Links in headers (typically backlinks to the ToC) won't be miscolored
now (so the HTML doesn't have to be generated with special options)
Modified: trunk/SQLObject/docs/default.css
===================================================================
--- trunk/SQLObject/docs/default.css 2005-02-16 04:31:51 UTC (rev 612)
+++ trunk/SQLObject/docs/default.css 2005-02-17 08:11:27 UTC (rev 613)
@@ -130,11 +130,19 @@
border: medium solid black;
}
+h1 a:link, h2 a:link {
+ color: #ffffff;
+}
+
h3, h4, h5, h6 {
background-color: #cccccc;
color: #000000;
}
+h3 a:link, h4 a:link, h5 a:link, h6 a:link {
+ color: #000000;
+}
+
h1.title {
text-align: center;
background-color: #444499;
|
|
From: <sub...@co...> - 2005-02-16 04:31:55
|
Author: ianb
Date: 2005-02-16 04:31:51 +0000 (Wed, 16 Feb 2005)
New Revision: 612
Modified:
trunk/SQLObject/sqlobject/col.py
trunk/SQLObject/sqlobject/dbconnection.py
trunk/SQLObject/sqlobject/firebird/firebirdconnection.py
trunk/SQLObject/sqlobject/index.py
trunk/SQLObject/sqlobject/inheritance/__init__.py
trunk/SQLObject/sqlobject/joins.py
trunk/SQLObject/sqlobject/main.py
trunk/SQLObject/sqlobject/maxdb/maxdbconnection.py
trunk/SQLObject/sqlobject/mysql/mysqlconnection.py
trunk/SQLObject/sqlobject/postgres/pgconnection.py
trunk/SQLObject/sqlobject/sqlbuilder.py
trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py
trunk/SQLObject/sqlobject/sresults.py
trunk/SQLObject/sqlobject/styles.py
trunk/SQLObject/sqlobject/sybase/sybaseconnection.py
Log:
* Replace cls._table with cls.sqlmeta.table
* and cls._idName with cls.sqlmeta.idName
* and cls._style with cls.sqlmeta.style
* and make tests angry if old styles are used. Tests be very angry with
old and deprecated styles, Hulk will smash bad tests!
Modified: trunk/SQLObject/sqlobject/col.py
===================================================================
--- trunk/SQLObject/sqlobject/col.py 2005-02-16 04:30:22 UTC (rev 611)
+++ trunk/SQLObject/sqlobject/col.py 2005-02-16 04:31:51 UTC (rev 612)
@@ -159,7 +159,7 @@
# the column, we separate the mixedCase into mixed_case
# and assume that.
if dbName is None:
- self.dbName = soClass._style.pythonAttrToDBColumn(self.name)
+ self.dbName = soClass.sqlmeta.style.pythonAttrToDBColumn(self.name)
else:
self.dbName = dbName
@@ -607,7 +607,7 @@
def __init__(self, **kw):
foreignKey = kw['foreignKey']
- style = kw['soClass']._style
+ style = kw['soClass'].sqlmeta.style
if not kw.get('name'):
kw['name'] = style.instanceAttrToIDAttr(style.pythonClassToAttr(foreignKey))
else:
@@ -619,8 +619,8 @@
def postgresCreateSQL(self):
sql = SOKeyCol.postgresCreateSQL(self)
other = findClass(self.foreignKey)
- tName = other._table
- idName = other._idName
+ tName = other.sqlmeta.table
+ idName = other.sqlmeta.idName
if self.cascade is not None:
if self.cascade == 'null':
action = 'ON DELETE SET NULL'
@@ -644,8 +644,8 @@
def sybaseCreateSQL(self):
sql = SOKeyCol.sybaseCreateSQL(self)
other = findClass(self.foreignKey)
- tName = other._table
- idName = other._idName
+ tName = other.sqlmeta.table
+ idName = other.sqlmeta.idName
reference = ('REFERENCES %(tName)s(%(idName)s) ' %
{'tName':tName,
'idName':idName})
@@ -657,8 +657,8 @@
fidName = self.dbName
#I assume that foreign key name is identical to the id of the reference table
sql = ' '.join([fidName, self._maxdbType()])
- tName = other._table
- idName = other._idName
+ tName = other.sqlmeta.table
+ idName = other.sqlmeta.idName
sql=sql + ',' + '\n'
sql=sql + 'FOREIGN KEY (%s) REFERENCES %s(%s)'%(fidName,tName,idName)
return sql
Modified: trunk/SQLObject/sqlobject/dbconnection.py
===================================================================
--- trunk/SQLObject/sqlobject/dbconnection.py 2005-02-16 04:30:22 UTC (rev 611)
+++ trunk/SQLObject/sqlobject/dbconnection.py 2005-02-16 04:31:51 UTC (rev 612)
@@ -271,18 +271,18 @@
q = 'SELECT '
if ops.get('lazyColumns', 0):
q += "%s.%s FROM %s WHERE " % \
- (cls._table, cls._idName,
+ (cls.sqlmeta.table, cls.sqlmeta.idName,
", ".join(select.tables))
else:
- columns = ", ".join(["%s.%s" % (cls._table, col.dbName)
+ columns = ", ".join(["%s.%s" % (cls.sqlmeta.table, col.dbName)
for col in cls._SO_columns])
if columns:
q += "%s.%s, %s FROM %s WHERE " % \
- (cls._table, cls._idName, columns,
+ (cls.sqlmeta.table, cls.sqlmeta.idName, columns,
", ".join(select.tables))
else:
q += "%s.%s FROM %s WHERE " % \
- (cls._table, cls._idName,
+ (cls.sqlmeta.table, cls.sqlmeta.idName,
", ".join(select.tables))
return self._addWhereClause(select, q)
@@ -360,7 +360,7 @@
def createTableSQL(self, soClass):
return ('CREATE TABLE %s (\n%s\n)' %
- (soClass._table, self.createColumns(soClass)))
+ (soClass.sqlmeta.table, self.createColumns(soClass)))
def createColumns(self, soClass):
columnDefs = [self.createIDColumn(soClass)] \
@@ -396,36 +396,45 @@
def _SO_update(self, so, values):
self.query("UPDATE %s SET %s WHERE %s = %s" %
- (so._table,
+ (so.sqlmeta.table,
", ".join(["%s = %s" % (dbName, self.sqlrepr(value))
for dbName, value in values]),
- so._idName,
+ so.sqlmeta.idName,
self.sqlrepr(so.id)))
def _SO_selectOne(self, so, columnNames):
- return self.queryOne("SELECT %s FROM %s WHERE %s = %s" %
- (", ".join(columnNames),
- so._table,
- so._idName,
- self.sqlrepr(so.id)))
+ columns = ", ".join(columnNames)
+ if columns:
+ return self.queryOne(
+ "SELECT %s FROM %s WHERE %s = %s" %
+ (columns,
+ so.sqlmeta.table,
+ so.sqlmeta.idName,
+ self.sqlrepr(so.id)))
+ else:
+ return self.queryOne(
+ "SELECT NULL FROM %s WHERE %s = %s" %
+ (so.sqlmeta.table,
+ so.sqlmeta.idName,
+ self.sqlrepr(so.id)))
def _SO_selectOneAlt(self, cls, columnNames, column, value):
return self.queryOne("SELECT %s FROM %s WHERE %s = %s" %
(", ".join(columnNames),
- cls._table,
+ cls.sqlmeta.table,
column,
self.sqlrepr(value)))
def _SO_delete(self, so):
self.query("DELETE FROM %s WHERE %s = %s" %
- (so._table,
- so._idName,
+ (so.sqlmeta.table,
+ so.sqlmeta.idName,
self.sqlrepr(so.id)))
def _SO_selectJoin(self, soClass, column, value):
return self.queryAll("SELECT %s FROM %s WHERE %s = %s" %
- (soClass._idName,
- soClass._table,
+ (soClass.sqlmeta.idName,
+ soClass.sqlmeta.table,
column,
self.sqlrepr(value)))
Modified: trunk/SQLObject/sqlobject/firebird/firebirdconnection.py
===================================================================
--- trunk/SQLObject/sqlobject/firebird/firebirdconnection.py 2005-02-16 04:30:22 UTC (rev 611)
+++ trunk/SQLObject/sqlobject/firebird/firebirdconnection.py 2005-02-16 04:31:51 UTC (rev 612)
@@ -86,8 +86,8 @@
"""Firebird uses 'generators' to create new ids for a table.
The users needs to create a generator named GEN_<tablename>
for each table this method to work."""
- table = soInstance._table
- idName = soInstance._idName
+ table = soInstance.sqlmeta.table
+ idName = soInstance.sqlmeta.idName
sequenceName = getattr(soInstance, '_idSequence',
'GEN_%s' % table)
if id is None:
@@ -122,14 +122,14 @@
def createTable(self, soClass):
self.query('CREATE TABLE %s (\n%s\n)' % \
- (soClass._table, self.createColumns(soClass)))
- self.query("CREATE GENERATOR GEN_%s" % soClass._table)
+ (soClass.sqlmeta.table, self.createColumns(soClass)))
+ self.query("CREATE GENERATOR GEN_%s" % soClass.sqlmeta.table)
def createColumn(self, soClass, col):
return col.firebirdCreateSQL()
def createIDColumn(self, soClass):
- return '%s INT NOT NULL PRIMARY KEY' % soClass._idName
+ return '%s INT NOT NULL PRIMARY KEY' % soClass.sqlmeta.idName
def createIndexSQL(self, soClass, index):
return index.firebirdCreateIndexSQL(soClass)
@@ -188,7 +188,7 @@
if field == 'id':
continue
colClass, kw = self.guessClass(t, flength, fscale)
- kw['name'] = soClass._style.dbColumnToPythonAttr(field)
+ kw['name'] = soClass.sqlmeta.style.dbColumnToPythonAttr(field)
kw['notNone'] = not nullAllowed
kw['default'] = thedefault
results.append(colClass(**kw))
Modified: trunk/SQLObject/sqlobject/index.py
===================================================================
--- trunk/SQLObject/sqlobject/index.py 2005-02-16 04:30:22 UTC (rev 611)
+++ trunk/SQLObject/sqlobject/index.py 2005-02-16 04:31:51 UTC (rev 612)
@@ -71,9 +71,9 @@
spec.append(desc['column'].dbName)
ret = 'CREATE %s %s_%s ON %s (%s)' % \
(uniqueOrIndex,
- self.soClass._table,
+ self.soClass.sqlmeta.table,
self.name,
- self.soClass._table,
+ self.soClass.sqlmeta.table,
', '.join(spec))
return ret
@@ -94,7 +94,7 @@
spec.append(desc['column'].dbName)
return 'ALTER TABLE %s ADD %s %s (%s)' % \
- (soClass._table, uniqueOrIndex,
+ (soClass.sqlmeta.table, uniqueOrIndex,
self.name,
', '.join(spec))
Modified: trunk/SQLObject/sqlobject/inheritance/__init__.py
===================================================================
--- trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-16 04:30:22 UTC (rev 611)
+++ trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-16 04:31:51 UTC (rev 612)
@@ -19,7 +19,7 @@
if clause is None or isinstance(clause, str) and clause == 'all':
clause = sqlbuilder.SQLTrueClause
tablesDict = sqlbuilder.tablesUsedDict(clause)
- tablesDict[sourceClass._table] = 1
+ tablesDict[sourceClass.sqlmeta.table] = 1
orderBy = ops.get('orderBy')
if orderBy and not isinstance(orderBy, basestring):
tablesDict.update(sqlbuilder.tablesUsedDict(orderBy))
@@ -33,11 +33,11 @@
tableRegistry = {}
allClasses = classregistry.registry(sourceClass._registry).allClasses()
for registryClass in allClasses:
- if registryClass._table in tablesDict:
+ if registryClass.sqlmeta.table in tablesDict:
#DSM: By default, no parents are needed for the clauses
tableRegistry[registryClass] = registryClass
for registryClass in allClasses:
- if registryClass._table in tablesDict:
+ if registryClass.sqlmeta.table in tablesDict:
currentClass = registryClass
while currentClass._parentClass:
currentClass = currentClass._parentClass
@@ -56,7 +56,7 @@
parentClass = currentClass._parentClass
parentClause.append(currentClass.q.id == parentClass.q.id)
currentClass = parentClass
- tablesDict[currentClass._table] = 1
+ tablesDict[currentClass.sqlmeta.table] = 1
clause = reduce(sqlbuilder.AND, parentClause, clause)
super(InheritableSelectResults, self).__init__(sourceClass, clause, clauseTables,
Modified: trunk/SQLObject/sqlobject/joins.py
===================================================================
--- trunk/SQLObject/sqlobject/joins.py 2005-02-16 04:30:22 UTC (rev 611)
+++ trunk/SQLObject/sqlobject/joins.py 2005-02-16 04:31:51 UTC (rev 612)
@@ -63,7 +63,8 @@
# Here we set up the basic join, which is
# one-to-many, where the other class points to
# us.
- self.joinColumn = styles.getStyle(self.soClass).tableReference(self.soClass._table)
+ self.joinColumn = styles.getStyle(
+ self.soClass).tableReference(self.soClass.sqlmeta.table)
def _setOtherClass(self, cls):
self.otherClass = cls
@@ -149,12 +150,13 @@
def _setOtherRelatedClass(self, otherClass):
if not self.intermediateTable:
- names = [self.soClass._table,
- otherClass._table]
+ names = [self.soClass.sqlmeta.table,
+ otherClass.sqlmeta.table]
names.sort()
self.intermediateTable = '%s_%s' % (names[0], names[1])
if not self.otherColumn:
- self.otherColumn = self.soClass._style.tableReference(otherClass._table)
+ self.otherColumn = self.soClass.sqlmeta.style.tableReference(
+ otherClass.sqlmeta.table)
def hasIntermediateTable(self):
Modified: trunk/SQLObject/sqlobject/main.py
===================================================================
--- trunk/SQLObject/sqlobject/main.py 2005-02-16 04:30:22 UTC (rev 611)
+++ trunk/SQLObject/sqlobject/main.py 2005-02-16 04:31:51 UTC (rev 612)
@@ -180,10 +180,15 @@
class _sqlmeta_attr(object):
- def __init__(self, name):
+ def __init__(self, name, deprecation_level):
self.name = name
+ self.deprecation_level = deprecation_level
def __get__(self, obj, type=None):
+ if self.deprecation_level is not None:
+ deprecated(
+ 'Use of this attribute should be replaced with '
+ '.sqlmeta.%s' % self.name, level=self.deprecation_level)
return getattr((type or obj).sqlmeta, self.name)
@@ -479,9 +484,9 @@
classregistry.registry(cls._registry).addClass(cls)
- _style = _sqlmeta_attr('style')
- _table = _sqlmeta_attr('table')
- _idName = _sqlmeta_attr('idName')
+ _style = _sqlmeta_attr('style', 2)
+ _table = _sqlmeta_attr('table', 2)
+ _idName = _sqlmeta_attr('idName', 2)
def get(cls, id, connection=None, selectResults=None):
@@ -624,7 +629,7 @@
if changeSchema:
conn = connection or cls._connection
- conn.addColumn(cls._table, column)
+ conn.addColumn(cls.sqlmeta.table, column)
if cls._SO_finishedClassCreation:
makeProperties(cls)
@@ -633,7 +638,7 @@
def addColumnsFromDatabase(cls, connection=None):
conn = connection or cls._connection
- for columnDef in conn.columnsFromSchema(cls._table, cls):
+ for columnDef in conn.columnsFromSchema(cls.sqlmeta.table, cls):
alreadyExists = False
for c in cls._columns:
if c.name == columnDef.name:
@@ -674,7 +679,7 @@
if changeSchema:
conn = connection or cls._connection
- conn.delColumn(cls._table, column)
+ conn.delColumn(cls.sqlmeta.table, column)
if cls._SO_finishedClassCreation:
unmakeProperties(cls)
@@ -1090,7 +1095,7 @@
def _SO_fetchAlternateID(cls, dbIDName, value, connection=None):
result = (connection or cls._connection)._SO_selectOneAlt(
cls,
- [cls._idName] +
+ [cls.sqlmeta.idName] +
[col.dbName for col in cls._SO_columns],
dbIDName,
value)
@@ -1139,9 +1144,9 @@
def dropTable(cls, ifExists=False, dropJoinTables=True, cascade=False,
connection=None):
conn = connection or cls._connection
- if ifExists and not conn.tableExists(cls._table):
+ if ifExists and not conn.tableExists(cls.sqlmeta.table):
return
- conn.dropTable(cls._table, cascade)
+ conn.dropTable(cls.sqlmeta.table, cascade)
if dropJoinTables:
cls.dropJoinTables(ifExists=ifExists, connection=conn)
dropTable = classmethod(dropTable)
@@ -1150,7 +1155,7 @@
createIndexes=True,
connection=None):
conn = connection or cls._connection
- if ifNotExists and conn.tableExists(cls._table):
+ if ifNotExists and conn.tableExists(cls.sqlmeta.table):
return
conn.createTable(cls)
if createJoinTables:
@@ -1227,7 +1232,7 @@
# 3-03 @@: Maybe this should check the cache... but it's
# kind of crude anyway, so...
conn = connection or cls._connection
- conn.clearTable(cls._table)
+ conn.clearTable(cls.sqlmeta.table)
clearTable = classmethod(clearTable)
def destroySelf(self):
Modified: trunk/SQLObject/sqlobject/maxdb/maxdbconnection.py
===================================================================
--- trunk/SQLObject/sqlobject/maxdb/maxdbconnection.py 2005-02-16 04:30:22 UTC (rev 611)
+++ trunk/SQLObject/sqlobject/maxdb/maxdbconnection.py 2005-02-16 04:31:51 UTC (rev 612)
@@ -119,8 +119,8 @@
return conn
def _queryInsertID(self, conn, soInstance, id, names, values):
- table = soInstance._table
- idName = soInstance._idName
+ table = soInstance.sqlmeta.table
+ idName = soInstance.sqlmeta.idName
c = conn.cursor()
if id is None:
c.execute('SELECT %s.NEXTVAL FROM DUAL' % (self.createSequenceName(table)))
@@ -158,21 +158,21 @@
#i tried to use the transaction class but i get a recursion limit error
#t=self.transaction()
# t.query('CREATE TABLE %s (\n%s\n)' % \
- # (soClass._table, self.createColumns(soClass)))
+ # (soClass.sqlmeta.table, self.createColumns(soClass)))
#
- # t.query("CREATE SEQUENCE %s" % self.createSequenceName(soClass._table))
+ # t.query("CREATE SEQUENCE %s" % self.createSequenceName(soClass.sqlmeta.table))
# t.commit()
#so use transaction when the problem will be solved
self.query('CREATE TABLE %s (\n%s\n)' % \
- (soClass._table, self.createColumns(soClass)))
+ (soClass.sqlmeta.table, self.createColumns(soClass)))
self.query("CREATE SEQUENCE %s"
- % self.createSequenceName(soClass._table))
+ % self.createSequenceName(soClass.sqlmeta.table))
def createColumn(self, soClass, col):
return col.maxdbCreateSQL()
def createIDColumn(self, soClass):
- return '%s INT PRIMARY KEY' % soClass._idName
+ return '%s INT PRIMARY KEY' % soClass.sqlmeta.idName
def createIndexSQL(self, soClass, index):
return index.maxdbCreateIndexSQL(soClass)
Modified: trunk/SQLObject/sqlobject/mysql/mysqlconnection.py
===================================================================
--- trunk/SQLObject/sqlobject/mysql/mysqlconnection.py 2005-02-16 04:30:22 UTC (rev 611)
+++ trunk/SQLObject/sqlobject/mysql/mysqlconnection.py 2005-02-16 04:31:51 UTC (rev 612)
@@ -41,8 +41,8 @@
raise
def _queryInsertID(self, conn, soInstance, id, names, values):
- table = soInstance._table
- idName = soInstance._idName
+ table = soInstance.sqlmeta.table
+ idName = soInstance.sqlmeta.idName
c = conn.cursor()
if id is not None:
names = [idName] + names
@@ -71,7 +71,7 @@
return index.mysqlCreateIndexSQL(soClass)
def createIDColumn(self, soClass):
- return '%s INT PRIMARY KEY AUTO_INCREMENT' % soClass._idName
+ return '%s INT PRIMARY KEY AUTO_INCREMENT' % soClass.sqlmeta.idName
def joinSQLType(self, join):
return 'INT NOT NULL'
@@ -100,7 +100,7 @@
if field == 'id':
continue
colClass, kw = self.guessClass(t)
- kw['name'] = soClass._style.dbColumnToPythonAttr(field)
+ kw['name'] = soClass.sqlmeta.style.dbColumnToPythonAttr(field)
kw['notNone'] = not nullAllowed
kw['default'] = default
# @@ skip key...
Modified: trunk/SQLObject/sqlobject/postgres/pgconnection.py
===================================================================
--- trunk/SQLObject/sqlobject/postgres/pgconnection.py 2005-02-16 04:30:22 UTC (rev 611)
+++ trunk/SQLObject/sqlobject/postgres/pgconnection.py 2005-02-16 04:31:51 UTC (rev 612)
@@ -74,8 +74,8 @@
return conn
def _queryInsertID(self, conn, soInstance, id, names, values):
- table = soInstance._table
- idName = soInstance._idName
+ table = soInstance.sqlmeta.table
+ idName = soInstance.sqlmeta.idName
sequenceName = getattr(soInstance, '_idSequence',
'%s_%s_seq' % (table, idName))
c = conn.cursor()
@@ -106,7 +106,7 @@
return index.postgresCreateIndexSQL(soClass)
def createIDColumn(self, soClass):
- return '%s SERIAL PRIMARY KEY' % soClass._idName
+ return '%s SERIAL PRIMARY KEY' % soClass.sqlmeta.idName
def dropTable(self, tableName, cascade=False):
if self.server_version[:3] <= "7.2":
@@ -189,7 +189,7 @@
if field == primaryKey:
continue
colClass, kw = self.guessClass(t)
- kw['name'] = soClass._style.dbColumnToPythonAttr(field)
+ kw['name'] = soClass.sqlmeta.style.dbColumnToPythonAttr(field)
kw['notNone'] = notnull
if defaultstr is not None:
kw['default'] = getattr(sqlbuilder.const, defaultstr)
Modified: trunk/SQLObject/sqlobject/sqlbuilder.py
===================================================================
--- trunk/SQLObject/sqlobject/sqlbuilder.py 2005-02-16 04:30:22 UTC (rev 611)
+++ trunk/SQLObject/sqlobject/sqlbuilder.py 2005-02-16 04:31:51 UTC (rev 612)
@@ -329,16 +329,16 @@
def __init__(self, soClass):
self.soClass = soClass
- assert soClass._table, (
+ assert soClass.sqlmeta.table, (
"Bad table name in class %r: %r"
- % (soClass, soClass._table))
- Table.__init__(self, soClass._table)
+ % (soClass, soClass.sqlmeta.table))
+ Table.__init__(self, soClass.sqlmeta.table)
def __getattr__(self, attr):
if attr.startswith('__'):
raise AttributeError
if attr == 'id':
- return SQLObjectField(self.tableName, self.soClass._idName, attr)
+ return SQLObjectField(self.tableName, self.soClass.sqlmeta.idName, attr)
else:
return SQLObjectField(self.tableName,
self.soClass._SO_columnDict[attr].dbName,
Modified: trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py
===================================================================
--- trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py 2005-02-16 04:30:22 UTC (rev 611)
+++ trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py 2005-02-16 04:31:51 UTC (rev 612)
@@ -42,8 +42,8 @@
return self._conn
def _queryInsertID(self, conn, soInstance, id, names, values):
- table = soInstance._table
- idName = soInstance._idName
+ table = soInstance.sqlmeta.table
+ idName = soInstance.sqlmeta.idName
c = conn.cursor()
if id is not None:
names = [idName] + names
@@ -59,6 +59,15 @@
self.printDebug(conn, id, 'QueryIns', 'result')
return id
+ def _insertSQL(self, table, names, values):
+ if not names:
+ assert not values
+ # INSERT INTO table () VALUES () isn't allowed in
+ # SQLite (though it is in other databases)
+ return ("INSERT INTO %s VALUES (NULL)" % table)
+ else:
+ return DBAPI._insertSQL(self, table, names, values)
+
def _queryAddLimitOffset(self, query, start, end):
if not start:
return "%s LIMIT %i" % (query, end)
@@ -70,7 +79,7 @@
return col.sqliteCreateSQL()
def createIDColumn(self, soClass):
- return '%s INTEGER PRIMARY KEY' % soClass._idName
+ return '%s INTEGER PRIMARY KEY' % soClass.sqlmeta.idName
def joinSQLType(self, join):
return 'INT NOT NULL'
Modified: trunk/SQLObject/sqlobject/sresults.py
===================================================================
--- trunk/SQLObject/sqlobject/sresults.py 2005-02-16 04:30:22 UTC (rev 611)
+++ trunk/SQLObject/sqlobject/sresults.py 2005-02-16 04:31:51 UTC (rev 612)
@@ -11,7 +11,7 @@
clause = sqlbuilder.SQLTrueClause
self.clause = clause
tablesDict = sqlbuilder.tablesUsedDict(self.clause)
- tablesDict[sourceClass._table] = 1
+ tablesDict[sourceClass.sqlmeta.table] = 1
if clauseTables:
for table in clauseTables:
tablesDict[table] = 1
Modified: trunk/SQLObject/sqlobject/styles.py
===================================================================
--- trunk/SQLObject/sqlobject/styles.py 2005-02-16 04:30:22 UTC (rev 611)
+++ trunk/SQLObject/sqlobject/styles.py 2005-02-16 04:31:51 UTC (rev 612)
@@ -115,8 +115,8 @@
if dbConnection is None:
if hasattr(soClass, '_connection'):
dbConnection = soClass._connection
- if hasattr(soClass, '_style') and soClass._style:
- return soClass._style
+ if hasattr(soClass.sqlmeta, 'style') and soClass.sqlmeta.style:
+ return soClass.sqlmeta.style
elif dbConnection and dbConnection.style:
return dbConnection.style
else:
Modified: trunk/SQLObject/sqlobject/sybase/sybaseconnection.py
===================================================================
--- trunk/SQLObject/sqlobject/sybase/sybaseconnection.py 2005-02-16 04:30:22 UTC (rev 611)
+++ trunk/SQLObject/sqlobject/sybase/sybaseconnection.py 2005-02-16 04:31:51 UTC (rev 612)
@@ -69,8 +69,8 @@
return r is not None
def _queryInsertID(self, conn, soInstance, id, names, values):
- table = soInstance._table
- idName = soInstance._idName
+ table = soInstance.sqlmeta.table
+ idName = soInstance.sqlmeta.idName
c = conn.cursor()
if id is not None:
names = [idName] + names
@@ -103,7 +103,7 @@
return col.sybaseCreateSQL()
def createIDColumn(self, soClass):
- return '%s NUMERIC(18,0) IDENTITY UNIQUE' % soClass._idName
+ return '%s NUMERIC(18,0) IDENTITY UNIQUE' % soClass.sqlmeta.idName
def createIndexSQL(self, soClass, index):
return index.sybaseCreateIndexSQL(soClass)
@@ -138,7 +138,7 @@
if field == 'id':
continue
colClass, kw = self.guessClass(t)
- kw['name'] = soClass._style.dbColumnToPythonAttr(field)
+ kw['name'] = soClass.sqlmeta.style.dbColumnToPythonAttr(field)
kw['notNone'] = not nullAllowed
kw['default'] = default
# @@ skip key...
|
|
From: <sub...@co...> - 2005-02-16 04:30:26
|
Author: ianb Date: 2005-02-16 04:30:22 +0000 (Wed, 16 Feb 2005) New Revision: 611 Added: trunk/SQLObject/sqlobject/tests/test_create_drop.py Log: Explicitly test creation and dropping, including with ifExists and ifNotExists Added: trunk/SQLObject/sqlobject/tests/test_create_drop.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_create_drop.py 2005-02-16 04:29:41 UTC (rev 610) +++ trunk/SQLObject/sqlobject/tests/test_create_drop.py 2005-02-16 04:30:22 UTC (rev 611) @@ -0,0 +1,29 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +class TestCreateDrop(SQLObject): + class sqlmeta(sqlmeta): + idName = 'test_id_here' + table = 'test_create_drop_table' + name = StringCol() + number = IntCol() + time = DateTimeCol() + short = StringCol(length=10) + blobcol = BLOBCol() + + +def test_create_drop(): + conn = getConnection() + TestCreateDrop.setConnection(conn) + TestCreateDrop.dropTable(ifExists=True) + assert not conn.tableExists(TestCreateDrop.sqlmeta.table) + TestCreateDrop.createTable(ifNotExists=True) + assert conn.tableExists(TestCreateDrop.sqlmeta.table) + TestCreateDrop.createTable(ifNotExists=True) + assert conn.tableExists(TestCreateDrop.sqlmeta.table) + TestCreateDrop.dropTable(ifExists=True) + assert not conn.tableExists(TestCreateDrop.sqlmeta.table) + TestCreateDrop.dropTable(ifExists=True) + assert not conn.tableExists(TestCreateDrop.sqlmeta.table) + + |
|
From: <sub...@co...> - 2005-02-16 04:29:45
|
Author: ianb
Date: 2005-02-16 04:29:41 +0000 (Wed, 16 Feb 2005)
New Revision: 610
Modified:
trunk/SQLObject/sqlobject/tests/test_select.py
Log:
Excersize the lazyColumns option
Modified: trunk/SQLObject/sqlobject/tests/test_select.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_select.py 2005-02-16 04:28:29 UTC (rev 609)
+++ trunk/SQLObject/sqlobject/tests/test_select.py 2005-02-16 04:29:41 UTC (rev 610)
@@ -26,6 +26,13 @@
count += 1
assert count == len(names)
+def test_00b_lazy():
+ setupIter()
+ count = 0
+ for test in IterTest.select(lazyColumns=True):
+ count += 1
+ assert count == len(names)
+
def test_01_turn_to_list():
count = 0
for test in list(IterTest.select()):
@@ -46,6 +53,7 @@
test = all[i]
count += 1
assert count == len(names)
+
def test_04_indexed_ended_by_exception():
all = IterTest.select()
|
|
From: <sub...@co...> - 2005-02-16 04:28:33
|
Author: ianb
Date: 2005-02-16 04:28:29 +0000 (Wed, 16 Feb 2005)
New Revision: 609
Modified:
trunk/SQLObject/sqlobject/tests/test_auto.py
Log:
Fix a bunch of small issues, like badly named attributes, and names
that didn't exist.
Modified: trunk/SQLObject/sqlobject/tests/test_auto.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_auto.py 2005-02-16 04:26:43 UTC (rev 608)
+++ trunk/SQLObject/sqlobject/tests/test_auto.py 2005-02-16 04:28:29 UTC (rev 609)
@@ -1,6 +1,11 @@
from sqlobject import *
from sqlobject.tests.dbtest import *
from sqlobject import classregistry
+try:
+ from datetime import datetime
+ now = datetime.now
+except ImportError:
+ from mx.DateTime import now
########################################
## Dynamic column tests
@@ -115,29 +120,42 @@
DROP TABLE auto_test
"""
- _table = 'auto_test'
+ def setup_method(self, meth):
+ conn = getConnection()
+ dbName = conn.dbName
+ creator = getattr(self, dbName + 'Create', None)
+ if creator:
+ conn.query(creator)
+ def teardown_method(self, meth):
+ conn = getConnection()
+ dbName = conn.dbName
+ dropper = getattr(self, dbName + 'Drop', None)
+ if dropper:
+ conn.query(dropper)
+
def test_classCreate(self):
if not supports('fromDatabase'):
return
class AutoTest(SQLObject):
_fromDatabase = True
- _idName = 'auto_id'
- _connection = connection()
+ _connection = getConnection()
+ class sqlmeta(sqlmeta):
+ idName = 'auto_id'
john = AutoTest(firstName='john',
lastName='doe',
age=10,
created=now(),
wannahavefun=False,
- long_field='x'*1000)
+ longField='x'*1000)
jane = AutoTest(firstName='jane',
lastName='doe',
happy='N',
created=now(),
wannahavefun=True,
- long_field='x'*1000)
+ longField='x'*1000)
assert not john.wannahavefun
assert jane.wannahavefun
- assert john.long_field == 'x'*1000
- assert jane.long_field == 'x'*1000
+ assert john.longField == 'x'*1000
+ assert jane.longField == 'x'*1000
del classregistry.registry(AutoTest._registry).classes['AutoTest']
|
|
From: <sub...@co...> - 2005-02-16 04:26:45
|
Author: ianb
Date: 2005-02-16 04:26:43 +0000 (Wed, 16 Feb 2005)
New Revision: 608
Modified:
trunk/SQLObject/sqlobject/tests/dbtest.py
Log:
* Use new attributes
* Be more careful about supports -- negation supports weren't working
before (supports like '-dynamicColumns': 'dbs...')
Modified: trunk/SQLObject/sqlobject/tests/dbtest.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/dbtest.py 2005-02-16 04:25:58 UTC (rev 607)
+++ trunk/SQLObject/sqlobject/tests/dbtest.py 2005-02-16 04:26:43 UTC (rev 608)
@@ -96,7 +96,7 @@
reversed = list(soClasses)[:]
reversed.reverse()
for soClass in reversed:
- table = soClass._table
+ table = soClass.sqlmeta.table
if not soClass._connection.tableExists(table):
continue
items = list(cls.selectBy(
@@ -115,7 +115,7 @@
else:
cls.clear(soClass)
for soClass in soClasses:
- table = soClass._table
+ table = soClass.sqlmeta.table
if not soClass._connection.tableExists(table):
cls.install(soClass)
installOrClear = classmethod(installOrClear)
@@ -131,7 +131,7 @@
else:
sql = soClass.createTableSQL()
soClass.createTable()
- cls(tableName=soClass._table,
+ cls(tableName=soClass.sqlmeta.table,
createSQL=sql,
connectionURI=soClass._connection.uri())
install = classmethod(install)
@@ -158,7 +158,7 @@
"""
This sets up *this* table.
"""
- if not cls._connection.tableExists(cls._table):
+ if not cls._connection.tableExists(cls.sqlmeta.table):
cls.createTable()
setup = classmethod(setup)
@@ -216,11 +216,11 @@
notSupport = supportsMatrix.get('-' + feature, None)
if support is not None and dbName in support.split():
return True
- else:
+ elif support:
return False
if notSupport is not None and dbName in notSupport.split():
return False
- else:
+ elif notSupport:
return True
assert notSupport is not None or support is not None, (
"The supportMatrix does not list this feature: %r"
|
|
From: <sub...@co...> - 2005-02-16 04:26:01
|
Author: ianb
Date: 2005-02-16 04:25:58 +0000 (Wed, 16 Feb 2005)
New Revision: 607
Modified:
trunk/SQLObject/sqlobject/tests/test_transactions.py
Log:
Use proper setupClass() invocations
Modified: trunk/SQLObject/sqlobject/tests/test_transactions.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_transactions.py 2005-02-16 04:25:03 UTC (rev 606)
+++ trunk/SQLObject/sqlobject/tests/test_transactions.py 2005-02-16 04:25:58 UTC (rev 607)
@@ -10,10 +10,12 @@
name = StringCol(length=10, alternateID=True, dbName='name_col')
_defaultOrderBy = 'name'
-def testTransaction():
+def test_transaction():
if not supports('transactions'):
return
- setupClass(TestSOTrans, [d(name='bob'), d(name='tim')])
+ setupClass(TestSOTrans)
+ TestSOTrans(name='bob')
+ TestSOTrans(name='tim')
trans = TestSOTrans._connection.transaction()
try:
TestSOTrans._connection.autoCommit = 'exception'
|
|
From: <sub...@co...> - 2005-02-16 04:25:06
|
Author: ianb
Date: 2005-02-16 04:25:03 +0000 (Wed, 16 Feb 2005)
New Revision: 606
Modified:
trunk/SQLObject/sqlobject/tests/test_basic.py
Log:
Get the setupClass calls right so that it creates and destroys
tables in the right order
Modified: trunk/SQLObject/sqlobject/tests/test_basic.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-16 04:24:29 UTC (rev 605)
+++ trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-16 04:25:03 UTC (rev 606)
@@ -115,9 +115,7 @@
name = StringCol(length=10, dbName='name_col')
def test_foreignKeyDestroySelfCascade():
- setupClass(TestSO7)
- setupClass(TestSO6)
- setupClass(TestSO5)
+ setupClass([TestSO7, TestSO6, TestSO5])
tc5 = TestSO5(name='a')
tc6a = TestSO6(name='1')
@@ -204,8 +202,7 @@
name = StringCol(length=10, dbName='name_col')
def testForeignKeyDestroySelfRestrict():
- setupClass(TestSO9)
- setupClass(TestSO8)
+ setupClass([TestSO9, TestSO8])
tc8a = TestSO8(name='a')
tc9a = TestSO9(name='1')
|
|
From: <sub...@co...> - 2005-02-16 04:24:36
|
Author: ianb Date: 2005-02-16 04:24:29 +0000 (Wed, 16 Feb 2005) New Revision: 605 Added: trunk/SQLObject/sqlobject/tests/test_empty.py Log: Added a test for empty classes (classes with no columns) Added: trunk/SQLObject/sqlobject/tests/test_empty.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_empty.py 2005-02-16 03:06:30 UTC (rev 604) +++ trunk/SQLObject/sqlobject/tests/test_empty.py 2005-02-16 04:24:29 UTC (rev 605) @@ -0,0 +1,17 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +class EmptyClass(SQLObject): + + pass + +def test_empty(): + setupClass(EmptyClass) + e1 = EmptyClass() + e2 = EmptyClass() + assert e1 != e2 + assert e1.id != e2.id + assert e1 in list(EmptyClass.select()) + assert e2 in list(EmptyClass.select()) + e1.destroySelf() + assert list(EmptyClass.select()) == [e2] |
|
From: <sub...@co...> - 2005-02-16 03:06:34
|
Author: ianb
Date: 2005-02-16 03:06:30 +0000 (Wed, 16 Feb 2005)
New Revision: 604
Modified:
trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py
Log:
Renamed Person class to InherPerson, so its name doesn't conflict with
the name of the table in test_auto
Modified: trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py
===================================================================
--- trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py 2005-02-14 02:31:35 UTC (rev 603)
+++ trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py 2005-02-16 03:06:30 UTC (rev 604)
@@ -7,29 +7,29 @@
########################################
-class Person(InheritableSQLObject):
+class InherPerson(InheritableSQLObject):
_inheritable = 1 # I want this class to be inherited
firstName = StringCol()
lastName = StringCol()
-class Employee(Person):
+class Employee(InherPerson):
_inheritable = 0 # If I don't want this class to be inherited
position = StringCol()
def setup():
- setupClass(Person)
+ setupClass(InherPerson)
setupClass(Employee)
Employee(firstName='Ian', lastName='Bicking', position='Project leader')
- Person(firstName='Daniel', lastName='Savard')
+ InherPerson(firstName='Daniel', lastName='Savard')
def test_inheritance():
setup()
- persons = Person.select() # all
+ persons = InherPerson.select() # all
for person in persons:
- assert isinstance(person, Person)
+ assert isinstance(person, InherPerson)
if isinstance(person, Employee):
assert not hasattr(person, "childName")
else:
@@ -40,7 +40,7 @@
def test_inheritance_select():
setup()
- persons = Person.select(Person.q.firstName <> None)
+ persons = InherPerson.select(InherPerson.q.firstName <> None)
assert persons.count() == 2
employees = Employee.select(Employee.q.firstName <> None)
|
|
From: <sub...@co...> - 2005-02-11 12:24:38
|
Author: phd
Date: 2005-02-11 12:24:33 +0000 (Fri, 11 Feb 2005)
New Revision: 602
Modified:
trunk/SQLObject/setup.py
trunk/SQLObject/sqlobject/inheritance/__init__.py
Log:
Do not pass parent's values to the child's _create() to avoid double UPDATEing of these values.
Added 'inheritance' subpackage to setup.py.
Modified: trunk/SQLObject/setup.py
===================================================================
--- trunk/SQLObject/setup.py 2005-02-11 12:20:51 UTC (rev 601)
+++ trunk/SQLObject/setup.py 2005-02-11 12:24:33 UTC (rev 602)
@@ -2,7 +2,7 @@
import warnings
warnings.filterwarnings("ignore", "Unknown distribution option")
-subpackages = ['firebird', 'include', 'mysql', 'postgres',
+subpackages = ['firebird', 'include', 'inheritance', 'mysql', 'postgres',
'sqlite', 'sybase', 'maxdb']
import sys
Modified: trunk/SQLObject/sqlobject/inheritance/__init__.py
===================================================================
--- trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-11 12:20:51 UTC (rev 601)
+++ trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-11 12:24:33 UTC (rev 602)
@@ -178,11 +178,14 @@
#DSM: we must first create our parent
if self._parentClass:
parentClass = self._parentClass
- parent_kw = dict(
- [(name, value) for (name, value) in kw.items()
- if hasattr(parentClass, name)
- ]
- )
+ new_kw = {}
+ parent_kw = {}
+ for (name, value) in kw.items():
+ if hasattr(parentClass, name):
+ parent_kw[name] = value
+ else:
+ new_kw[name] = value
+ kw = new_kw
self._parent = parentClass(kw=parent_kw)
self._parent.childName = self.__class__.__name__
id = self._parent.id
|
|
From: <sub...@co...> - 2005-02-11 12:20:57
|
Author: phd
Date: 2005-02-11 12:20:51 +0000 (Fri, 11 Feb 2005)
New Revision: 601
Modified:
trunk/SQLObject/sqlobject/inheritance/__init__.py
trunk/SQLObject/sqlobject/tests/test_unicode.py
Log:
Minor bugfixes for Python 2.2.
Modified: trunk/SQLObject/sqlobject/inheritance/__init__.py
===================================================================
--- trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-10 19:46:14 UTC (rev 600)
+++ trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-11 12:20:51 UTC (rev 601)
@@ -4,6 +4,13 @@
import iteration
+try:
+ basestring
+except NameError: # Python 2.2
+ import types
+ basestring = (types.StringType, types.UnicodeType)
+
+
class InheritableSelectResults(SelectResults):
IterationClass = iteration.InheritableIteration
Modified: trunk/SQLObject/sqlobject/tests/test_unicode.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_unicode.py 2005-02-10 19:46:14 UTC (rev 600)
+++ trunk/SQLObject/sqlobject/tests/test_unicode.py 2005-02-11 12:20:51 UTC (rev 601)
@@ -10,6 +10,15 @@
col1 = UnicodeCol()
col2 = UnicodeCol(dbEncoding='latin-1')
+try:
+ enumerate
+except NameError: # Python 2.2
+ def enumerate(list):
+ new_list = []
+ for i in range(len(list)):
+ new_list.append((i, list[0]))
+ return new_list
+
def test_create():
setupClass(Unicode1)
data = [u'\u00f0', u'test', 'ascii test']
|
|
From: <sub...@co...> - 2005-02-10 19:46:25
|
Author: phd Date: 2005-02-10 19:46:14 +0000 (Thu, 10 Feb 2005) New Revision: 600 Added: trunk/SQLObject/docs/Inheritance.txt trunk/SQLObject/sqlobject/inheritance/ trunk/SQLObject/sqlobject/inheritance/__init__.py trunk/SQLObject/sqlobject/inheritance/iteration.py trunk/SQLObject/sqlobject/inheritance/tests/ trunk/SQLObject/sqlobject/inheritance/tests/__init__.py trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py Modified: trunk/SQLObject/docs/Authors.txt trunk/SQLObject/docs/FAQ.txt trunk/SQLObject/sqlobject/classregistry.py trunk/SQLObject/sqlobject/col.py trunk/SQLObject/sqlobject/dbconnection.py trunk/SQLObject/sqlobject/main.py trunk/SQLObject/sqlobject/sresults.py Log: Inheritance patch! Modified: trunk/SQLObject/docs/Authors.txt =================================================================== --- trunk/SQLObject/docs/Authors.txt 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/docs/Authors.txt 2005-02-10 19:46:14 UTC (rev 600) @@ -13,4 +13,7 @@ * James Ralston <jralston at hotmail.com> * Sidnei da Silva <sidnei at awkly.org> * Brad Bollenbach <brad at bbnet.ca> +* Daniel Savard, Xsoli Inc <sqlobject at xsoli.com> +* alexander smishlajev <alex at ank-sia.com> +* Yaroslav Samchuk <yarcat at ank-sia.com> * Oleg Broytmann <phd at phd.pp.ru> Modified: trunk/SQLObject/docs/FAQ.txt =================================================================== --- trunk/SQLObject/docs/FAQ.txt 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/docs/FAQ.txt 2005-02-10 19:46:14 UTC (rev 600) @@ -107,6 +107,11 @@ anyway). +There is also another kind of inheritance. See Inheritance.txt_ + +.. _Inheritance.txt: Inheritance.txt + + Composite/Compound Attributes ----------------------------- Added: trunk/SQLObject/docs/Inheritance.txt =================================================================== --- trunk/SQLObject/docs/Inheritance.txt 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/docs/Inheritance.txt 2005-02-10 19:46:14 UTC (rev 600) @@ -0,0 +1,134 @@ +:Author: Daniel Savard, XSOLI Inc. + +Inheritance +----------- + +* As suggested by Ian Bicking, each child class now has the same + ID than the parent class. No more need for childID column and + parent foreignKey (with a small speed boost). +* No more need to call getSubClass as the 'latest' child will always + be returned when an instance of a class is created. +* This version now seems to works correctly with addColumn, delColumn, + addJoin and delJoin. + + +The following code:: + + class Person(SQLObject): + _inheritable = 1 # I want this class to be inherited + firstName = StringCol() + lastName = StringCol() + + class Employee(Person): + _inheritable = 0 # If I don't want this class to be inherited + position = StringCol() + +will generate the following tables:: + + CREATE TABLE person ( + id INT PRIMARY KEY, + child_name TEXT, + first_name TEXT, + last_name TEXT + ); + + CREATE TABLE employee ( + id INT PRIMARY KEY, + position TEXT + ) + + +A new class attribute ``_inheritable`` is added. When this new +attribute is set to 1, the class is marked 'inheritable' and a new +columns will automatically be added: childName (TEXT). + +Each class that inherits from a parent class will get the same ID as +the parent class. So, there is no need to keep track of parent ID and +child ID as they are the same. + +The column childName will contain the name of the child class (for +exemple 'Employee'). This will permit to a class to always return its +child class if available (a person that is also an employee will always +return an instance of the employee class). + +For exemple, the following code:: + + p = Person(firstName='John', lastName='Doe') + e = Employee(firstName='Jane', lastName='Doe', position='Chief') + p2 = Person.get(1) + +Will create the following data in the database:: + + *Person* + id + child_name + first_name + last_name + 0 + Null + John + Doe + 1 + Employee + Jane + Doe + + + *Employee* + id + position + 1 + Chief + + +You will still be able to ask for the attribute normally: +e.firstName will return Jane and setting it will write the new value in +the person table. + +If you use p2, as p2 is a person object, you will get an employee +object. +person(0) will return a Person instance and will have the following +attributes: firstName and lastName +person(1) or employee(1) will each return the same Employee instance and +will have the following attributes: firstName, lastName and position + +Also, deleting a person or an employee that are linked will destroy +both entries as one would expect. + +The SQLObject q magic also work. Using this select are valid:: + + Employee.select(AND(Employee.q.firstName == 'Jane' Employee.q.position == 'Chief')) will return Jane Doe + Employee.select(AND(Person.q.firstName == 'Jane', Employee.q.position == 'Chief')) will return Jane Doe + Employee.select(Employee.q.lastName == 'Doe') will only return Jane Doe (as Joe isn't an employee) + Person.select(Person.q.lastName == 'Doe') will return both entries. + +The SQL 'where' clause will contain additional clauses when used with +'inherited' classes. These clauses are the link between the id and the +parent id. This will look like the following request:: + + SELECT employee.id, person.first_name, person.last_name + FROM person, employee WHERE person.first_name = 'Jane' + AND employee.position = 'Chief' AND person.id = employee.id + +Some limitation or notice about this version: + +* Only simple inheritance will work. It is not possible to inherit + from multiple SQLObject classes. +* It is possible to inherit from an inherited class and this will + work well. In the above exemple, you can have a Chief class that + inherits from Employee and all parents attributes will be + available through the Chief class. +* You may not redefine columns in an inherited class (this + will raise an exception). +* If you don't want 'childName' columns in your last class (one that + will never be inherited), you must set '_inheritable' to 0 in this + class. +* I made it because I needed to be able to have automatic + inheritance with linked table. +* This version works for me, it may not works for you. I tried to do + my best but it is possible that I broke some things... So, there + is no warranty that this version will work. +* Thanks to Ian Bicking for SQLObject, this is a wonderful python + module. +* If you have suggestion, bugs, or patch to this patch, you can + contact me at <sqlobject xsoli.com> Modified: trunk/SQLObject/sqlobject/classregistry.py =================================================================== --- trunk/SQLObject/sqlobject/classregistry.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/classregistry.py 2005-02-10 19:46:14 UTC (rev 600) @@ -110,3 +110,6 @@ MasterRegistry = _MasterRegistry() registry = MasterRegistry.registry + +def findClass(name, class_registry=None): + return registry(class_registry).getClass(name) Modified: trunk/SQLObject/sqlobject/col.py =================================================================== --- trunk/SQLObject/sqlobject/col.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/col.py 2005-02-10 19:46:14 UTC (rev 600) @@ -28,6 +28,7 @@ # arguments in this module, so we rename it: import constraints as consts from include import validators +from classregistry import findClass NoDefault = sqlbuilder.NoDefault True, False = 1==1, 0==1 @@ -616,7 +617,6 @@ SOKeyCol.__init__(self, **kw) def postgresCreateSQL(self): - from main import findClass sql = SOKeyCol.postgresCreateSQL(self) other = findClass(self.foreignKey) tName = other._table @@ -642,7 +642,6 @@ return sql def sybaseCreateSQL(self): - from sqlobject.main import findClass sql = SOKeyCol.sybaseCreateSQL(self) other = findClass(self.foreignKey) tName = other._table @@ -654,7 +653,6 @@ return sql def maxdbCreateSQL(self): - from main import findClass other = findClass(self.foreignKey) fidName = self.dbName #I assume that foreign key name is identical to the id of the reference table Modified: trunk/SQLObject/sqlobject/dbconnection.py =================================================================== --- trunk/SQLObject/sqlobject/dbconnection.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/dbconnection.py 2005-02-10 19:46:14 UTC (rev 600) @@ -15,6 +15,7 @@ from converters import sqlrepr import urllib import weakref +from classregistry import findClass warnings.filterwarnings("ignore", "DB-API extension cursor.lastrowid used") @@ -246,7 +247,7 @@ return self._runWithConnection(self._queryInsertID, soInstance, id, names, values) def iterSelect(self, select): - return Iteration(self, self.getConnection(), + return select.IterationClass(self, self.getConnection(), select, keepConnection=False) def accumulateSelect(self, select, expression): @@ -542,9 +543,7 @@ def __del__(self): self._cleanup() - - class Transaction(object): def __init__(self, dbConnection): @@ -581,7 +580,7 @@ # still iterating through the results. # @@: But would it be okay for psycopg, with threadsafety # level 2? - return iter(list(Iteration(self, self._connection, + return iter(list(select.IterationClass(self, self._connection, select, keepConnection=True))) def commit(self): Added: trunk/SQLObject/sqlobject/inheritance/__init__.py =================================================================== --- trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-10 19:46:14 UTC (rev 600) @@ -0,0 +1,192 @@ +from sqlobject import sqlbuilder +from sqlobject import classregistry +from sqlobject.main import SQLObject, SelectResults, True, False, makeProperties, getterName, setterName +import iteration + + +class InheritableSelectResults(SelectResults): + IterationClass = iteration.InheritableIteration + + def __init__(self, sourceClass, clause, clauseTables=None, + **ops): + if clause is None or isinstance(clause, str) and clause == 'all': + clause = sqlbuilder.SQLTrueClause + tablesDict = sqlbuilder.tablesUsedDict(clause) + tablesDict[sourceClass._table] = 1 + orderBy = ops.get('orderBy') + if orderBy and not isinstance(orderBy, basestring): + tablesDict.update(sqlbuilder.tablesUsedDict(orderBy)) + #DSM: if this class has a parent, we need to link it + #DSM: and be sure the parent is in the table list. + #DSM: The following code is before clauseTables + #DSM: because if the user uses clauseTables + #DSM: (and normal string SELECT), he must know what he wants + #DSM: and will do himself the relationship between classes. + if type(clause) is not str: + tableRegistry = {} + allClasses = classregistry.registry(sourceClass._registry).allClasses() + for registryClass in allClasses: + if registryClass._table in tablesDict: + #DSM: By default, no parents are needed for the clauses + tableRegistry[registryClass] = registryClass + for registryClass in allClasses: + if registryClass._table in tablesDict: + currentClass = registryClass + while currentClass._parentClass: + currentClass = currentClass._parentClass + if tableRegistry.has_key(currentClass): + #DSM: Must keep the last parent needed + #DSM: (to limit the number of join needed) + tableRegistry[registryClass] = currentClass + #DSM: Remove this class as it is a parent one + #DSM: of a needed children + del tableRegistry[currentClass] + #DSM: Table registry contains only the last children + #DSM: or standalone classes + parentClause = [] + for (currentClass, minParentClass) in tableRegistry.items(): + while currentClass != minParentClass and currentClass._parentClass: + parentClass = currentClass._parentClass + parentClause.append(currentClass.q.id == parentClass.q.id) + currentClass = parentClass + tablesDict[currentClass._table] = 1 + clause = reduce(sqlbuilder.AND, parentClause, clause) + + super(InheritableSelectResults, self).__init__(sourceClass, clause, clauseTables, + **ops) + + +class InheritableSQLObject(SQLObject): + SelectResultsClass = InheritableSelectResults + + def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False): + + val = super(InheritableSQLObject, cls).get(id, connection, selectResults) + + #DSM: If we are updating a child, we should never return a child... + if childUpdate: return val + #DSM: If this class has a child, return the child + if hasattr(val, 'childName'): + childName = val.childName + if childName is not None: + return val._childClasses[childName].get(id, selectResults=childResults) + #DSM: Now, we know we are alone or the last child in a family... + #DSM: It's time to find our parents + inst = val + while inst._parentClass and not inst._parent: + inst._parent = inst._parentClass.get(id, childUpdate=True) + inst = inst._parent + #DSM: We can now return ourself + return val + + get = classmethod(get) + + def addColumn(cls, columnDef, changeSchema=False, connection=None, childUpdate=False): + #DSM: Try to add parent properties to the current class + #DSM: Only do this once if possible at object creation and once for + #DSM: each new dynamic column to refresh the current class + if childUpdate or cls._parentClass: + for col in cls._parentClass._columns: + cname = col.name + if cname == 'childName': continue + setattr(cls, getterName(cname), eval( + 'lambda self: self._parent.%s' % cname)) + if not col.kw.has_key('immutable') or not col.kw['immutable']: + setattr(cls, setterName(cname), eval( + 'lambda self, val: setattr(self._parent, %s, val)' + % repr(cname))) + if childUpdate: + makeProperties(cls) + return + + super(InheritableSQLObject, cls).addColumn(columnDef, changeSchema, connection) + + #DSM: Update each child class if needed and existing (only for new + #DSM: dynamic column as no child classes exists at object creation) + for c in cls._childClasses.values(): + c.addColumn(columnDef, childUpdate=True) + + addColumn = classmethod(addColumn) + + def delColumn(cls, column, changeSchema=False, connection=None): + super(InheritableSQLObject, cls).delColumn(column, changeSchema, connection) + + #DSM: Update each child class if needed + #DSM: and delete properties for this column + for c in cls._childClasses.values(): + delattr(c, name) + + delColumn = classmethod(delColumn) + + def addJoin(cls, joinDef, childUpdate=False): + #DSM: Try to add parent properties to the current class + #DSM: Only do this once if possible at object creation and once for + #DSM: each new dynamic join to refresh the current class + if childUpdate or cls._parentClass: + for jdef in cls._parentClass._joins: + join = jdef.withClass(cls) + jname = join.joinMethodName + jarn = join.addRemoveName + setattr(cls, getterName(jname), + eval('lambda self: self._parent.%s' % jname)) + if hasattr(join, 'remove'): + setattr(cls, 'remove' + jarn, + eval('lambda self,o: self._parent.remove%s(o)' % jarn)) + if hasattr(join, 'add'): + setattr(cls, 'add' + jarn, + eval('lambda self,o: self._parent.add%s(o)' % jarn)) + if childUpdate: + makeProperties(cls) + return + + super(InheritableSQLObject, cls).addJoin(joinDef) + + #DSM: Update each child class if needed and existing (only for new + #DSM: dynamic join as no child classes exists at object creation) + for c in cls._childClasses.values(): + c.addJoin(joinDef, childUpdate=True) + + addJoin = classmethod(addJoin) + + def delJoin(cls, joinDef): + super(InheritableSQLObject, cls).delJoin(joinDef) + + #DSM: Update each child class if needed + #DSM: and delete properties for this join + for c in cls._childClasses.values(): + delattr(c, meth) + + delJoin = classmethod(delJoin) + + def _create(self, id, **kw): + + #DSM: If we were called by a children class, + #DSM: we must retreive the properties dictionary. + #DSM: Note: we can't use the ** call paremeter directly + #DSM: as we must be able to delete items from the dictionary + #DSM: (and our children must know that the items were removed!) + if kw.has_key('kw'): + kw = kw['kw'] + #DSM: If we are the children of an inheritable class, + #DSM: we must first create our parent + if self._parentClass: + parentClass = self._parentClass + parent_kw = dict( + [(name, value) for (name, value) in kw.items() + if hasattr(parentClass, name) + ] + ) + self._parent = parentClass(kw=parent_kw) + self._parent.childName = self.__class__.__name__ + id = self._parent.id + + super(InheritableSQLObject, self)._create(id, **kw) + + def destroySelf(self): + #DSM: If this object has parents, recursivly kill them + if hasattr(self, '_parent') and self._parent: + self._parent.destroySelf() + super(InheritableSQLObject, self).destroySelf() + + +__all__ = ['InheritableSQLObject'] Added: trunk/SQLObject/sqlobject/inheritance/iteration.py =================================================================== --- trunk/SQLObject/sqlobject/inheritance/iteration.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/inheritance/iteration.py 2005-02-10 19:46:14 UTC (rev 600) @@ -0,0 +1,76 @@ +from sqlobject import sqlbuilder +from sqlobject.classregistry import findClass +from sqlobject.dbconnection import Iteration + +class InheritableIteration(Iteration): + #phd: default array size for cursor.fetchmany() + defaultArraySize = 10000 + + def __init__(self, dbconn, rawconn, select, keepConnection=False): + super(InheritableIteration, self).__init__(dbconn, rawconn, select, keepConnection) + self.cursor.arraysize = self.defaultArraySize + self._results = [] + #phd: find the index of the childName column + childNameIdx = None + columns = select.sourceClass._SO_columns + for i in range(len(columns)): #phd: enumerate() is unavailable python 2.2 + if columns[i].name == "childName": + childNameIdx = i + break + self._childNameIdx = childNameIdx + + def next(self): + lazyColumns = self.select.ops.get('lazyColumns', 0) + if not self._results: + self._results = list(self.cursor.fetchmany()) + if not lazyColumns: self.fetchChildren() + if not self._results: + self._cleanup() + raise StopIteration + result = self._results[0] + del self._results[0] + if lazyColumns: + obj = self.select.sourceClass.get(result[0], connection=self.dbconn) + return obj + else: + id = result[0] + if id in self._childrenResults: + childResults = self._childrenResults[id] + del self._childrenResults[id] + else: + childResults = None + obj = self.select.sourceClass.get(id, selectResults=result[1:], + childResults=childResults, connection=self.dbconn) + return obj + + def fetchChildren(self): + """Prefetch childrens' data + + Fetch childrens' data for every subclass in one big .select() + to avoid .get() fetching it one by one. + """ + self._childrenResults = {} + if self._childNameIdx is None: + return + childIdsNames = {} + childNameIdx = self._childNameIdx + for result in self._results: + childName = result[childNameIdx+1] + if childName: + ids = childIdsNames.get(childName) + if ids is None: + ids = childIdsNames[childName] = [] + ids.append(result[0]) + dbconn = self.dbconn + rawconn = self.rawconn + cursor = rawconn.cursor() + registry = self.select.sourceClass._registry + for childName, ids in childIdsNames.items(): + klass = findClass(childName, registry) + select = klass.select(sqlbuilder.IN(sqlbuilder.SQLConstant("id"), ids)) + query = dbconn.queryForSelect(select) + if dbconn.debug: + dbconn.printDebug(rawconn, query, 'Select children of the class %s' % childName) + self.dbconn._executeRetry(rawconn, cursor, query) + for result in cursor.fetchall(): + self._childrenResults[result[0]] = result[1:] Added: trunk/SQLObject/sqlobject/inheritance/tests/__init__.py =================================================================== --- trunk/SQLObject/sqlobject/inheritance/tests/__init__.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/inheritance/tests/__init__.py 2005-02-10 19:46:14 UTC (rev 600) @@ -0,0 +1 @@ +# Added: trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py =================================================================== --- trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py 2005-02-10 19:46:14 UTC (rev 600) @@ -0,0 +1,50 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * +from sqlobject.inheritance import InheritableSQLObject + +######################################## +## Inheritance +######################################## + + +class Person(InheritableSQLObject): + _inheritable = 1 # I want this class to be inherited + firstName = StringCol() + lastName = StringCol() + +class Employee(Person): + _inheritable = 0 # If I don't want this class to be inherited + position = StringCol() + +def setup(): + setupClass(Person) + setupClass(Employee) + + Employee(firstName='Ian', lastName='Bicking', position='Project leader') + Person(firstName='Daniel', lastName='Savard') + + +def test_inheritance(): + setup() + + persons = Person.select() # all + for person in persons: + assert isinstance(person, Person) + if isinstance(person, Employee): + assert not hasattr(person, "childName") + else: + assert hasattr(person, "childName") + assert not person.childName + + +def test_inheritance_select(): + setup() + + persons = Person.select(Person.q.firstName <> None) + assert persons.count() == 2 + + employees = Employee.select(Employee.q.firstName <> None) + assert employees.count() == 1 + + employees = Employee.select(Employee.q.position <> None) + assert employees.count() == 1 Modified: trunk/SQLObject/sqlobject/main.py =================================================================== --- trunk/SQLObject/sqlobject/main.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/main.py 2005-02-10 19:46:14 UTC (rev 600) @@ -4,6 +4,14 @@ SQLObject is a object-relational mapper. See SQLObject.html or SQLObject.txt for more. +Modified by + Daniel Savard, Xsoli Inc <sqlobject xsoli.com> 7 Feb 2004 + - Added support for simple table inheritance. + + Oleg Broytmann, SIA "ANK" <ph...@ph...> 3 Feb 2005 + - Split inheritance support into a number of separate modules and classes - + InheritableSQLObject at al. + This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the @@ -110,10 +118,6 @@ delFunc(obj, var) break -def findClass(name, registry=None): - #assert classRegistry.get(registry, {}).has_key(name), "No class by the name %s found (I have %s)" % (repr(name), ', '.join(map(str, classRegistry.keys()))) - return classregistry.registry(registry).getClass(name) - def findDependencies(name, registry=None): depends = [] for klass in classregistry.registry(registry).allClasses(): @@ -181,8 +185,8 @@ def __get__(self, obj, type=None): return getattr((type or obj).sqlmeta, self.name) - + # @@: This should become a public interface or documented or # something. Turning it on gives earlier warning about things # that will be deprecated (having this off we won't flood people @@ -260,6 +264,17 @@ sqlmeta = sqlmeta + #DSM: The _inheritable attribute controls wheter the class can by + #DSM: inherited 'logically' with a foreignKey and back reference. + _inheritable = False # Does this class is inheritable + _parentClass = None # A reference to the parent class + _parent = None # A reference to the parent instance + _childClasses = {} # Reference to child classes + childName = None # Children name (to be able to get a subclass) + + # The law of Demeter: the class should not call another classes by name + SelectResultsClass = SelectResults + def __classinit__(cls, new_attrs): # This is true if we're initializing the SQLObject class, @@ -329,6 +344,40 @@ # superclass's _columns list, so we make a copy if necessary if not new_attrs.has_key('_columns'): cls._columns = cls._columns[:] + + #DSM: Need to know very soon if the class is a child of an + #DSM: inheritable class. If so, we keep a link to our parent class. + cls._childClasses = {} + for _cls in cls.__bases__: + if hasattr(_cls, '_inheritable') and _cls._inheritable: + cls._parentClass = _cls + cls._parent = None + _cls._childClasses[cls.__name__] = cls + + #DSM: If this class is a child of a parent class, we need to do some + #DSM: attribute check and a a foreign key to the parent. + if cls._parentClass: + #DSM: First, look for invalid column name: + #DSM: reserved ones or same as a parent + parentCols = [column.name for column in cls._columns] + for column in implicitColumns: + cname = column.name + if cname in ['childName']: + raise AttributeError, \ + "The column name '%s' is reserved" % cname + if cname in parentCols: + raise AttributeError, "The column '%s' is already " \ + "defined in an inheritable parent" % cname + #DSM: Remove columns if inherited from an inheritable class + #DSM: as we don't want them. All we want is a foreign key + #DSM: that will link to our parent + cls._columns = [] + #DSM: If this is inheritable, add some default columns + # to be able to link to children + if hasattr(cls, '_inheritable') and cls._inheritable: + cls._columns.append( + col.StringCol(name='childName',default=None)) + cls._columns.extend(implicitColumns) if not new_attrs.has_key('_joins'): cls._joins = cls._joins[:] @@ -419,6 +468,15 @@ if not is_base: cls.q = sqlbuilder.SQLObjectTable(cls) + #DSM: If we are a child, get the q magic from the parent + currentClass = cls + while currentClass._parentClass: + currentClass = currentClass._parentClass + for column in currentClass._columns: + if type(column) == col.ForeignKey: continue + setattr(cls.q, column.name, + getattr(currentClass.q, column.name)) + classregistry.registry(cls._registry).addClass(cls) _style = _sqlmeta_attr('style') @@ -488,7 +546,7 @@ # Here if the _get_columnName method isn't in the # definition, we add it with the default # _SO_get_columnName definition. - if not hasattr(cls, getterName(name)): + if not hasattr(cls, getterName(name)) or (name == 'childName'): setattr(cls, getterName(name), getter) cls._SO_plainGetters[name] = 1 @@ -506,7 +564,7 @@ setattr(cls, '_SO_toPython_%s' % name, column.toPython) setattr(cls, rawSetterName(name), setter) # Then do the aliasing - if not hasattr(cls, setterName(name)): + if not hasattr(cls, setterName(name)) or (name == 'childName'): setattr(cls, setterName(name), setter) # We keep track of setters that haven't been # overridden, because we can combine these @@ -1060,7 +1118,7 @@ lazyColumns=False, reversed=False, distinct=False, connection=None): - return SelectResults(cls, clause, + return cls.SelectResultsClass(cls, clause, clauseTables=clauseTables, orderBy=orderBy, limit=limit, @@ -1072,7 +1130,7 @@ def selectBy(cls, connection=None, **kw): conn = connection or cls._connection - return SelectResults(cls, + return cls.SelectResultsClass(cls, conn._SO_columnClause(cls, kw), connection=conn) Modified: trunk/SQLObject/sqlobject/sresults.py =================================================================== --- trunk/SQLObject/sqlobject/sresults.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/sresults.py 2005-02-10 19:46:14 UTC (rev 600) @@ -1,6 +1,8 @@ import sqlbuilder +import dbconnection class SelectResults(object): + IterationClass = dbconnection.Iteration def __init__(self, sourceClass, clause, clauseTables=None, **ops): |
|
From: <sub...@co...> - 2005-02-10 18:46:12
|
Author: phd Date: 2005-02-10 18:46:08 +0000 (Thu, 10 Feb 2005) New Revision: 599 Added: home/phd/SQLObject/inheritance/sqlobject/sresults.py Modified: home/phd/SQLObject/inheritance/sqlobject/main.py Log: Merged patch from revision 598: class SelectResults has been moved to sresults.py. Modified: home/phd/SQLObject/inheritance/sqlobject/main.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-10 18:32:10 UTC (rev 598) +++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-10 18:46:08 UTC (rev 599) @@ -4,6 +4,14 @@ SQLObject is a object-relational mapper. See SQLObject.html or SQLObject.txt for more. +Modified by + Daniel Savard, Xsoli Inc <sqlobject xsoli.com> 7 Feb 2004 + - Added support for simple table inheritance. + + Oleg Broytmann, SIA "ANK" <ph...@ph...> 3 Feb 2005 + - Split inheritance support into a number of separate modules and classes - + InheritableSQLObject at al. + This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the @@ -31,6 +39,7 @@ import index import classregistry import declarative +from sresults import SelectResults import sys if sys.version_info[:3] < (2, 2, 0): @@ -195,164 +204,6 @@ if warnings_level is not None and warnings_level <= level: warnings.warn(message, DeprecationWarning, stacklevel=2) -class SelectResults(object): - IterationClass = dbconnection.Iteration - - def __init__(self, sourceClass, clause, clauseTables=None, - **ops): - self.sourceClass = sourceClass - if clause is None or isinstance(clause, str) and clause == 'all': - clause = sqlbuilder.SQLTrueClause - self.clause = clause - tablesDict = sqlbuilder.tablesUsedDict(self.clause) - tablesDict[sourceClass._table] = 1 - if clauseTables: - for table in clauseTables: - tablesDict[table] = 1 - self.clauseTables = clauseTables - self.tables = tablesDict.keys() - self.ops = ops - if self.ops.get('orderBy', NoDefault) is NoDefault: - self.ops['orderBy'] = sourceClass._defaultOrder - orderBy = self.ops['orderBy'] - if isinstance(orderBy, list) or isinstance(orderBy, tuple): - orderBy = map(self._mungeOrderBy, orderBy) - else: - orderBy = self._mungeOrderBy(orderBy) - #print "OUT: %r; in: %r" % (sourceClass.sqlrepr(orderBy), sourceClass.sqlrepr(self.ops['orderBy'])) - self.ops['dbOrderBy'] = orderBy - if ops.has_key('connection') and ops['connection'] is None: - del ops['connection'] - - def _mungeOrderBy(self, orderBy): - if isinstance(orderBy, str) and orderBy.startswith('-'): - orderBy = orderBy[1:] - desc = True - else: - desc = False - if isinstance(orderBy, (str, unicode)): - if self.sourceClass._SO_columnDict.has_key(orderBy): - val = self.sourceClass._SO_columnDict[orderBy].dbName - if desc: - return '-' + val - else: - return val - else: - if desc: - return '-' + orderBy - else: - return orderBy - else: - return orderBy - - def clone(self, **newOps): - ops = self.ops.copy() - ops.update(newOps) - return self.__class__(self.sourceClass, self.clause, - self.clauseTables, **ops) - - def orderBy(self, orderBy): - return self.clone(orderBy=orderBy) - - def connection(self, conn): - return self.clone(connection=conn) - - def limit(self, limit): - return self[:limit] - - def lazyColumns(self, value): - return self.clone(lazyColumns=value) - - def reversed(self): - return self.clone(reversed=not self.ops.get('reversed', False)) - - def distinct(self): - return self.clone(distinct=True) - - def __getitem__(self, value): - if type(value) is type(slice(1)): - assert not value.step, "Slices do not support steps" - if not value.start and not value.stop: - # No need to copy, I'm immutable - return self - - # Negative indexes aren't handled (and everything we - # don't handle ourselves we just create a list to - # handle) - if (value.start and value.start < 0) \ - or (value.stop and value.stop < 0): - if value.start: - if value.stop: - return list(self)[value.start:value.stop] - return list(self)[value.start:] - return list(self)[:value.stop] - - - if value.start: - assert value.start >= 0 - start = self.ops.get('start', 0) + value.start - if value.stop is not None: - assert value.stop >= 0 - if value.stop < value.start: - # an empty result: - end = start - else: - end = value.stop + self.ops.get('start', 0) - if self.ops.get('end', None) is not None \ - and value['end'] < end: - # truncated by previous slice: - end = self.ops['end'] - else: - end = self.ops.get('end', None) - else: - start = self.ops.get('start', 0) - end = value.stop + start - if self.ops.get('end', None) is not None \ - and self.ops['end'] < end: - end = self.ops['end'] - return self.clone(start=start, end=end) - else: - if value < 0: - return list(iter(self))[value] - else: - start = self.ops.get('start', 0) + value - return list(self.clone(start=start, end=start+1))[0] - - def __iter__(self): - conn = self.ops.get('connection', self.sourceClass._connection) - return conn.iterSelect(self) - - def accumulate(self, expression): - """ Use an accumulate expression to select result - using another SQL select through current - connection. - Return the accumulate result - """ - conn = self.ops.get('connection', self.sourceClass._connection) - return conn.accumulateSelect(self, expression) - - def count(self): - """ Counting elements of current select results """ - assert not self.ops.get('distinct'), "It is not currently supported to count distinct objects" - - count = self.accumulate('COUNT(*)') - if self.ops.get('start'): - count -= self.ops['start'] - if self.ops.get('end'): - count = min(self.ops['end'] - self.ops.get('start', 0), count) - return count - - def sum(self, attribute): - """ Making the sum of a given select result attribute. - `attribute` can be a column name (like 'a_column') - or a dot-q attribute (like Table.q.aColumn) - """ - if type(attribute) == type(''): - expression = 'SUM(%s)' % attribute - else: - expression = sqlbuilder.func.SUM(attribute) - return self.accumulate(expression) - # SQLObject is the superclass for all SQLObject classes, of # course. All the deeper magic is done in MetaSQLObject, and # only lesser magic is done here. All the actual work is done Added: home/phd/SQLObject/inheritance/sqlobject/sresults.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/sresults.py 2005-02-10 18:32:10 UTC (rev 598) +++ home/phd/SQLObject/inheritance/sqlobject/sresults.py 2005-02-10 18:46:08 UTC (rev 599) @@ -0,0 +1,161 @@ +import sqlbuilder +import dbconnection + +class SelectResults(object): + IterationClass = dbconnection.Iteration + + def __init__(self, sourceClass, clause, clauseTables=None, + **ops): + self.sourceClass = sourceClass + if clause is None or isinstance(clause, str) and clause == 'all': + clause = sqlbuilder.SQLTrueClause + self.clause = clause + tablesDict = sqlbuilder.tablesUsedDict(self.clause) + tablesDict[sourceClass._table] = 1 + if clauseTables: + for table in clauseTables: + tablesDict[table] = 1 + self.clauseTables = clauseTables + self.tables = tablesDict.keys() + self.ops = ops + if self.ops.get('orderBy', sqlbuilder.NoDefault) is sqlbuilder.NoDefault: + self.ops['orderBy'] = sourceClass._defaultOrder + orderBy = self.ops['orderBy'] + if isinstance(orderBy, list) or isinstance(orderBy, tuple): + orderBy = map(self._mungeOrderBy, orderBy) + else: + orderBy = self._mungeOrderBy(orderBy) + self.ops['dbOrderBy'] = orderBy + if ops.has_key('connection') and ops['connection'] is None: + del ops['connection'] + + def _mungeOrderBy(self, orderBy): + if isinstance(orderBy, str) and orderBy.startswith('-'): + orderBy = orderBy[1:] + desc = True + else: + desc = False + if isinstance(orderBy, (str, unicode)): + if self.sourceClass._SO_columnDict.has_key(orderBy): + val = self.sourceClass._SO_columnDict[orderBy].dbName + if desc: + return '-' + val + else: + return val + else: + if desc: + return '-' + orderBy + else: + return orderBy + else: + return orderBy + + def clone(self, **newOps): + ops = self.ops.copy() + ops.update(newOps) + return self.__class__(self.sourceClass, self.clause, + self.clauseTables, **ops) + + def orderBy(self, orderBy): + return self.clone(orderBy=orderBy) + + def connection(self, conn): + return self.clone(connection=conn) + + def limit(self, limit): + return self[:limit] + + def lazyColumns(self, value): + return self.clone(lazyColumns=value) + + def reversed(self): + return self.clone(reversed=not self.ops.get('reversed', False)) + + def distinct(self): + return self.clone(distinct=True) + + def __getitem__(self, value): + if type(value) is type(slice(1)): + assert not value.step, "Slices do not support steps" + if not value.start and not value.stop: + # No need to copy, I'm immutable + return self + + # Negative indexes aren't handled (and everything we + # don't handle ourselves we just create a list to + # handle) + if (value.start and value.start < 0) \ + or (value.stop and value.stop < 0): + if value.start: + if value.stop: + return list(self)[value.start:value.stop] + return list(self)[value.start:] + return list(self)[:value.stop] + + + if value.start: + assert value.start >= 0 + start = self.ops.get('start', 0) + value.start + if value.stop is not None: + assert value.stop >= 0 + if value.stop < value.start: + # an empty result: + end = start + else: + end = value.stop + self.ops.get('start', 0) + if self.ops.get('end', None) is not None \ + and value['end'] < end: + # truncated by previous slice: + end = self.ops['end'] + else: + end = self.ops.get('end', None) + else: + start = self.ops.get('start', 0) + end = value.stop + start + if self.ops.get('end', None) is not None \ + and self.ops['end'] < end: + end = self.ops['end'] + return self.clone(start=start, end=end) + else: + if value < 0: + return list(iter(self))[value] + else: + start = self.ops.get('start', 0) + value + return list(self.clone(start=start, end=start+1))[0] + + def __iter__(self): + conn = self.ops.get('connection', self.sourceClass._connection) + return conn.iterSelect(self) + + def accumulate(self, expression): + """ Use an accumulate expression to select result + using another SQL select through current + connection. + Return the accumulate result + """ + conn = self.ops.get('connection', self.sourceClass._connection) + return conn.accumulateSelect(self, expression) + + def count(self): + """ Counting elements of current select results """ + assert not self.ops.get('distinct'), "It is not currently supported to count distinct objects" + + count = self.accumulate('COUNT(*)') + if self.ops.get('start'): + count -= self.ops['start'] + if self.ops.get('end'): + count = min(self.ops['end'] - self.ops.get('start', 0), count) + return count + + def sum(self, attribute): + """ Making the sum of a given select result attribute. + `attribute` can be a column name (like 'a_column') + or a dot-q attribute (like Table.q.aColumn) + """ + if type(attribute) == type(''): + expression = 'SUM(%s)' % attribute + else: + expression = sqlbuilder.func.SUM(attribute) + return self.accumulate(expression) + +__all__ = ['SelectResults'] |