[Sqlalchemy-commits] sqlalchemy: - rename hybrid.property_, hybrid.method to hybrid_p...
Brought to you by:
zzzeek
From: <co...@sq...> - 2011-01-12 20:50:15
|
details: http://hg.sqlalchemy.org/sqlalchemy/sqlalchemy/rev/220e53e630ad changeset: 7258:220e53e630ad user: zzzeek date: Wed Jan 12 15:35:20 2011 -0500 description: - rename hybrid.property_, hybrid.method to hybrid_property, hybrid_method. more typing on the import but this is just clearer. - adapt dictlike-polymorphic.py to use hybrid. Subject: sqlalchemy: - dont count server_default absense as part of autoincrement, PG details: http://hg.sqlalchemy.org/sqlalchemy/sqlalchemy/rev/ca50d1b232a4 changeset: 7259:ca50d1b232a4 user: zzzeek date: Wed Jan 12 15:49:59 2011 -0500 description: - dont count server_default absense as part of autoincrement, PG will have a value here upon reflection. - mysql, others will have to check "server_default" when rendering special keywords like AUTOINCREMENT diffstat: examples/vertical/dictlike-polymorphic.py | 64 ++++++++++++++---------------- lib/sqlalchemy/dialects/mysql/base.py | 2 +- lib/sqlalchemy/ext/hybrid.py | 14 +++--- lib/sqlalchemy/schema.py | 8 ++- test/sql/test_defaults.py | 1 - 5 files changed, 42 insertions(+), 47 deletions(-) diffs (227 lines): diff -r cb63dc77032d -r ca50d1b232a4 examples/vertical/dictlike-polymorphic.py --- a/examples/vertical/dictlike-polymorphic.py Wed Jan 12 11:31:13 2011 -0500 +++ b/examples/vertical/dictlike-polymorphic.py Wed Jan 12 15:49:59 2011 -0500 @@ -21,14 +21,16 @@ This example approach uses exactly the same dict mapping approach as the 'dictlike' example. It only differs in the mapping for vertical rows. Here, -we'll use a Python @property to build a smart '.value' attribute that wraps up +we'll use a @hybrid_property to build a smart '.value' attribute that wraps up reading and writing those various '_value' columns and keeps the '.type' up to date. +Class decorators are used, so Python 2.6 or greater is required. """ from sqlalchemy.orm.interfaces import PropComparator from sqlalchemy.orm import comparable_property +from sqlalchemy.ext.hybrid import hybrid_property # Using the VerticalPropertyDictMixin from the base example from dictlike import VerticalPropertyDictMixin @@ -72,39 +74,19 @@ type(None): (None, None), } - class Comparator(PropComparator): - """A comparator for .value, builds a polymorphic comparison via CASE. - - Optional. If desired, install it as a comparator in the mapping:: - - mapper(..., properties={ - 'value': comparable_property(PolymorphicVerticalProperty.Comparator, - PolymorphicVerticalProperty.value) - }) - """ - - def _case(self): - cls = self.prop.parent.class_ - whens = [(text("'%s'" % p[0]), getattr(cls, p[1])) - for p in cls.type_map.values() - if p[1] is not None] - return case(whens, cls.type, null()) - def __eq__(self, other): - return cast(self._case(), String) == cast(other, String) - def __ne__(self, other): - return cast(self._case(), String) != cast(other, String) - def __init__(self, key, value=None): self.key = key self.value = value - def _get_value(self): + @hybrid_property + def value(self): for discriminator, field in self.type_map.values(): if self.type == discriminator: return getattr(self, field) return None - def _set_value(self, value): + @value.setter + def value(self, value): py_type = type(value) if py_type not in self.type_map: raise TypeError(py_type) @@ -118,11 +100,27 @@ if field is not None: setattr(self, field, field_value) - def _del_value(self): + @value.deleter + def value(self): self._set_value(None) - value = property(_get_value, _set_value, _del_value, doc= - """The logical value of this property.""") + @value.comparator + class value(PropComparator): + """A comparator for .value, builds a polymorphic comparison via CASE. + + """ + def __init__(self, cls): + self.cls = cls + + def _case(self): + whens = [(text("'%s'" % p[0]), getattr(self.cls, p[1])) + for p in self.cls.type_map.values() + if p[1] is not None] + return case(whens, self.cls.type, null()) + def __eq__(self, other): + return cast(self._case(), String) == cast(other, String) + def __ne__(self, other): + return cast(self._case(), String) != cast(other, String) def __repr__(self): return '<%s %r=%r>' % (self.__class__.__name__, self.key, self.value) @@ -131,7 +129,7 @@ if __name__ == '__main__': from sqlalchemy import (MetaData, Table, Column, Integer, Unicode, ForeignKey, UnicodeText, and_, not_, or_, String, Boolean, cast, text, - null, case) + null, case, create_engine) from sqlalchemy.orm import mapper, relationship, Session from sqlalchemy.orm.collections import attribute_mapped_collection @@ -185,11 +183,9 @@ collection_class=attribute_mapped_collection('key')), }) - mapper(AnimalFact, chars, properties={ - 'value': comparable_property(AnimalFact.Comparator, AnimalFact.value) - }) + mapper(AnimalFact, chars) - metadata.bind = 'sqlite:///' + metadata.bind = create_engine('sqlite://', echo=True) metadata.create_all() session = Session() @@ -208,9 +204,7 @@ print "changing cuteness value and type:" critter[u'cuteness'] = u'very cute' - metadata.bind.echo = True session.commit() - metadata.bind.echo = False marten = Animal(u'marten') marten[u'cuteness'] = 5 diff -r cb63dc77032d -r ca50d1b232a4 lib/sqlalchemy/dialects/mysql/base.py --- a/lib/sqlalchemy/dialects/mysql/base.py Wed Jan 12 11:31:13 2011 -0500 +++ b/lib/sqlalchemy/dialects/mysql/base.py Wed Jan 12 15:49:59 2011 -0500 @@ -1309,7 +1309,7 @@ elif column.nullable and is_timestamp and default is None: colspec.append('NULL') - if column is column.table._autoincrement_column: + if column is column.table._autoincrement_column and column.server_default is None: colspec.append('AUTO_INCREMENT') return ' '.join(colspec) diff -r cb63dc77032d -r ca50d1b232a4 lib/sqlalchemy/ext/hybrid.py --- a/lib/sqlalchemy/ext/hybrid.py Wed Jan 12 11:31:13 2011 -0500 +++ b/lib/sqlalchemy/ext/hybrid.py Wed Jan 12 15:49:59 2011 -0500 @@ -31,22 +31,22 @@ # A base class for intervals - from sqlalchemy.orm import hybrid + from sqlalchemy.orm.hybrid import hybrid_property, hybrid_method class Interval(object): def __init__(self, start, end): self.start = start self.end = end - @hybrid.property + @hybrid_property def length(self): return self.end - self.start - @hybrid.method + @hybrid_method def contains(self,point): return (self.start <= point) & (point < self.end) - @hybrid.method + @hybrid_method def intersects(self, other): return self.contains(other.start) | self.contains(other.end) @@ -56,7 +56,7 @@ from sqlalchemy import util from sqlalchemy.orm import attributes, interfaces -class method(object): +class hybrid_method(object): def __init__(self, func, expr=None): self.func = func self.expr = expr or func @@ -71,7 +71,7 @@ self.expr = expr return self -class property_(object): +class hybrid_property(object): def __init__(self, fget, fset=None, fdel=None, expr=None): self.fget = fget self.fset = fset @@ -107,7 +107,7 @@ proxy_attr = attributes.\ create_proxied_attribute(self) def expr(owner): - return proxy_attr(self.__name__, self, comparator(owner)) + return proxy_attr(owner, self.__name__, self, comparator(owner)) self.expr = expr return self diff -r cb63dc77032d -r ca50d1b232a4 lib/sqlalchemy/schema.py --- a/lib/sqlalchemy/schema.py Wed Jan 12 11:31:13 2011 -0500 +++ b/lib/sqlalchemy/schema.py Wed Jan 12 15:49:59 2011 -0500 @@ -325,9 +325,11 @@ if col.autoincrement and \ issubclass(col.type._type_affinity, types.Integer) and \ not col.foreign_keys and \ - isinstance(col.default, (type(None), Sequence)) and \ - col.server_default is None: - + isinstance(col.default, (type(None), Sequence)): + # don't look at server_default here since different backends may + # or may not have a server_default, e.g. postgresql reflected + # SERIAL cols will have a DefaultClause here but are still + # autoincrement. return col @property diff -r cb63dc77032d -r ca50d1b232a4 test/sql/test_defaults.py --- a/test/sql/test_defaults.py Wed Jan 12 11:31:13 2011 -0500 +++ b/test/sql/test_defaults.py Wed Jan 12 15:49:59 2011 -0500 @@ -746,7 +746,6 @@ self._run_test(Sequence('foo_seq')) @testing.fails_on('mysql', "Pending [ticket:2021]") - @testing.fails_on('sqlite', "Pending [ticket:2021]") def test_server_default(self): # note that the MySQL dialect has to not render AUTOINCREMENT on this one self._run_test(server_default='1',) |