From 3f620ec49050320f86d9ee21bc396d40f342fd0c Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 9 Mar 2006 18:30:27 +0000 Subject: [PATCH] overhaul to types system, decoupled base type and engine-specific type into a composed pattern instead of goofy inheritance....gets rid of TypeDecorator (now a no-op) and enables all inhertance --- lib/sqlalchemy/types.py | 143 +++++++++++++++++----------------------- test/testtypes.py | 19 ++++-- 2 files changed, 76 insertions(+), 86 deletions(-) diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py index fcd416398d..f8b2b490f3 100644 --- a/lib/sqlalchemy/types.py +++ b/lib/sqlalchemy/types.py @@ -14,52 +14,48 @@ __all__ = [ 'TypeEngine', 'TypeDecorator', 'NullTypeEngine', import sqlalchemy.util as util class TypeEngine(object): + basetypes = [] + def __init__(self, *args, **kwargs): + pass + def _get_impl(self): + if hasattr(self, '_impl'): + return self._impl + else: + return NULLTYPE + def _set_impl(self, impl): + self._impl = impl + impl = property(_get_impl, _set_impl) def get_col_spec(self): - raise NotImplementedError() + return self.impl.get_col_spec() def convert_bind_param(self, value, engine): - raise NotImplementedError() + return self.impl.convert_bind_param(value, engine) def convert_result_value(self, value, engine): - raise NotImplementedError() - def adapt(self, typeobj): - """given a class that is a subclass of this TypeEngine's class, produces a new - instance of that class with an equivalent state to this TypeEngine. The given - class is a database-specific subclass which is obtained via a lookup dictionary, - mapped against the class returned by the class_to_adapt() method.""" - return typeobj() + return self.impl.convert_result_value(value, engine) + def set_impl(self, impltype): + self.impl = impltype(**self.get_constructor_args()) + def get_constructor_args(self): + return {} def adapt_args(self): - """Returns an instance of this TypeEngine instance's class, adapted according - to the constructor arguments of this TypeEngine. Default return value is - just this object instance.""" return self - def class_to_adapt(self): - """returns the class that should be sent to the adapt() method. This class - will be used to lookup an approprate database-specific subclass.""" - return self.__class__ -# def __repr__(self): - # return util.generic_repr(self) - + def adapt_type(typeobj, colspecs): - """given a generic type from this package, and a dictionary of - "conversion" specs from a DB-specific package, adapts the type - to a correctly-configured type instance from the DB-specific package.""" - if type(typeobj) is type: + if isinstance(typeobj, type): typeobj = typeobj() - # if the type is not a base type, i.e. not from our module, or its Null, - # we return the type as is - if (typeobj.__module__ != 'sqlalchemy.types' or typeobj.__class__ is NullTypeEngine) and not isinstance(typeobj, TypeDecorator): - return typeobj - typeobj = typeobj.adapt_args() - t = typeobj.class_to_adapt() - for t in t.__mro__[0:-1]: + t2 = typeobj.adapt_args() + for t in t2.__class__.__mro__[0:-1]: try: - return typeobj.adapt(colspecs[t]) - except KeyError, e: + impltype = colspecs[t] + break + except KeyError: pass - return typeobj.adapt(typeobj.__class__) + else: + # couldnt adapt...raise exception ? + return typeobj + typeobj.set_impl(impltype) + typeobj.impl.impl = NULLTYPE + return typeobj class NullTypeEngine(TypeEngine): - def __init__(self, *args, **kwargs): - pass def get_col_spec(self): raise NotImplementedError() def convert_bind_param(self, value, engine): @@ -68,32 +64,15 @@ class NullTypeEngine(TypeEngine): return value class TypeDecorator(object): - def get_col_spec(self): - return self.extended.get_col_spec() - def adapt(self, typeobj): - if self.extended is self: - t = self.__class__.__mro__[2] - self.extended = t.adapt(self, typeobj) - else: - self.extended = self.extended.adapt(typeobj) - return self - def adapt_args(self): - t = self.__class__.__mro__[2] - self.extended = t.adapt_args(self) - return self - def class_to_adapt(self): - return self.extended.__class__ + """TypeDecorator is deprecated""" + pass -class String(NullTypeEngine): + +class String(TypeEngine): def __init__(self, length = None): self.length = length - def adapt(self, typeobj): - return typeobj(self.length) - def adapt_args(self): - if self.length is None: - return TEXT() - else: - return self + def get_constructor_args(self): + return {'length':self.length} def convert_bind_param(self, value, engine): if not engine.convert_unicode or value is None or not isinstance(value, unicode): return value @@ -104,10 +83,13 @@ class String(NullTypeEngine): return value else: return value.decode('utf-8') - -class Unicode(TypeDecorator,String): - def __init__(self, length=None): - String.__init__(self, length) + def adapt_args(self): + if self.length is None: + return TEXT() + else: + return self + +class Unicode(String): def convert_bind_param(self, value, engine): if isinstance(value, unicode): return value.encode('utf-8') @@ -119,49 +101,48 @@ class Unicode(TypeDecorator,String): else: return value -class Integer(NullTypeEngine): +class Integer(TypeEngine): """integer datatype""" - # TODO: do string bind params need int(value) performed before sending ? - # seems to be not needed with SQLite, Postgres pass - -class Smallinteger(Integer): + +class SmallInteger(Integer): """ smallint datatype """ pass - -class Numeric(NullTypeEngine): +Smallinteger = SmallInteger + +class Numeric(TypeEngine): def __init__(self, precision = 10, length = 2): self.precision = precision self.length = length - def adapt(self, typeobj): - return typeobj(self.precision, self.length) + def get_constructor_args(self): + return {'precision':self.precision, 'length':self.length} -class Float(NullTypeEngine): +class Float(TypeEngine): def __init__(self, precision = 10): self.precision = precision - def adapt(self, typeobj): - return typeobj(self.precision) + def get_constructor_args(self): + return {'precision':self.precision} -class DateTime(NullTypeEngine): +class DateTime(TypeEngine): pass -class Date(NullTypeEngine): +class Date(TypeEngine): pass -class Time(NullTypeEngine): +class Time(TypeEngine): pass -class Binary(NullTypeEngine): +class Binary(TypeEngine): def __init__(self, length=None): self.length = length def convert_bind_param(self, value, engine): return engine.dbapi().Binary(value) def convert_result_value(self, value, engine): return value - def adapt(self, typeobj): - return typeobj(self.length) + def get_constructor_args(self): + return {'length':self.length} -class Boolean(NullTypeEngine): +class Boolean(TypeEngine): pass class FLOAT(Float):pass diff --git a/test/testtypes.py b/test/testtypes.py index 4bee6a3949..d44c439bdb 100644 --- a/test/testtypes.py +++ b/test/testtypes.py @@ -17,25 +17,31 @@ class MyType(types.TypeEngine): def adapt_args(self): return self -class MyDecoratedType(types.TypeDecorator, types.String): +class MyDecoratedType(types.String): def convert_bind_param(self, value, engine): return "BIND_IN"+ value def convert_result_value(self, value, engine): return value + "BIND_OUT" +class MyUnicodeType(types.Unicode): + def convert_bind_param(self, value, engine): + return "UNI_BIND_IN"+ value + def convert_result_value(self, value, engine): + return value + "UNI_BIND_OUT" + class OverrideTest(PersistTest): """tests user-defined types, including a full type as well as a TypeDecorator""" def testprocessing(self): global users - users.insert().execute(user_id = 2, goofy = 'jack', goofy2='jack', goofy3='jack') - users.insert().execute(user_id = 3, goofy = 'lala', goofy2='lala', goofy3='lala') - users.insert().execute(user_id = 4, goofy = 'fred', goofy2='fred', goofy3='fred') + users.insert().execute(user_id = 2, goofy = 'jack', goofy2='jack', goofy3='jack', goofy4='jack') + users.insert().execute(user_id = 3, goofy = 'lala', goofy2='lala', goofy3='lala', goofy4='lala') + users.insert().execute(user_id = 4, goofy = 'fred', goofy2='fred', goofy3='fred', goofy4='fred') l = users.select().execute().fetchall() print repr(l) - self.assert_(l == [(2, 'BIND_INjackBIND_OUT', 'BIND_INjackBIND_OUT', 'BIND_INjackBIND_OUT'), (3, 'BIND_INlalaBIND_OUT', 'BIND_INlalaBIND_OUT', 'BIND_INlalaBIND_OUT'), (4, 'BIND_INfredBIND_OUT', 'BIND_INfredBIND_OUT', 'BIND_INfredBIND_OUT')]) + self.assert_(l == [(2, 'BIND_INjackBIND_OUT', 'BIND_INjackBIND_OUT', 'BIND_INjackBIND_OUT', u'UNI_BIND_INjackUNI_BIND_OUT'), (3, 'BIND_INlalaBIND_OUT', 'BIND_INlalaBIND_OUT', 'BIND_INlalaBIND_OUT', u'UNI_BIND_INlalaUNI_BIND_OUT'), (4, 'BIND_INfredBIND_OUT', 'BIND_INfredBIND_OUT', 'BIND_INfredBIND_OUT', u'UNI_BIND_INfredUNI_BIND_OUT')]) def setUpAll(self): global users @@ -49,6 +55,9 @@ class OverrideTest(PersistTest): # decorated type without an argument, it will adapt_args to TEXT Column('goofy3', MyDecoratedType, nullable = False), + + Column('goofy4', MyUnicodeType, nullable = False), + ) users.create() -- 2.47.2