diff options
-rw-r--r-- | lib/sqlalchemy/__init__.py | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/types.py | 46 | ||||
-rw-r--r-- | test/dialect/mysql.py | 16 | ||||
-rw-r--r-- | test/sql/testtypes.py | 82 |
4 files changed, 87 insertions, 61 deletions
diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py index 8bc1c4597..0fc4e117e 100644 --- a/lib/sqlalchemy/__init__.py +++ b/lib/sqlalchemy/__init__.py @@ -7,9 +7,9 @@ import inspect from sqlalchemy.types import \ BLOB, BOOLEAN, CHAR, CLOB, DATE, DATETIME, DECIMAL, FLOAT, INT, \ - NCHAR, NUMERIC, SMALLINT, TEXT, Text, TIME, TIMESTAMP, VARCHAR, \ + NCHAR, NUMERIC, SMALLINT, TEXT, TIME, TIMESTAMP, VARCHAR, \ Binary, Boolean, Date, DateTime, Float, Integer, Interval, Numeric, \ - PickleType, SmallInteger, String, Time, Unicode + PickleType, SmallInteger, String, Text, Time, Unicode, UnicodeText from sqlalchemy.sql import \ func, modifier, text, literal, literal_column, null, alias, \ diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py index 1a5d0ab56..2de54804a 100644 --- a/lib/sqlalchemy/types.py +++ b/lib/sqlalchemy/types.py @@ -4,8 +4,8 @@ # This module is part of SQLAlchemy and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php -"""defines genericized SQL types, each represented by a subclass of -[sqlalchemy.types#AbstractType]. Dialects define further subclasses of these +"""defines genericized SQL types, each represented by a subclass of +[sqlalchemy.types#AbstractType]. Dialects define further subclasses of these types. For more information see the SQLAlchemy documentation on types. @@ -17,7 +17,7 @@ __all__ = [ 'TypeEngine', 'TypeDecorator', 'AbstractType', 'BOOLEAN', 'SMALLINT', 'DATE', 'TIME', 'String', 'Integer', 'SmallInteger','Smallinteger', 'Numeric', 'Float', 'DateTime', 'Date', 'Time', 'Binary', - 'Boolean', 'Unicode', 'PickleType', 'Interval', + 'Boolean', 'Unicode', 'UnicodeText', 'PickleType', 'Interval', 'type_map' ] @@ -222,7 +222,7 @@ class TypeDecorator(AbstractType): by default calls dialect.type_descriptor(self.impl), but can be overridden to provide different behavior. """ - + return dialect.type_descriptor(self.impl) def __getattr__(self, key): @@ -235,10 +235,10 @@ class TypeDecorator(AbstractType): def process_bind_param(self, value, dialect): raise NotImplementedError() - + def process_result_value(self, value, dialect): raise NotImplementedError() - + def bind_processor(self, dialect): if self.__class__.process_bind_param.func_code is not TypeDecorator.process_bind_param.func_code: impl_processor = self.impl.bind_processor(dialect) @@ -342,12 +342,12 @@ class Concatenable(object): class String(Concatenable, TypeEngine): """A sized string type. - + Usually corresponds to VARCHAR. Can also take Python unicode objects - and encode to the database's encoding in bind params (and the reverse for + and encode to the database's encoding in bind params (and the reverse for result sets.) - - a String with no length will adapt itself automatically to a Text + + a String with no length will adapt itself automatically to a Text object at the dialect level (this behavior is deprecated in 0.4). """ def __init__(self, length=None, convert_unicode=False, assert_unicode=None): @@ -395,7 +395,7 @@ class String(Concatenable, TypeEngine): if _for_ddl and self.length is None: warn_deprecated("Using String type with no length for CREATE TABLE is deprecated; use the Text type explicitly") return TypeEngine.dialect_impl(self, dialect, **kwargs) - + def get_search_list(self): l = super(String, self).get_search_list() # if we are String or Unicode with no length, @@ -409,14 +409,26 @@ class String(Concatenable, TypeEngine): def get_dbapi_type(self, dbapi): return dbapi.STRING +class Text(String): + def dialect_impl(self, dialect, **kwargs): + return TypeEngine.dialect_impl(self, dialect, **kwargs) + class Unicode(String): """A synonym for String(length, convert_unicode=True, assert_unicode='warn').""" - + def __init__(self, length=None, **kwargs): kwargs['convert_unicode'] = True kwargs['assert_unicode'] = 'warn' super(Unicode, self).__init__(length=length, **kwargs) +class UnicodeText(Text): + """A synonym for Text(convert_unicode=True, assert_unicode='warn').""" + + def __init__(self, length=None, **kwargs): + kwargs['convert_unicode'] = True + kwargs['assert_unicode'] = 'warn' + super(UnicodeText, self).__init__(length=length, **kwargs) + class Integer(TypeEngine): """Integer datatype.""" @@ -426,13 +438,11 @@ class Integer(TypeEngine): class SmallInteger(Integer): """Smallint datatype.""" - pass - Smallinteger = SmallInteger class Numeric(TypeEngine): """Numeric datatype, usually resolves to DECIMAL or NUMERIC.""" - + def __init__(self, precision=10, length=2, asdecimal=True): self.precision = precision self.length = length @@ -654,10 +664,7 @@ class Interval(TypeDecorator): return process class FLOAT(Float): pass -class Text(String): - def dialect_impl(self, dialect, **kwargs): - return TypeEngine.dialect_impl(self, dialect, **kwargs) - + TEXT = Text class NUMERIC(Numeric): pass class DECIMAL(Numeric): pass @@ -690,4 +697,3 @@ type_map = { dt.timedelta : Interval, type(None): NullType } - diff --git a/test/dialect/mysql.py b/test/dialect/mysql.py index 5f5c4a750..0a9153327 100644 --- a/test/dialect/mysql.py +++ b/test/dialect/mysql.py @@ -1,5 +1,5 @@ import testbase -import sets +import sets, warnings from sqlalchemy import * from sqlalchemy import sql, exceptions from sqlalchemy.databases import mysql @@ -632,6 +632,11 @@ class TypesTest(AssertMixin): specs = [( String(), mysql.MSText(), ), ( String(1), mysql.MSString(1), ), ( String(3), mysql.MSString(3), ), + ( Text(), mysql.MSText(), ), + ( Unicode(), mysql.MSText(), ), + ( Unicode(1), mysql.MSString(1), ), + ( Unicode(3), mysql.MSString(3), ), + ( UnicodeText(), mysql.MSText(), ), ( mysql.MSChar(1), ), ( mysql.MSChar(3), ), ( NCHAR(2), mysql.MSChar(2), ), @@ -660,7 +665,13 @@ class TypesTest(AssertMixin): m = MetaData(db) t_table = Table('mysql_types', m, *columns) try: - m.create_all() + try: + warnings.filterwarnings('ignore', + 'Using String type with no length.*') + m.create_all() + finally: + warnings.filterwarnings("always", + 'Using String type with no length.*') m2 = MetaData(db) rt = Table('mysql_types', m2, autoload=True) @@ -876,6 +887,7 @@ class SQLTest(SQLCompileTest): (String, "CAST(t.col AS CHAR)"), (Unicode, "CAST(t.col AS CHAR)"), + (UnicodeText, "CAST(t.col AS CHAR)"), (VARCHAR, "CAST(t.col AS CHAR)"), (NCHAR, "CAST(t.col AS CHAR)"), (CHAR, "CAST(t.col AS CHAR)"), diff --git a/test/sql/testtypes.py b/test/sql/testtypes.py index 7bd012f42..98a8ca0e1 100644 --- a/test/sql/testtypes.py +++ b/test/sql/testtypes.py @@ -47,7 +47,7 @@ class AdaptTest(PersistTest): impl = String def copy(self): return MyDecoratedType() - + col = Column('', MyDecoratedType) dialect_type = col.type.dialect_impl(dialect) assert isinstance(dialect_type.impl, oracle.OracleText), repr(dialect_type.impl) @@ -82,21 +82,25 @@ class AdaptTest(PersistTest): (oracle_dialect, VARCHAR(), oracle.OracleString), (oracle_dialect, String(50), oracle.OracleString), (oracle_dialect, Unicode(), oracle.OracleText), + (oracle_dialect, UnicodeText(), oracle.OracleText), (oracle_dialect, NCHAR(), oracle.OracleString), (mysql_dialect, String(), mysql.MSText), (mysql_dialect, VARCHAR(), mysql.MSString), (mysql_dialect, String(50), mysql.MSString), (mysql_dialect, Unicode(), mysql.MSText), + (mysql_dialect, UnicodeText(), mysql.MSText), (mysql_dialect, NCHAR(), mysql.MSNChar), (postgres_dialect, String(), postgres.PGText), (postgres_dialect, VARCHAR(), postgres.PGString), (postgres_dialect, String(50), postgres.PGString), (postgres_dialect, Unicode(), postgres.PGText), + (postgres_dialect, UnicodeText(), postgres.PGText), (postgres_dialect, NCHAR(), postgres.PGString), (firebird_dialect, String(), firebird.FBText), (firebird_dialect, VARCHAR(), firebird.FBString), (firebird_dialect, String(50), firebird.FBString), (firebird_dialect, Unicode(), firebird.FBText), + (firebird_dialect, UnicodeText(), firebird.FBText), (firebird_dialect, NCHAR(), firebird.FBString), ]: assert isinstance(start.dialect_impl(dialect), test), "wanted %r got %r" % (test, start.dialect_impl(dialect)) @@ -124,7 +128,7 @@ class UserDefinedTest(PersistTest): [1200, 1500, 900], [1800, 2250, 1350], l - + ): for col in row[1:8]: self.assertEquals(col, assertstr) @@ -132,7 +136,7 @@ class UserDefinedTest(PersistTest): self.assertEquals(row[9], assertint2) for col in (row[4], row[5], row[7]): assert isinstance(col, unicode) - + def setUpAll(self): global users, metadata @@ -304,7 +308,7 @@ class UnicodeTest(AssertMixin): unicode_table = Table('unicode_table', metadata, Column('id', Integer, Sequence('uni_id_seq', optional=True), primary_key=True), Column('unicode_varchar', Unicode(250)), - Column('unicode_text', Unicode), + Column('unicode_text', UnicodeText), Column('plain_varchar', String(250)) ) unicode_table.create() @@ -330,30 +334,34 @@ class UnicodeTest(AssertMixin): self.assert_(isinstance(x['unicode_text'], unicode) and x['unicode_text'] == unicodedata) if isinstance(x['plain_varchar'], unicode): # SQLLite and MSSQL return non-unicode data as unicode - self.assert_(testbase.db.name in ('sqlite', 'mssql')) + self.assert_(testing.against('sqlite', 'mssql')) self.assert_(x['plain_varchar'] == unicodedata) print "it's %s!" % testbase.db.name else: self.assert_(not isinstance(x['plain_varchar'], unicode) and x['plain_varchar'] == rawdata) - + def testassert(self): import warnings warnings.filterwarnings("always", r".*non-unicode bind") - - # test that data still goes in if warning is emitted.... - unicode_table.insert().execute(unicode_varchar='im not unicode') - assert select([unicode_table.c.unicode_varchar]).execute().fetchall() == [('im not unicode', )] - + + ## test that data still goes in if warning is emitted.... + unicode_table.insert().execute(unicode_varchar='not unicode') + assert (select([unicode_table.c.unicode_varchar]).execute().fetchall() + == [('not unicode', )]) + warnings.filterwarnings("error", r".*non-unicode bind") - try: - unicode_table.insert().execute(unicode_varchar='im not unicode') - assert False - except RuntimeWarning, e: - assert str(e) == "Unicode type received non-unicode bind param value 'im not unicode'", str(e) + try: + unicode_table.insert().execute(unicode_varchar='not unicode') + assert False + except RuntimeWarning, e: + assert str(e) == "Unicode type received non-unicode bind param value 'not unicode'", str(e) + finally: + warnings.filterwarnings("always", r".*non-unicode bind") - unicode_engine = engines.utf8_engine(options={'convert_unicode':True, 'assert_unicode':True}) + unicode_engine = engines.utf8_engine(options={'convert_unicode':True, + 'assert_unicode':True}) try: try: unicode_engine.execute(unicode_table.insert(), plain_varchar='im not unicode') @@ -363,7 +371,7 @@ class UnicodeTest(AssertMixin): finally: unicode_engine.dispose() - @testing.unsupported('oracle') + @testing.fails_on('oracle') def testblanks(self): unicode_table.insert().execute(unicode_varchar=u'') assert select([unicode_table.c.unicode_varchar]).scalar() == u'' @@ -482,40 +490,40 @@ class ExpressionTest(AssertMixin): return process def adapt_operator(self, op): return {operators.add:operators.sub, operators.sub:operators.add}.get(op, op) - + meta = MetaData(testbase.db) - test_table = Table('test', meta, + test_table = Table('test', meta, Column('id', Integer, primary_key=True), Column('data', String(30)), Column('timestamp', Date), Column('value', MyCustomType)) - + meta.create_all() - + test_table.insert().execute({'id':1, 'data':'somedata', 'timestamp':datetime.date(2007, 10, 15), 'value':25}) - + def tearDownAll(self): meta.drop_all() - + def test_control(self): assert testbase.db.execute("select value from test").scalar() == 250 - + assert test_table.select().execute().fetchall() == [(1, 'somedata', datetime.date(2007, 10, 15), 25)] - + def test_bind_adapt(self): expr = test_table.c.timestamp == bindparam("thedate") assert expr.right.type.__class__ == test_table.c.timestamp.type.__class__ - + assert testbase.db.execute(test_table.select().where(expr), {"thedate":datetime.date(2007, 10, 15)}).fetchall() == [(1, 'somedata', datetime.date(2007, 10, 15), 25)] expr = test_table.c.value == bindparam("somevalue") assert expr.right.type.__class__ == test_table.c.value.type.__class__ assert testbase.db.execute(test_table.select().where(expr), {"somevalue":25}).fetchall() == [(1, 'somedata', datetime.date(2007, 10, 15), 25)] - + def test_operator_adapt(self): """test type-based overloading of operators""" - + # test string concatenation expr = test_table.c.data + "somedata" assert testbase.db.execute(select([expr])).scalar() == "somedatasomedata" @@ -526,7 +534,7 @@ class ExpressionTest(AssertMixin): # test custom operator conversion expr = test_table.c.value + 40 assert expr.type.__class__ is test_table.c.value.type.__class__ - + # + operator converted to - # value is calculated as: (250 - (40 * 10)) / 10 == -15 assert testbase.db.execute(select([expr.label('foo')])).scalar() == -15 @@ -534,7 +542,7 @@ class ExpressionTest(AssertMixin): # this one relies upon anonymous labeling to assemble result # processing rules on the column. assert testbase.db.execute(select([expr])).scalar() == -15 - + class DateTest(AssertMixin): def setUpAll(self): global users_with_date, insert_data @@ -651,19 +659,19 @@ class DateTest(AssertMixin): self.assert_(x.adatetime.__class__ == datetime.datetime) t.delete().execute() - + # test mismatched date/datetime t.insert().execute(adate=d2, adatetime=d2) self.assertEquals(select([t.c.adate, t.c.adatetime], t.c.adate==d1).execute().fetchall(), [(d1, d2)]) self.assertEquals(select([t.c.adate, t.c.adatetime], t.c.adate==d1).execute().fetchall(), [(d1, d2)]) - + finally: t.drop(checkfirst=True) class StringTest(AssertMixin): def test_nolen_string_deprecated(self): metadata = MetaData(testbase.db) - foo =Table('foo', metadata, + foo =Table('foo', metadata, Column('one', String)) import warnings @@ -680,7 +688,7 @@ class StringTest(AssertMixin): assert False except SADeprecationWarning, e: assert "Using String type with no length" in str(e) - + bar = Table('bar', metadata, Column('one', String(40))) try: @@ -692,8 +700,8 @@ class StringTest(AssertMixin): finally: bar.drop() warnings.filterwarnings("always", r"Using String type with no length.*") - - + + class NumericTest(AssertMixin): def setUpAll(self): global numeric_table, metadata |