0001"""
0002Col -- SQLObject columns
0003
0004Note that each column object is named BlahBlahCol, and these are used
0005in class definitions. But there's also a corresponding SOBlahBlahCol
0006object, which is used in SQLObject *classes*.
0007
0008An explanation: when a SQLObject subclass is created, the metaclass
0009looks through your class definition for any subclasses of Col. It
0010collects them together, and indexes them to do all the database stuff
0011you like, like the magic attributes and whatnot. It then asks the Col
0012object to create an SOCol object (usually a subclass, actually). The
0013SOCol object contains all the interesting logic, as well as a record
0014of the attribute name you used and the class it is bound to (set by
0015the metaclass).
0016
0017So, in summary: Col objects are what you define, but SOCol objects
0018are what gets used.
0019"""
0020
0021from array import array
0022from itertools import count
0023import re
0024import sys
0025import time
0026try:
0027 import cPickle as pickle
0028except ImportError:
0029 import pickle
0030import weakref
0031from formencode import compound, validators
0032from classregistry import findClass
0033
0034
0035import constraints as constrs
0036import converters
0037import sqlbuilder
0038from styles import capword
0039
0040NoDefault = sqlbuilder.NoDefault
0041
0042import datetime
0043datetime_available = True
0044
0045try:
0046 from mx import DateTime
0047except ImportError:
0048 try:
0049 import DateTime
0050 except ImportError:
0051 mxdatetime_available = False
0052 else:
0053 mxdatetime_available = True
0054else:
0055 mxdatetime_available = True
0056
0057DATETIME_IMPLEMENTATION = "datetime"
0058MXDATETIME_IMPLEMENTATION = "mxDateTime"
0059
0060if mxdatetime_available:
0061 if hasattr(DateTime, "Time"):
0062 DateTimeType = type(DateTime.now())
0063 TimeType = type(DateTime.Time())
0064 else:
0065 DateTimeType = type(DateTime.DateTime())
0066 TimeType = type(DateTime.DateTime.Time(DateTime.DateTime()))
0067
0068default_datetime_implementation = DATETIME_IMPLEMENTATION
0069
0070__all__ = ["datetime_available", "mxdatetime_available",
0071 "default_datetime_implementation", "DATETIME_IMPLEMENTATION"]
0072
0073if mxdatetime_available:
0074 __all__.append("MXDATETIME_IMPLEMENTATION")
0075
0076
0077def use_microseconds(use=True):
0078 if use:
0079 SODateTimeCol.datetimeFormat = '%Y-%m-%d %H:%M:%S.%f'
0080 SOTimeCol.timeFormat = '%H:%M:%S.%f'
0081 dt_types = [(datetime.datetime, converters.DateTimeConverterMS),
0082 (datetime.time, converters.TimeConverterMS)]
0083 else:
0084 SODateTimeCol.datetimeFormat = '%Y-%m-%d %H:%M:%S'
0085 SOTimeCol.timeFormat = '%H:%M:%S'
0086 dt_types = [(datetime.datetime, converters.DateTimeConverter),
0087 (datetime.time, converters.TimeConverter)]
0088 for dt_type, converter in dt_types:
0089 converters.registerConverter(dt_type, converter)
0090
0091
0092__all__.append("use_microseconds")
0093
0094
0095creationOrder = count()
0096
0097
0098
0099
0100
0101
0102
0103class SOCol(object):
0104
0105 def __init__(self,
0106 name,
0107 soClass,
0108 creationOrder,
0109 dbName=None,
0110 default=NoDefault,
0111 defaultSQL=None,
0112 foreignKey=None,
0113 alternateID=False,
0114 alternateMethodName=None,
0115 constraints=None,
0116 notNull=NoDefault,
0117 notNone=NoDefault,
0118 unique=NoDefault,
0119 sqlType=None,
0120 columnDef=None,
0121 validator=None,
0122 validator2=None,
0123 immutable=False,
0124 cascade=None,
0125 lazy=False,
0126 noCache=False,
0127 forceDBName=False,
0128 title=None,
0129 tags=[],
0130 origName=None,
0131 dbEncoding=None,
0132 extra_vars=None):
0133
0134 super(SOCol, self).__init__()
0135
0136
0137
0138
0139
0140
0141 if not forceDBName:
0142 assert sqlbuilder.sqlIdentifier(name), 'Name must be SQL-safe (letters, numbers, underscores): %s (or use forceDBName=True)' % repr(name)
0144 assert name != 'id', 'The column name "id" is reserved for SQLObject use (and is implicitly created).'
0145 assert name, "You must provide a name for all columns"
0146
0147 self.columnDef = columnDef
0148 self.creationOrder = creationOrder
0149
0150 self.immutable = immutable
0151
0152
0153
0154
0155
0156
0157 if isinstance(cascade, str):
0158 assert cascade == 'null', (
0159 "The only string value allowed for cascade is 'null' (you gave: %r)" % cascade)
0160 self.cascade = cascade
0161
0162 if not isinstance(constraints, (list, tuple)):
0163 constraints = [constraints]
0164 self.constraints = self.autoConstraints() + constraints
0165
0166 self.notNone = False
0167 if notNull is not NoDefault:
0168 self.notNone = notNull
0169 assert notNone is NoDefault or (not notNone) == (not notNull), "The notNull and notNone arguments are aliases, and must not conflict. You gave notNull=%r, notNone=%r" % (notNull, notNone)
0172 elif notNone is not NoDefault:
0173 self.notNone = notNone
0174 if self.notNone:
0175 self.constraints = [constrs.notNull] + self.constraints
0176
0177 self.name = name
0178 self.soClass = soClass
0179 self._default = default
0180 self.defaultSQL = defaultSQL
0181 self.customSQLType = sqlType
0182
0183
0184 self.foreignKey = foreignKey
0185 if self.foreignKey:
0186 if origName is not None:
0187 idname = soClass.sqlmeta.style.instanceAttrToIDAttr(origName)
0188 else:
0189 idname = soClass.sqlmeta.style.instanceAttrToIDAttr(name)
0190 if self.name != idname:
0191 self.foreignName = self.name
0192 self.name = idname
0193 else:
0194 self.foreignName = soClass.sqlmeta.style.instanceIDAttrToAttr(self.name)
0195 else:
0196 self.foreignName = None
0197
0198
0199
0200
0201 if dbName is None:
0202 self.dbName = soClass.sqlmeta.style.pythonAttrToDBColumn(self.name)
0203 else:
0204 self.dbName = dbName
0205
0206
0207
0208 self.alternateID = alternateID
0209
0210 if unique is NoDefault:
0211 self.unique = alternateID
0212 else:
0213 self.unique = unique
0214 if self.unique and alternateMethodName is None:
0215 self.alternateMethodName = 'by' + capword(self.name)
0216 else:
0217 self.alternateMethodName = alternateMethodName
0218
0219 _validators = self.createValidators()
0220 if validator: _validators.append(validator)
0221 if validator2: _validators.insert(0, validator2)
0222 _vlen = len(_validators)
0223 if _vlen:
0224 for _validator in _validators:
0225 _validator.soCol=weakref.proxy(self)
0226 if _vlen == 0:
0227 self.validator = None
0228 elif _vlen == 1:
0229 self.validator = _validators[0]
0230 elif _vlen > 1:
0231 self.validator = compound.All.join(_validators[0], *_validators[1:])
0232 self.noCache = noCache
0233 self.lazy = lazy
0234
0235
0236 self.origName = origName or name
0237 self.title = title
0238 self.tags = tags
0239 self.dbEncoding = dbEncoding
0240
0241 if extra_vars:
0242 for name, value in extra_vars.items():
0243 setattr(self, name, value)
0244
0245 def _set_validator(self, value):
0246 self._validator = value
0247 if self._validator:
0248 self.to_python = self._validator.to_python
0249 self.from_python = self._validator.from_python
0250 else:
0251 self.to_python = None
0252 self.from_python = None
0253
0254 def _get_validator(self):
0255 return self._validator
0256
0257 validator = property(_get_validator, _set_validator)
0258
0259 def createValidators(self):
0260 """Create a list of validators for the column."""
0261 return []
0262
0263 def autoConstraints(self):
0264 return []
0265
0266 def _get_default(self):
0267
0268
0269 if self._default is NoDefault:
0270 return NoDefault
0271 elif hasattr(self._default, '__sqlrepr__'):
0272 return self._default
0273 elif callable(self._default):
0274 return self._default()
0275 else:
0276 return self._default
0277 default = property(_get_default, None, None)
0278
0279 def _get_joinName(self):
0280 return self.soClass.sqlmeta.style.instanceIDAttrToAttr(self.name)
0281 joinName = property(_get_joinName, None, None)
0282
0283 def __repr__(self):
0284 r = '<%s %s' % (self.__class__.__name__, self.name)
0285 if self.default is not NoDefault:
0286 r += ' default=%s' % repr(self.default)
0287 if self.foreignKey:
0288 r += ' connected to %s' % self.foreignKey
0289 if self.alternateID:
0290 r += ' alternate ID'
0291 if self.notNone:
0292 r += ' not null'
0293 return r + '>'
0294
0295 def createSQL(self):
0296 return ' '.join([self._sqlType()] + self._extraSQL())
0297
0298 def _extraSQL(self):
0299 result = []
0300 if self.notNone or self.alternateID:
0301 result.append('NOT NULL')
0302 if self.unique or self.alternateID:
0303 result.append('UNIQUE')
0304 if self.defaultSQL is not None:
0305 result.append("DEFAULT %s" % self.defaultSQL)
0306 return result
0307
0308 def _sqlType(self):
0309 if self.customSQLType is None:
0310 raise ValueError, ("Col %s (%s) cannot be used for automatic "
0311 "schema creation (too abstract)" %
0312 (self.name, self.__class__))
0313 else:
0314 return self.customSQLType
0315
0316 def _mysqlType(self):
0317 return self._sqlType()
0318
0319 def _postgresType(self):
0320 return self._sqlType()
0321
0322 def _sqliteType(self):
0323
0324
0325 try:
0326 return self._sqlType()
0327 except ValueError:
0328 return ''
0329
0330 def _sybaseType(self):
0331 return self._sqlType()
0332
0333 def _mssqlType(self):
0334 return self._sqlType()
0335
0336 def _firebirdType(self):
0337 return self._sqlType()
0338
0339 def _maxdbType(self):
0340 return self._sqlType()
0341
0342 def mysqlCreateSQL(self, connection=None):
0343 self.connection = connection
0344 return ' '.join([self.dbName, self._mysqlType()] + self._extraSQL())
0345
0346 def postgresCreateSQL(self):
0347 return ' '.join([self.dbName, self._postgresType()] + self._extraSQL())
0348
0349 def sqliteCreateSQL(self):
0350 return ' '.join([self.dbName, self._sqliteType()] + self._extraSQL())
0351
0352 def sybaseCreateSQL(self):
0353 return ' '.join([self.dbName, self._sybaseType()] + self._extraSQL())
0354
0355 def mssqlCreateSQL(self, connection=None):
0356 self.connection = connection
0357 return ' '.join([self.dbName, self._mssqlType()] + self._extraSQL())
0358
0359 def firebirdCreateSQL(self):
0360
0361
0362
0363 if not isinstance(self, SOEnumCol):
0364 return ' '.join([self.dbName, self._firebirdType()] + self._extraSQL())
0365 else:
0366 return ' '.join([self.dbName] + [self._firebirdType()[0]] + self._extraSQL() + [self._firebirdType()[1]])
0367
0368 def maxdbCreateSQL(self):
0369 return ' '.join([self.dbName, self._maxdbType()] + self._extraSQL())
0370
0371 def __get__(self, obj, type=None):
0372 if obj is None:
0373
0374 return self
0375 if obj.sqlmeta._obsolete:
0376 raise RuntimeError('The object <%s %s> is obsolete' % (
0377 obj.__class__.__name__, obj.id))
0378 if obj.sqlmeta.cacheColumns:
0379 columns = obj.sqlmeta._columnCache
0380 if columns is None:
0381 obj.sqlmeta.loadValues()
0382 try:
0383 return columns[name]
0384 except KeyError:
0385 return obj.sqlmeta.loadColumn(self)
0386 else:
0387 return obj.sqlmeta.loadColumn(self)
0388
0389 def __set__(self, obj, value):
0390 if self.immutable:
0391 raise AttributeError("The column %s.%s is immutable" %
0392 (obj.__class__.__name__,
0393 self.name))
0394 obj.sqlmeta.setColumn(self, value)
0395
0396 def __delete__(self, obj):
0397 raise AttributeError("I can't be deleted from %r" % obj)
0398
0399 def getDbEncoding(self, state, default='utf-8'):
0400 if self.dbEncoding:
0401 return self.dbEncoding
0402 dbEncoding = state.soObject.sqlmeta.dbEncoding
0403 if dbEncoding:
0404 return dbEncoding
0405 try:
0406 connection = state.connection or state.soObject._connection
0407 except AttributeError:
0408 dbEncoding = None
0409 else:
0410 dbEncoding = getattr(connection, "dbEncoding", None)
0411 if not dbEncoding:
0412 dbEncoding = default
0413 return dbEncoding
0414
0415
0416class Col(object):
0417
0418 baseClass = SOCol
0419
0420 def __init__(self, name=None, **kw):
0421 super(Col, self).__init__()
0422 self.__dict__['_name'] = name
0423 self.__dict__['_kw'] = kw
0424 self.__dict__['creationOrder'] = creationOrder.next()
0425 self.__dict__['_extra_vars'] = {}
0426
0427 def _set_name(self, value):
0428 assert self._name is None or self._name == value, (
0429 "You cannot change a name after it has already been set "
0430 "(from %s to %s)" % (self.name, value))
0431 self.__dict__['_name'] = value
0432
0433 def _get_name(self):
0434 return self._name
0435
0436 name = property(_get_name, _set_name)
0437
0438 def withClass(self, soClass):
0439 return self.baseClass(soClass=soClass, name=self._name,
0440 creationOrder=self.creationOrder,
0441 columnDef=self,
0442 extra_vars=self._extra_vars,
0443 **self._kw)
0444
0445 def __setattr__(self, var, value):
0446 if var == 'name':
0447 super(Col, self).__setattr__(var, value)
0448 return
0449 self._extra_vars[var] = value
0450
0451 def __repr__(self):
0452 return '<%s %s %s>' % (
0453 self.__class__.__name__, hex(abs(id(self)))[2:],
0454 self._name or '(unnamed)')
0455
0456
0457class SOValidator(validators.Validator):
0458 def getDbEncoding(self, state, default='utf-8'):
0459 try:
0460 return self.dbEncoding
0461 except AttributeError:
0462 return self.soCol.getDbEncoding(state, default=default)
0463
0464
0465class SOStringLikeCol(SOCol):
0466 """A common ancestor for SOStringCol and SOUnicodeCol"""
0467 def __init__(self, **kw):
0468 self.length = kw.pop('length', None)
0469 self.varchar = kw.pop('varchar', 'auto')
0470 self.char_binary = kw.pop('char_binary', None)
0471 if not self.length:
0472 assert self.varchar == 'auto' or not self.varchar, "Without a length strings are treated as TEXT, not varchar"
0474 self.varchar = False
0475 elif self.varchar == 'auto':
0476 self.varchar = True
0477
0478 super(SOStringLikeCol, self).__init__(**kw)
0479
0480 def autoConstraints(self):
0481 constraints = [constrs.isString]
0482 if self.length is not None:
0483 constraints += [constrs.MaxLength(self.length)]
0484 return constraints
0485
0486 def _sqlType(self):
0487 if self.customSQLType is not None:
0488 return self.customSQLType
0489 if not self.length:
0490 return 'TEXT'
0491 elif self.varchar:
0492 return 'VARCHAR(%i)' % self.length
0493 else:
0494 return 'CHAR(%i)' % self.length
0495
0496 def _check_case_sensitive(self, db):
0497 if self.char_binary:
0498 raise ValueError, "%s does not support binary character columns" % db
0499
0500 def _mysqlType(self):
0501 type = self._sqlType()
0502 if self.char_binary:
0503 type += " BINARY"
0504 return type
0505
0506 def _postgresType(self):
0507 self._check_case_sensitive("PostgreSQL")
0508 return super(SOStringLikeCol, self)._postgresType()
0509
0510 def _sqliteType(self):
0511 self._check_case_sensitive("SQLite")
0512 return super(SOStringLikeCol, self)._sqliteType()
0513
0514 def _sybaseType(self):
0515 self._check_case_sensitive("SYBASE")
0516 type = self._sqlType()
0517 if not self.notNone and not self.alternateID:
0518 type += ' NULL'
0519 return type
0520
0521 def _mssqlType(self):
0522 if self.customSQLType is not None:
0523 return self.customSQLType
0524 if not self.length:
0525 if self.connection and self.connection.can_use_max_types():
0526 type = 'VARCHAR(MAX)'
0527 else:
0528 type = 'VARCHAR(4000)'
0529 elif self.varchar:
0530 type = 'VARCHAR(%i)' % self.length
0531 else:
0532 type = 'CHAR(%i)' % self.length
0533 if not self.notNone and not self.alternateID:
0534 type += ' NULL'
0535 return type
0536
0537 def _firebirdType(self):
0538 self._check_case_sensitive("FireBird")
0539 if not self.length:
0540 return 'BLOB SUB_TYPE TEXT'
0541 else:
0542 return self._sqlType()
0543
0544 def _maxdbType(self):
0545 self._check_case_sensitive("SAP DB/MaxDB")
0546 if not self.length:
0547 return 'LONG ASCII'
0548 else:
0549 return self._sqlType()
0550
0551
0552class StringValidator(SOValidator):
0553
0554 def to_python(self, value, state):
0555 if value is None:
0556 return None
0557 try:
0558 connection = state.connection or state.soObject._connection
0559 binaryType = connection._binaryType
0560 except AttributeError:
0561 binaryType = type(None)
0562 dbEncoding = self.getDbEncoding(state, default='ascii')
0563 if isinstance(value, unicode):
0564 return value.encode(dbEncoding)
0565 if self.dataType and isinstance(value, self.dataType):
0566 return value
0567 if isinstance(value, (str, buffer, binaryType, sqlbuilder.SQLExpression)):
0568 return value
0569 if hasattr(value, '__unicode__'):
0570 return unicode(value).encode(dbEncoding)
0571 raise validators.Invalid("expected a str in the StringCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
0573
0574 from_python = to_python
0575
0576class SOStringCol(SOStringLikeCol):
0577
0578 def createValidators(self, dataType=None):
0579 return [StringValidator(name=self.name, dataType=dataType)] + super(SOStringCol, self).createValidators()
0581
0582class StringCol(Col):
0583 baseClass = SOStringCol
0584
0585
0586class NQuoted(sqlbuilder.SQLExpression):
0587 def __init__(self, value):
0588 assert isinstance(value, unicode)
0589 self.value = value
0590 def __hash__(self):
0591 return hash(self.value)
0592 def __sqlrepr__(self, db):
0593 assert db == 'mssql'
0594 return "N" + sqlbuilder.sqlrepr(self.value, db)
0595
0596class UnicodeStringValidator(SOValidator):
0597
0598 def to_python(self, value, state):
0599 if value is None:
0600 return None
0601 if isinstance(value, (unicode, sqlbuilder.SQLExpression)):
0602 return value
0603 if isinstance(value, str):
0604 return value.decode(self.getDbEncoding(state))
0605 if isinstance(value, array):
0606 return value.tostring().decode(self.getDbEncoding(state))
0607 if hasattr(value, '__unicode__'):
0608 return unicode(value)
0609 raise validators.Invalid("expected a str or a unicode in the UnicodeCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
0611
0612 def from_python(self, value, state):
0613 if value is None:
0614 return None
0615 if isinstance(value, (str, sqlbuilder.SQLExpression)):
0616 return value
0617 if isinstance(value, unicode):
0618 try:
0619 connection = state.connection or state.soObject._connection
0620 except AttributeError:
0621 pass
0622 else:
0623 if connection.dbName == 'mssql':
0624 return NQuoted(value)
0625 return value.encode(self.getDbEncoding(state))
0626 if hasattr(value, '__unicode__'):
0627 return unicode(value).encode(self.getDbEncoding(state))
0628 raise validators.Invalid("expected a str or a unicode in the UnicodeCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
0630
0631class SOUnicodeCol(SOStringLikeCol):
0632 def _mssqlType(self):
0633 if self.customSQLType is not None:
0634 return self.customSQLType
0635 return 'N' + super(SOUnicodeCol, self)._mssqlType()
0636
0637 def createValidators(self):
0638 return [UnicodeStringValidator(name=self.name)] + super(SOUnicodeCol, self).createValidators()
0640
0641class UnicodeCol(Col):
0642 baseClass = SOUnicodeCol
0643
0644
0645class IntValidator(SOValidator):
0646
0647 def to_python(self, value, state):
0648 if value is None:
0649 return None
0650 if isinstance(value, (int, long, sqlbuilder.SQLExpression)):
0651 return value
0652 for converter, attr_name in (int, '__int__'), (long, '__long__'):
0653 if hasattr(value, attr_name):
0654 try:
0655 return converter(value)
0656 except:
0657 break
0658 raise validators.Invalid("expected an int in the IntCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
0660
0661 from_python = to_python
0662
0663class SOIntCol(SOCol):
0664
0665 def __init__(self, **kw):
0666 self.length = kw.pop('length', None)
0667 self.unsigned = bool(kw.pop('unsigned', None))
0668 self.zerofill = bool(kw.pop('zerofill', None))
0669 SOCol.__init__(self, **kw)
0670
0671 def autoConstraints(self):
0672 return [constrs.isInt]
0673
0674 def createValidators(self):
0675 return [IntValidator(name=self.name)] + super(SOIntCol, self).createValidators()
0677
0678 def addSQLAttrs(self, str):
0679 _ret = str
0680 if str is None or len(str) < 1:
0681 return None
0682
0683 if self.length >= 1:
0684 _ret = "%s(%d)" % (_ret, self.length)
0685 if self.unsigned:
0686 _ret = _ret + " UNSIGNED"
0687 if self.zerofill:
0688 _ret = _ret + " ZEROFILL"
0689 return _ret
0690
0691 def _sqlType(self):
0692 return self.addSQLAttrs("INT")
0693
0694class IntCol(Col):
0695 baseClass = SOIntCol
0696
0697class SOTinyIntCol(SOIntCol):
0698 def _sqlType(self):
0699 return self.addSQLAttrs("TINYINT")
0700
0701class TinyIntCol(Col):
0702 baseClass = SOTinyIntCol
0703
0704class SOSmallIntCol(SOIntCol):
0705 def _sqlType(self):
0706 return self.addSQLAttrs("SMALLINT")
0707
0708class SmallIntCol(Col):
0709 baseClass = SOSmallIntCol
0710
0711class SOMediumIntCol(SOIntCol):
0712 def _sqlType(self):
0713 return self.addSQLAttrs("MEDIUMINT")
0714
0715class MediumIntCol(Col):
0716 baseClass = SOMediumIntCol
0717
0718class SOBigIntCol(SOIntCol):
0719 def _sqlType(self):
0720 return self.addSQLAttrs("BIGINT")
0721
0722class BigIntCol(Col):
0723 baseClass = SOBigIntCol
0724
0725
0726class BoolValidator(SOValidator):
0727
0728 def to_python(self, value, state):
0729 if value is None:
0730 return None
0731 if isinstance(value, (bool, sqlbuilder.SQLExpression)):
0732 return value
0733 if isinstance(value, (int, long)) or hasattr(value, '__nonzero__'):
0734 return bool(value)
0735 raise validators.Invalid("expected a bool or an int in the BoolCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
0737
0738 from_python = to_python
0739
0740class SOBoolCol(SOCol):
0741 def autoConstraints(self):
0742 return [constrs.isBool]
0743
0744 def createValidators(self):
0745 return [BoolValidator(name=self.name)] + super(SOBoolCol, self).createValidators()
0747
0748 def _postgresType(self):
0749 return 'BOOL'
0750
0751 def _mysqlType(self):
0752 return "BOOL"
0753
0754 def _sybaseType(self):
0755 return "BIT"
0756
0757 def _mssqlType(self):
0758 return "BIT"
0759
0760 def _firebirdType(self):
0761 return 'INT'
0762
0763 def _maxdbType(self):
0764 return "BOOLEAN"
0765
0766 def _sqliteType(self):
0767 return "BOOLEAN"
0768
0769class BoolCol(Col):
0770 baseClass = SOBoolCol
0771
0772
0773class FloatValidator(SOValidator):
0774
0775 def to_python(self, value, state):
0776 if value is None:
0777 return None
0778 if isinstance(value, (float, int, long, sqlbuilder.SQLExpression)):
0779 return value
0780 for converter, attr_name in (float, '__float__'), (int, '__int__'), (long, '__long__'):
0781 if hasattr(value, attr_name):
0782 try:
0783 return converter(value)
0784 except:
0785 break
0786 raise validators.Invalid("expected a float in the FloatCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
0788
0789 from_python = to_python
0790
0791class SOFloatCol(SOCol):
0792
0793
0794 def autoConstraints(self):
0795 return [constrs.isFloat]
0796
0797 def createValidators(self):
0798 return [FloatValidator(name=self.name)] + super(SOFloatCol, self).createValidators()
0800
0801 def _sqlType(self):
0802 return 'FLOAT'
0803
0804 def _mysqlType(self):
0805 return "DOUBLE PRECISION"
0806
0807class FloatCol(Col):
0808 baseClass = SOFloatCol
0809
0810
0811class SOKeyCol(SOCol):
0812 key_type = {int: "INT", str: "TEXT"}
0813
0814
0815
0816
0817 def __init__(self, **kw):
0818 self.refColumn = kw.pop('refColumn', None)
0819 super(SOKeyCol, self).__init__(**kw)
0820
0821 def _sqlType(self):
0822 return self.key_type[self.soClass.sqlmeta.idType]
0823
0824 def _sybaseType(self):
0825 key_type = {int: "NUMERIC(18,0) NULL", str: "TEXT"}
0826 return key_type[self.soClass.sqlmeta.idType]
0827
0828 def _mssqlType(self):
0829 key_type = {int: "INT NULL", str: "TEXT"}
0830 return key_type[self.soClass.sqlmeta.idType]
0831
0832class KeyCol(Col):
0833
0834 baseClass = SOKeyCol
0835
0836class SOForeignKey(SOKeyCol):
0837
0838 def __init__(self, **kw):
0839 foreignKey = kw['foreignKey']
0840 style = kw['soClass'].sqlmeta.style
0841 if kw.get('name'):
0842 kw['origName'] = kw['name']
0843 kw['name'] = style.instanceAttrToIDAttr(kw['name'])
0844 else:
0845 kw['name'] = style.instanceAttrToIDAttr(style.pythonClassToAttr(foreignKey))
0846 super(SOForeignKey, self).__init__(**kw)
0847
0848 def sqliteCreateSQL(self):
0849 sql = SOKeyCol.sqliteCreateSQL(self)
0850 other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0851 tName = other.sqlmeta.table
0852 idName = self.refColumn or other.sqlmeta.idName
0853 if self.cascade is not None:
0854 if self.cascade == 'null':
0855 action = 'ON DELETE SET NULL'
0856 elif self.cascade:
0857 action = 'ON DELETE CASCADE'
0858 else:
0859 action = 'ON DELETE RESTRICT'
0860 else:
0861 action = ''
0862 constraint = ('CONSTRAINT %(colName)s_exists '
0863
0864 'REFERENCES %(tName)s(%(idName)s) '
0865 '%(action)s' %
0866 {'tName': tName,
0867 'colName': self.dbName,
0868 'idName': idName,
0869 'action': action})
0870 sql = ' '.join([sql, constraint])
0871 return sql
0872
0873 def postgresCreateSQL(self):
0874 sql = SOKeyCol.postgresCreateSQL(self)
0875 return sql
0876
0877 def postgresCreateReferenceConstraint(self):
0878 sTName = self.soClass.sqlmeta.table
0879 other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0880 tName = other.sqlmeta.table
0881 idName = self.refColumn or other.sqlmeta.idName
0882 if self.cascade is not None:
0883 if self.cascade == 'null':
0884 action = 'ON DELETE SET NULL'
0885 elif self.cascade:
0886 action = 'ON DELETE CASCADE'
0887 else:
0888 action = 'ON DELETE RESTRICT'
0889 else:
0890 action = ''
0891 constraint = ('ALTER TABLE %(sTName)s ADD CONSTRAINT %(colName)s_exists '
0892 'FOREIGN KEY (%(colName)s) '
0893 'REFERENCES %(tName)s (%(idName)s) '
0894 '%(action)s' %
0895 {'tName': tName,
0896 'colName': self.dbName,
0897 'idName': idName,
0898 'action': action,
0899 'sTName': sTName})
0900 return constraint
0901
0902 def mysqlCreateReferenceConstraint(self):
0903 sTName = self.soClass.sqlmeta.table
0904 sTLocalName = sTName.split('.')[-1]
0905 other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0906 tName = other.sqlmeta.table
0907 idName = self.refColumn or other.sqlmeta.idName
0908 if self.cascade is not None:
0909 if self.cascade == 'null':
0910 action = 'ON DELETE SET NULL'
0911 elif self.cascade:
0912 action = 'ON DELETE CASCADE'
0913 else:
0914 action = 'ON DELETE RESTRICT'
0915 else:
0916 action = ''
0917 constraint = ('ALTER TABLE %(sTName)s ADD CONSTRAINT %(sTLocalName)s_%(colName)s_exists '
0918 'FOREIGN KEY (%(colName)s) '
0919 'REFERENCES %(tName)s (%(idName)s) '
0920 '%(action)s' %
0921 {'tName': tName,
0922 'colName': self.dbName,
0923 'idName': idName,
0924 'action': action,
0925 'sTName': sTName,
0926 'sTLocalName': sTLocalName})
0927 return constraint
0928
0929 def mysqlCreateSQL(self, connection=None):
0930 return SOKeyCol.mysqlCreateSQL(self, connection)
0931
0932 def sybaseCreateSQL(self):
0933 sql = SOKeyCol.sybaseCreateSQL(self)
0934 other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0935 tName = other.sqlmeta.table
0936 idName = self.refColumn or other.sqlmeta.idName
0937 reference = ('REFERENCES %(tName)s(%(idName)s) ' %
0938 {'tName':tName,
0939 'idName':idName})
0940 sql = ' '.join([sql, reference])
0941 return sql
0942
0943 def sybaseCreateReferenceConstraint(self):
0944
0945 return None
0946
0947 def mssqlCreateSQL(self, connection=None):
0948 sql = SOKeyCol.mssqlCreateSQL(self, connection)
0949 other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0950 tName = other.sqlmeta.table
0951 idName = self.refColumn or other.sqlmeta.idName
0952 reference = ('REFERENCES %(tName)s(%(idName)s) ' %
0953 {'tName':tName,
0954 'idName':idName})
0955 sql = ' '.join([sql, reference])
0956 return sql
0957
0958 def mssqlCreateReferenceConstraint(self):
0959
0960 return None
0961
0962 def maxdbCreateSQL(self):
0963 other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0964 fidName = self.dbName
0965
0966 sql = ' '.join([fidName, self._maxdbType()])
0967 tName = other.sqlmeta.table
0968 idName = self.refColumn or other.sqlmeta.idName
0969 sql=sql + ',' + '\n'
0970 sql=sql + 'FOREIGN KEY (%s) REFERENCES %s(%s)'%(fidName,tName,idName)
0971 return sql
0972
0973 def maxdbCreateReferenceConstraint(self):
0974
0975 return None
0976
0977class ForeignKey(KeyCol):
0978
0979 baseClass = SOForeignKey
0980
0981 def __init__(self, foreignKey=None, **kw):
0982 super(ForeignKey, self).__init__(foreignKey=foreignKey, **kw)
0983
0984
0985class EnumValidator(SOValidator):
0986
0987 def to_python(self, value, state):
0988 if value in self.enumValues:
0989 if isinstance(value, unicode):
0990 dbEncoding = self.getDbEncoding(state)
0991 value = value.encode(dbEncoding)
0992 return value
0993 elif not self.notNone and value is None:
0994 return None
0995 raise validators.Invalid("expected a member of %r in the EnumCol '%s', got %r instead" % (self.enumValues, self.name, value), value, state)
0997
0998 from_python = to_python
0999
1000class SOEnumCol(SOCol):
1001
1002 def __init__(self, **kw):
1003 self.enumValues = kw.pop('enumValues', None)
1004 assert self.enumValues is not None, 'You must provide an enumValues keyword argument'
1006 super(SOEnumCol, self).__init__(**kw)
1007
1008 def autoConstraints(self):
1009 return [constrs.isString, constrs.InList(self.enumValues)]
1010
1011 def createValidators(self):
1012 return [EnumValidator(name=self.name, enumValues=self.enumValues,
1013 notNone=self.notNone)] + super(SOEnumCol, self).createValidators()
1015
1016 def _mysqlType(self):
1017
1018
1019 if None in self.enumValues:
1020 return "ENUM(%s)" % ', '.join([sqlbuilder.sqlrepr(v, 'mysql') for v in self.enumValues if v is not None])
1021 else:
1022 return "ENUM(%s) NOT NULL" % ', '.join([sqlbuilder.sqlrepr(v, 'mysql') for v in self.enumValues])
1023
1024 def _postgresType(self):
1025 length = max(map(self._getlength, self.enumValues))
1026 enumValues = ', '.join([sqlbuilder.sqlrepr(v, 'postgres') for v in self.enumValues])
1027 checkConstraint = "CHECK (%s in (%s))" % (self.dbName, enumValues)
1028 return "VARCHAR(%i) %s" % (length, checkConstraint)
1029
1030 _sqliteType = _postgresType
1031
1032 def _sybaseType(self):
1033 return self._postgresType()
1034
1035 def _mssqlType(self):
1036 return self._postgresType()
1037
1038 def _firebirdType(self):
1039 length = max(map(self._getlength, self.enumValues))
1040 enumValues = ', '.join([sqlbuilder.sqlrepr(v, 'firebird') for v in self.enumValues])
1041 checkConstraint = "CHECK (%s in (%s))" % (self.dbName, enumValues)
1042
1043 return "VARCHAR(%i)" % (length), checkConstraint
1044
1045 def _maxdbType(self):
1046 raise TypeError("Enum type is not supported on MAX DB")
1047
1048 def _getlength(self, obj):
1049 """
1050 None counts as 0; everything else uses len()
1051 """
1052 if obj is None:
1053 return 0
1054 else:
1055 return len(obj)
1056
1057class EnumCol(Col):
1058 baseClass = SOEnumCol
1059
1060
1061class SetValidator(SOValidator):
1062 """
1063 Translates Python tuples into SQL comma-delimited SET strings.
1064 """
1065
1066 def to_python(self, value, state):
1067 if isinstance(value, str):
1068 return tuple(value.split(","))
1069 raise validators.Invalid("expected a string in the SetCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
1071
1072 def from_python(self, value, state):
1073 if isinstance(value, basestring):
1074 value = (value,)
1075 try:
1076 return ",".join(value)
1077 except:
1078 raise validators.Invalid("expected a string or a sequence of stringsin the SetCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
1080
1081class SOSetCol(SOCol):
1082 def __init__(self, **kw):
1083 self.setValues = kw.pop('setValues', None)
1084 assert self.setValues is not None, 'You must provide a setValues keyword argument'
1086 super(SOSetCol, self).__init__(**kw)
1087
1088 def autoConstraints(self):
1089 return [constrs.isString, constrs.InList(self.setValues)]
1090
1091 def createValidators(self):
1092 return [SetValidator(name=self.name, setValues=self.setValues)] + super(SOSetCol, self).createValidators()
1094
1095 def _mysqlType(self):
1096 return "SET(%s)" % ', '.join([sqlbuilder.sqlrepr(v, 'mysql') for v in self.setValues])
1097
1098class SetCol(Col):
1099 baseClass = SOSetCol
1100
1101
1102class DateTimeValidator(validators.DateValidator):
1103 def to_python(self, value, state):
1104 if value is None:
1105 return None
1106 if isinstance(value, (datetime.datetime, datetime.date, datetime.time, sqlbuilder.SQLExpression)):
1107 return value
1108 if mxdatetime_available:
1109 if isinstance(value, DateTimeType):
1110
1111 if (self.format.find("%H") >= 0) or (self.format.find("%T")) >= 0:
1112 return datetime.datetime(value.year, value.month, value.day,
1113 value.hour, value.minute, int(value.second))
1114 else:
1115 return datetime.date(value.year, value.month, value.day)
1116 elif isinstance(value, TimeType):
1117
1118 if self.format.find("%d") >= 0:
1119 return datetime.timedelta(seconds=value.seconds)
1120 else:
1121 return datetime.time(value.hour, value.minute, int(value.second))
1122 try:
1123 if self.format.find(".%f") >= 0:
1124 if '.' in value:
1125 _value = value.split('.')
1126 microseconds = _value[-1]
1127 _l = len(microseconds)
1128 if _l < 6:
1129 _value[-1] = microseconds + '0'*(6 - _l)
1130 elif _l > 6:
1131 _value[-1] = microseconds[:6]
1132 if _l != 6:
1133 value = '.'.join(_value)
1134 else:
1135 value += '.0'
1136 return datetime.datetime.strptime(value, self.format)
1137 except:
1138 raise validators.Invalid("expected a date/time string of the '%s' format in the DateTimeCol '%s', got %s %r instead" % (self.format, self.name, type(value), value), value, state)
1140
1141 def from_python(self, value, state):
1142 if value is None:
1143 return None
1144 if isinstance(value, (datetime.datetime, datetime.date, datetime.time, sqlbuilder.SQLExpression)):
1145 return value
1146 if hasattr(value, "strftime"):
1147 return value.strftime(self.format)
1148 raise validators.Invalid("expected a datetime in the DateTimeCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
1150
1151if mxdatetime_available:
1152 class MXDateTimeValidator(validators.DateValidator):
1153 def to_python(self, value, state):
1154 if value is None:
1155 return None
1156 if isinstance(value, (DateTimeType, TimeType, sqlbuilder.SQLExpression)):
1157 return value
1158 if isinstance(value, datetime.datetime):
1159 return DateTime.DateTime(value.year, value.month, value.day,
1160 value.hour, value.minute, value.second)
1161 elif isinstance(value, datetime.date):
1162 return DateTime.Date(value.year, value.month, value.day)
1163 elif isinstance(value, datetime.time):
1164 return DateTime.Time(value.hour, value.minute, value.second)
1165 try:
1166 if self.format.find(".%f") >= 0:
1167 if '.' in value:
1168 _value = value.split('.')
1169 microseconds = _value[-1]
1170 _l = len(microseconds)
1171 if _l < 6:
1172 _value[-1] = microseconds + '0'*(6 - _l)
1173 elif _l > 6:
1174 _value[-1] = microseconds[:6]
1175 if _l != 6:
1176 value = '.'.join(_value)
1177 else:
1178 value += '.0'
1179 value = datetime.datetime.strptime(value, self.format)
1180 return DateTime.DateTime(value.year, value.month, value.day,
1181 value.hour, value.minute, value.second)
1182 except:
1183 raise validators.Invalid("expected a date/time string of the '%s' format in the DateTimeCol '%s', got %s %r instead" % (self.format, self.name, type(value), value), value, state)
1185
1186 def from_python(self, value, state):
1187 if value is None:
1188 return None
1189 if isinstance(value, (DateTimeType, TimeType, sqlbuilder.SQLExpression)):
1190 return value
1191 if hasattr(value, "strftime"):
1192 return value.strftime(self.format)
1193 raise validators.Invalid("expected a mxDateTime in the DateTimeCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
1195
1196class SODateTimeCol(SOCol):
1197 datetimeFormat = '%Y-%m-%d %H:%M:%S.%f'
1198
1199 def __init__(self, **kw):
1200 datetimeFormat = kw.pop('datetimeFormat', None)
1201 if datetimeFormat:
1202 self.datetimeFormat = datetimeFormat
1203 super(SODateTimeCol, self).__init__(**kw)
1204
1205 def createValidators(self):
1206 _validators = super(SODateTimeCol, self).createValidators()
1207 if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1208 validatorClass = DateTimeValidator
1209 elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1210 validatorClass = MXDateTimeValidator
1211 if default_datetime_implementation:
1212 _validators.insert(0, validatorClass(name=self.name, format=self.datetimeFormat))
1213 return _validators
1214
1215 def _mysqlType(self):
1216 if self.connection and self.connection.can_use_microseconds():
1217 return 'DATETIME(6)'
1218 else:
1219 return 'DATETIME'
1220
1221 def _postgresType(self):
1222 return 'TIMESTAMP'
1223
1224 def _sybaseType(self):
1225 return 'DATETIME'
1226
1227 def _mssqlType(self):
1228 if self.connection and self.connection.can_use_microseconds():
1229 return 'DATETIME2(6)'
1230 else:
1231 return 'DATETIME'
1232
1233 def _sqliteType(self):
1234 return 'TIMESTAMP'
1235
1236 def _firebirdType(self):
1237 return 'TIMESTAMP'
1238
1239 def _maxdbType(self):
1240 return 'TIMESTAMP'
1241
1242class DateTimeCol(Col):
1243 baseClass = SODateTimeCol
1244 @staticmethod
1245 def now():
1246 if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1247 return datetime.datetime.now()
1248 elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1249 return DateTime.now()
1250 else:
1251 assert 0, ("No datetime implementation available "
1252 "(DATETIME_IMPLEMENTATION=%r)"
1253 % DATETIME_IMPLEMENTATION)
1254
1255
1256class DateValidator(DateTimeValidator):
1257 def to_python(self, value, state):
1258 if isinstance(value, datetime.datetime):
1259 value = value.date()
1260 if isinstance(value, (datetime.date, sqlbuilder.SQLExpression)):
1261 return value
1262 value = super(DateValidator, self).to_python(value, state)
1263 if isinstance(value, datetime.datetime):
1264 value = value.date()
1265 return value
1266
1267 from_python = to_python
1268
1269class SODateCol(SOCol):
1270 dateFormat = '%Y-%m-%d'
1271
1272 def __init__(self, **kw):
1273 dateFormat = kw.pop('dateFormat', None)
1274 if dateFormat: self.dateFormat = dateFormat
1275 super(SODateCol, self).__init__(**kw)
1276
1277 def createValidators(self):
1278 """Create a validator for the column. Can be overriden in descendants."""
1279 _validators = super(SODateCol, self).createValidators()
1280 if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1281 validatorClass = DateValidator
1282 elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1283 validatorClass = MXDateTimeValidator
1284 if default_datetime_implementation:
1285 _validators.insert(0, validatorClass(name=self.name, format=self.dateFormat))
1286 return _validators
1287
1288 def _mysqlType(self):
1289 return 'DATE'
1290
1291 def _postgresType(self):
1292 return 'DATE'
1293
1294 def _sybaseType(self):
1295 return self._postgresType()
1296
1297 def _mssqlType(self):
1298 """
1299 SQL Server doesn't have a DATE data type, to emulate we use a vc(10)
1300 """
1301 return 'VARCHAR(10)'
1302
1303 def _firebirdType(self):
1304 return 'DATE'
1305
1306 def _maxdbType(self):
1307 return 'DATE'
1308
1309 def _sqliteType(self):
1310 return 'DATE'
1311
1312class DateCol(Col):
1313 baseClass = SODateCol
1314
1315
1316class TimeValidator(DateTimeValidator):
1317 def to_python(self, value, state):
1318 if isinstance(value, (datetime.time, sqlbuilder.SQLExpression)):
1319 return value
1320 if isinstance(value, datetime.timedelta):
1321 if value.days:
1322 raise validators.Invalid(
1323 "the value for the TimeCol '%s' must has days=0, it has days=%d" %
1324 (self.name, value.days), value, state)
1325 return datetime.time(*time.gmtime(value.seconds)[3:6])
1326 value = super(TimeValidator, self).to_python(value, state)
1327 if isinstance(value, datetime.datetime):
1328 value = value.time()
1329 return value
1330
1331 from_python = to_python
1332
1333class SOTimeCol(SOCol):
1334 timeFormat = '%H:%M:%S.%f'
1335
1336 def __init__(self, **kw):
1337 timeFormat = kw.pop('timeFormat', None)
1338 if timeFormat:
1339 self.timeFormat = timeFormat
1340 super(SOTimeCol, self).__init__(**kw)
1341
1342 def createValidators(self):
1343 _validators = super(SOTimeCol, self).createValidators()
1344 if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1345 validatorClass = TimeValidator
1346 elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1347 validatorClass = MXDateTimeValidator
1348 if default_datetime_implementation:
1349 _validators.insert(0, validatorClass(name=self.name, format=self.timeFormat))
1350 return _validators
1351
1352 def _mysqlType(self):
1353 if self.connection and self.connection.can_use_microseconds():
1354 return 'TIME(6)'
1355 else:
1356 return 'TIME'
1357
1358 def _postgresType(self):
1359 return 'TIME'
1360
1361 def _sybaseType(self):
1362 return 'TIME'
1363
1364 def _mssqlType(self):
1365 if self.connection and self.connection.can_use_microseconds():
1366 return 'TIME(6)'
1367 else:
1368 return 'TIME'
1369
1370 def _sqliteType(self):
1371 return 'TIME'
1372
1373 def _firebirdType(self):
1374 return 'TIME'
1375
1376 def _maxdbType(self):
1377 return 'TIME'
1378
1379class TimeCol(Col):
1380 baseClass = SOTimeCol
1381
1382
1383class SOTimestampCol(SODateTimeCol):
1384 """
1385 Necessary to support MySQL's use of TIMESTAMP versus DATETIME types
1386 """
1387
1388 def __init__(self, **kw):
1389 if 'default' not in kw:
1390 kw['default'] = None
1391 SOCol.__init__(self, **kw)
1392
1393 def _mysqlType(self):
1394 if self.connection and self.connection.can_use_microseconds():
1395 return 'TIMESTAMP(6)'
1396 else:
1397 return 'TIMESTAMP'
1398
1399class TimestampCol(Col):
1400 baseClass = SOTimestampCol
1401
1402
1403class TimedeltaValidator(SOValidator):
1404 def to_python(self, value, state):
1405 return value
1406
1407 from_python = to_python
1408
1409class SOTimedeltaCol(SOCol):
1410 def _postgresType(self):
1411 return 'INTERVAL'
1412
1413 def createValidators(self):
1414 return [TimedeltaValidator(name=self.name)] + super(SOTimedeltaCol, self).createValidators()
1416
1417class TimedeltaCol(Col):
1418 baseClass = SOTimedeltaCol
1419
1420
1421from decimal import Decimal
1422
1423class DecimalValidator(SOValidator):
1424 def to_python(self, value, state):
1425 if value is None:
1426 return None
1427 if isinstance(value, (int, long, Decimal, sqlbuilder.SQLExpression)):
1428 return value
1429 if isinstance(value, float):
1430 value = str(value)
1431 try:
1432 connection = state.connection or state.soObject._connection
1433 except AttributeError:
1434 pass
1435 else:
1436 if hasattr(connection, "decimalSeparator"):
1437 value = value.replace(connection.decimalSeparator, ".")
1438 try:
1439 return Decimal(value)
1440 except:
1441 raise validators.Invalid("expected a Decimal in the DecimalCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
1443
1444 def from_python(self, value, state):
1445 if value is None:
1446 return None
1447 if isinstance(value, float):
1448 value = str(value)
1449 if isinstance(value, basestring):
1450 try:
1451 connection = state.connection or state.soObject._connection
1452 except AttributeError:
1453 pass
1454 else:
1455 if hasattr(connection, "decimalSeparator"):
1456 value = value.replace(connection.decimalSeparator, ".")
1457 try:
1458 return Decimal(value)
1459 except:
1460 raise validators.Invalid("can not parse Decimal value '%s' in the DecimalCol from '%s'" %
1461 (value, getattr(state, 'soObject', '(unknown)')), value, state)
1462 if isinstance(value, (int, long, Decimal, sqlbuilder.SQLExpression)):
1463 return value
1464 raise validators.Invalid("expected a Decimal in the DecimalCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
1466
1467class SODecimalCol(SOCol):
1468
1469 def __init__(self, **kw):
1470 self.size = kw.pop('size', NoDefault)
1471 assert self.size is not NoDefault, "You must give a size argument"
1473 self.precision = kw.pop('precision', NoDefault)
1474 assert self.precision is not NoDefault, "You must give a precision argument"
1476 super(SODecimalCol, self).__init__(**kw)
1477
1478 def _sqlType(self):
1479 return 'DECIMAL(%i, %i)' % (self.size, self.precision)
1480
1481 def createValidators(self):
1482 return [DecimalValidator(name=self.name)] + super(SODecimalCol, self).createValidators()
1484
1485class DecimalCol(Col):
1486 baseClass = SODecimalCol
1487
1488class SOCurrencyCol(SODecimalCol):
1489
1490 def __init__(self, **kw):
1491 pushKey(kw, 'size', 10)
1492 pushKey(kw, 'precision', 2)
1493 super(SOCurrencyCol, self).__init__(**kw)
1494
1495class CurrencyCol(DecimalCol):
1496 baseClass = SOCurrencyCol
1497
1498
1499class DecimalStringValidator(DecimalValidator):
1500 def to_python(self, value, state):
1501 value = super(DecimalStringValidator, self).to_python(value, state)
1502 if self.precision and isinstance(value, Decimal):
1503 assert value < self.max, "Value must be less than %s" % int(self.max)
1505 value = value.quantize(self.precision)
1506 return value
1507
1508 def from_python(self, value, state):
1509 value = super(DecimalStringValidator, self).from_python(value, state)
1510 if isinstance(value, Decimal):
1511 if self.precision:
1512 assert value < self.max, "Value must be less than %s" % int(self.max)
1514 value = value.quantize(self.precision)
1515 value = value.to_eng_string()
1516 elif isinstance(value, (int, long)):
1517 value = str(value)
1518 return value
1519
1520class SODecimalStringCol(SOStringCol):
1521 def __init__(self, **kw):
1522 self.size = kw.pop('size', NoDefault)
1523 assert (self.size is not NoDefault) and (self.size >= 0), "You must give a size argument as a positive integer"
1525 self.precision = kw.pop('precision', NoDefault)
1526 assert (self.precision is not NoDefault) and (self.precision >= 0), "You must give a precision argument as a positive integer"
1528 kw['length'] = int(self.size) + int(self.precision)
1529 self.quantize = kw.pop('quantize', False)
1530 assert isinstance(self.quantize, bool), "quantize argument must be Boolean True/False"
1532 super(SODecimalStringCol, self).__init__(**kw)
1533
1534 def createValidators(self):
1535 if self.quantize:
1536 v = DecimalStringValidator(name=self.name,
1537 precision=Decimal(10) ** (-1 * int(self.precision)),
1538 max=Decimal(10) ** (int(self.size) - int(self.precision)))
1539 else:
1540 v = DecimalStringValidator(name=self.name, precision=0)
1541 return [v] + super(SODecimalStringCol, self).createValidators(dataType=Decimal)
1543
1544class DecimalStringCol(StringCol):
1545 baseClass = SODecimalStringCol
1546
1547
1548class BinaryValidator(SOValidator):
1549 """
1550 Validator for binary types.
1551
1552 We're assuming that the per-database modules provide some form
1553 of wrapper type for binary conversion.
1554 """
1555
1556 _cachedValue = None
1557
1558 def to_python(self, value, state):
1559 if value is None:
1560 return None
1561 try:
1562 connection = state.connection or state.soObject._connection
1563 except AttributeError:
1564 dbName = None
1565 binaryType = type(None)
1566 else:
1567 dbName = connection.dbName
1568 binaryType = connection._binaryType
1569 if isinstance(value, str):
1570 if dbName == "sqlite":
1571 value = connection.module.decode(value)
1572 return value
1573 if isinstance(value, (buffer, binaryType)):
1574 cachedValue = self._cachedValue
1575 if cachedValue and cachedValue[1] == value:
1576 return cachedValue[0]
1577 if isinstance(value, array):
1578 return value.tostring()
1579 return str(value)
1580 raise validators.Invalid("expected a string in the BLOBCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
1582
1583 def from_python(self, value, state):
1584 if value is None:
1585 return None
1586 connection = state.connection or state.soObject._connection
1587 binary = connection.createBinary(value)
1588 self._cachedValue = (value, binary)
1589 return binary
1590
1591class SOBLOBCol(SOStringCol):
1592 def __init__(self, **kw):
1593
1594 if 'varchar' not in kw: kw['varchar'] = False
1595 super(SOBLOBCol, self).__init__(**kw)
1596
1597 def createValidators(self):
1598 return [BinaryValidator(name=self.name)] + super(SOBLOBCol, self).createValidators()
1600
1601 def _mysqlType(self):
1602 length = self.length
1603 varchar = self.varchar
1604 if length >= 2**24:
1605 return varchar and "LONGTEXT" or "LONGBLOB"
1606 if length >= 2**16:
1607 return varchar and "MEDIUMTEXT" or "MEDIUMBLOB"
1608 if length >= 2**8:
1609 return varchar and "TEXT" or "BLOB"
1610 return varchar and "TINYTEXT" or "TINYBLOB"
1611
1612 def _postgresType(self):
1613 return 'BYTEA'
1614
1615 def _mssqlType(self):
1616 if self.connection and self.connection.can_use_max_types():
1617 return 'VARBINARY(MAX)'
1618 else:
1619 return "IMAGE"
1620
1621class BLOBCol(StringCol):
1622 baseClass = SOBLOBCol
1623
1624
1625class PickleValidator(BinaryValidator):
1626 """
1627 Validator for pickle types. A pickle type is simply a binary type
1628 with hidden pickling, so that we can simply store any kind of
1629 stuff in a particular column.
1630
1631 The support for this relies directly on the support for binary for
1632 your database.
1633 """
1634
1635 def to_python(self, value, state):
1636 if value is None:
1637 return None
1638 if isinstance(value, unicode):
1639 dbEncoding = self.getDbEncoding(state, default='ascii')
1640 value = value.encode(dbEncoding)
1641 if isinstance(value, str):
1642 return pickle.loads(value)
1643 raise validators.Invalid("expected a pickle string in the PickleCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
1645
1646 def from_python(self, value, state):
1647 if value is None:
1648 return None
1649 return pickle.dumps(value, self.pickleProtocol)
1650
1651class SOPickleCol(SOBLOBCol):
1652
1653 def __init__(self, **kw):
1654 self.pickleProtocol = kw.pop('pickleProtocol', pickle.HIGHEST_PROTOCOL)
1655 super(SOPickleCol, self).__init__(**kw)
1656
1657 def createValidators(self):
1658 return [PickleValidator(name=self.name,
1659 pickleProtocol=self.pickleProtocol)] + super(SOPickleCol, self).createValidators()
1661
1662 def _mysqlType(self):
1663 length = self.length
1664 if length >= 2**24:
1665 return "LONGBLOB"
1666 if length >= 2**16:
1667 return "MEDIUMBLOB"
1668 return "BLOB"
1669
1670class PickleCol(BLOBCol):
1671 baseClass = SOPickleCol
1672
1673
1674def pushKey(kw, name, value):
1675 if not name in kw:
1676 kw[name] = value
1677
1678all = []
1679for key, value in globals().items():
1680 if isinstance(value, type) and (issubclass(value, (Col, SOCol))):
1681 all.append(key)
1682__all__.extend(all)
1683del all