[Sqlalchemy-commits] sqlalchemy: - Added type_coerce(expr, type_) expression element.
Brought to you by:
zzzeek
From: <co...@sq...> - 2010-10-23 20:40:57
|
details: http://hg.sqlalchemy.org/sqlalchemy/sqlalchemy/rev/b8ac5e1bf91e changeset: 6889:b8ac5e1bf91e user: zzzeek date: Sat Oct 23 16:40:39 2010 -0400 description: - Added type_coerce(expr, type_) expression element. Treats the given expression as the given type when evaluating expressions and processing result rows, but does not affect the generation of SQL, other than an anonymous label. diffstat: CHANGES | 6 ++++ doc/build/core/expression_api.rst | 2 + lib/sqlalchemy/__init__.py | 1 + lib/sqlalchemy/sql/__init__.py | 1 + lib/sqlalchemy/sql/compiler.py | 8 ++++- lib/sqlalchemy/sql/expression.py | 52 +++++++++++++++++++++++++++++++++- test/sql/test_types.py | 58 ++++++++++++++++++++++++++++++++++++++- 7 files changed, 123 insertions(+), 5 deletions(-) diffs (219 lines): diff -r f4e22deffb5d -r b8ac5e1bf91e CHANGES --- a/CHANGES Sat Oct 23 15:27:21 2010 -0400 +++ b/CHANGES Sat Oct 23 16:40:39 2010 -0400 @@ -160,6 +160,12 @@ "self.impl". This to support compilation correctly. Behavior can be user-overridden in exactly the same way as before to the same effect. + + - Added type_coerce(expr, type_) expression element. + Treats the given expression as the given type when evaluating + expressions and processing result rows, but does not + affect the generation of SQL, other than an anonymous + label. - Table.tometadata() now copies Index objects associated with the Table as well. diff -r f4e22deffb5d -r b8ac5e1bf91e doc/build/core/expression_api.rst --- a/doc/build/core/expression_api.rst Sat Oct 23 15:27:21 2010 -0400 +++ b/doc/build/core/expression_api.rst Sat Oct 23 16:40:39 2010 -0400 @@ -115,6 +115,8 @@ .. autofunction:: tuple_ +.. autofunction:: type_coerce + .. autofunction:: union .. autofunction:: union_all diff -r f4e22deffb5d -r b8ac5e1bf91e lib/sqlalchemy/__init__.py --- a/lib/sqlalchemy/__init__.py Sat Oct 23 15:27:21 2010 -0400 +++ b/lib/sqlalchemy/__init__.py Sat Oct 23 16:40:39 2010 -0400 @@ -43,6 +43,7 @@ subquery, text, tuple_, + type_coerce, union, union_all, update, diff -r f4e22deffb5d -r b8ac5e1bf91e lib/sqlalchemy/sql/__init__.py --- a/lib/sqlalchemy/sql/__init__.py Sat Oct 23 15:27:21 2010 -0400 +++ b/lib/sqlalchemy/sql/__init__.py Sat Oct 23 16:40:39 2010 -0400 @@ -47,6 +47,7 @@ table, text, tuple_, + type_coerce, union, union_all, update, diff -r f4e22deffb5d -r b8ac5e1bf91e lib/sqlalchemy/sql/compiler.py --- a/lib/sqlalchemy/sql/compiler.py Sat Oct 23 15:27:21 2010 -0400 +++ b/lib/sqlalchemy/sql/compiler.py Sat Oct 23 16:40:39 2010 -0400 @@ -153,6 +153,10 @@ def __init__(self, col, name): self.element = col self.name = name + + @property + def type(self): + return self.element.type @property def quote(self): @@ -317,7 +321,7 @@ if result_map is not None: result_map[labelname.lower()] = \ - (label.name, (label, label.element, labelname), label.element.type) + (label.name, (label, label.element, labelname), label.type) return self.process(label.element, within_columns_clause=True, @@ -329,7 +333,7 @@ return self.process(label.element, within_columns_clause=False, **kw) - + def visit_column(self, column, result_map=None, **kwargs): name = column.name if name is None: diff -r f4e22deffb5d -r b8ac5e1bf91e lib/sqlalchemy/sql/expression.py --- a/lib/sqlalchemy/sql/expression.py Sat Oct 23 15:27:21 2010 -0400 +++ b/lib/sqlalchemy/sql/expression.py Sat Oct 23 16:40:39 2010 -0400 @@ -45,8 +45,8 @@ 'except_', 'except_all', 'exists', 'extract', 'func', 'modifier', 'collate', 'insert', 'intersect', 'intersect_all', 'join', 'label', 'literal', 'literal_column', 'not_', 'null', 'or_', 'outparam', - 'outerjoin', 'select', 'subquery', 'table', 'text', 'tuple_', 'union', - 'union_all', 'update', ] + 'outerjoin', 'select', 'subquery', 'table', 'text', 'tuple_', 'type_coerce', + 'union', 'union_all', 'update', ] PARSE_AUTOCOMMIT = util._symbol('PARSE_AUTOCOMMIT') @@ -666,6 +666,54 @@ """ return _Tuple(*expr) + +def type_coerce(expr, type_): + """Coerce the given expression into the given type, on the Python side only. + + :func:`.type_coerce` is roughly similar to :func:.`cast`, except no + "CAST" expression is rendered - the given type is only applied towards + expression typing and against received result values. + + e.g.:: + + from sqlalchemy.types import TypeDecorator + import uuid + + class AsGuid(TypeDecorator): + impl = String + + def process_bind_param(self, value, dialect): + if value is not None: + return str(value) + else: + return None + + def process_result_value(self, value, dialect): + if value is not None: + return uuid.UUID(value) + else: + return None + + conn.execute( + select([type_coerce(mytable.c.ident, AsGuid)]).\\ + where( + type_coerce(mytable.c.ident, AsGuid) == + uuid.uuid3(uuid.NAMESPACE_URL, 'bar') + ) + ) + + """ + if hasattr(expr, '__clause_expr__'): + return type_coerce(expr.__clause_expr__()) + + elif not isinstance(expr, Visitable): + if expr is None: + return null() + else: + return literal(expr, type_=type_) + else: + return _Label(None, expr, type_=type_) + def label(name, obj): """Return a :class:`_Label` object for the diff -r f4e22deffb5d -r b8ac5e1bf91e test/sql/test_types.py --- a/test/sql/test_types.py Sat Oct 23 15:27:21 2010 -0400 +++ b/test/sql/test_types.py Sat Oct 23 16:40:39 2010 -0400 @@ -201,7 +201,63 @@ t.dialect_impl(dialect=pg).impl.__class__, Float().dialect_impl(pg).__class__ ) - + + @testing.provide_metadata + def test_type_coerce(self): + """test ad-hoc usage of custom types with type_coerce().""" + + class MyType(types.TypeDecorator): + impl = String + + def process_bind_param(self, value, dialect): + return value[0:-8] + + def process_result_value(self, value, dialect): + return value + "BIND_OUT" + + t = Table('t', metadata, Column('data', String(50))) + metadata.create_all() + + t.insert().values(data=type_coerce('d1BIND_OUT',MyType)).execute() + + eq_( + select([type_coerce(t.c.data, MyType)]).execute().fetchall(), + [('d1BIND_OUT', )] + ) + + eq_( + select([t.c.data, type_coerce(t.c.data, MyType)]).execute().fetchall(), + [('d1', 'd1BIND_OUT')] + ) + + eq_( + select([t.c.data, type_coerce(t.c.data, MyType)]).\ + where(type_coerce(t.c.data, MyType) == 'd1BIND_OUT').\ + execute().fetchall(), + [('d1', 'd1BIND_OUT')] + ) + + eq_( + select([t.c.data, type_coerce(t.c.data, MyType)]).\ + where(t.c.data == type_coerce('d1BIND_OUT', MyType)).\ + execute().fetchall(), + [('d1', 'd1BIND_OUT')] + ) + + eq_( + select([t.c.data, type_coerce(t.c.data, MyType)]).\ + where(t.c.data == type_coerce(None, MyType)).\ + execute().fetchall(), + [] + ) + + eq_( + select([t.c.data, type_coerce(t.c.data, MyType)]).\ + where(type_coerce(t.c.data, MyType) == None).\ + execute().fetchall(), + [] + ) + @classmethod def setup_class(cls): global users, metadata |