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