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