From: Mike Bayer Date: Mon, 20 Dec 2010 21:04:05 +0000 (-0500) Subject: - move the "descriptor" properties into a separate module. X-Git-Tag: rel_0_7b1~137 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1bfaa104188d0205a49a2f5b633a07482ee97602;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - move the "descriptor" properties into a separate module. --- diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py new file mode 100644 index 0000000000..f9df47030d --- /dev/null +++ b/lib/sqlalchemy/orm/descriptor_props.py @@ -0,0 +1,302 @@ +# descriptor_props.py +# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Michael Bayer +# mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""Descriptor proprerties are more "auxilliary" properties +that exist as configurational elements, but don't participate +as actively in the load/persist ORM loop. They all +build on the "hybrid" extension to produce class descriptors. + +""" + +from sqlalchemy.orm.interfaces import \ + MapperProperty, PropComparator, StrategizedProperty +from sqlalchemy.orm import attributes +from sqlalchemy import util, sql, exc as sa_exc +from sqlalchemy.sql import expression +properties = util.importlater('sqlalchemy.orm', 'properties') + +class DescriptorProperty(MapperProperty): + """:class:`MapperProperty` which proxies access to a + user-defined descriptor.""" + + def instrument_class(self, mapper): + from sqlalchemy.ext import hybrid + + prop = self + + # hackety hack hack + class _ProxyImpl(object): + accepts_scalar_loader = False + expire_missing = True + + def __init__(self, key): + self.key = key + + if hasattr(prop, 'get_history'): + def get_history(self, state, dict_, **kw): + return prop.get_history(state, dict_, **kw) + + if self.descriptor is None: + desc = getattr(mapper.class_, self.key, None) + if mapper._is_userland_descriptor(desc): + self.descriptor = desc + + if self.descriptor is None: + def fset(obj, value): + setattr(obj, self.name, value) + def fdel(obj): + delattr(obj, self.name) + def fget(obj): + return getattr(obj, self.name) + fget.__doc__ = self.doc + + descriptor = hybrid.property_( + fget=fget, + fset=fset, + fdel=fdel, + ) + elif isinstance(self.descriptor, property): + descriptor = hybrid.property_( + fget=self.descriptor.fget, + fset=self.descriptor.fset, + fdel=self.descriptor.fdel, + ) + else: + descriptor = hybrid.property_( + fget=self.descriptor.__get__, + fset=self.descriptor.__set__, + fdel=self.descriptor.__delete__, + ) + + proxy_attr = attributes.\ + create_proxied_attribute(self.descriptor or descriptor)\ + ( + self.key, + self.descriptor or descriptor, + lambda: self._comparator_factory(mapper) + ) + def get_comparator(owner): + return util.update_wrapper(proxy_attr, descriptor) + descriptor.expr = get_comparator + descriptor.impl = _ProxyImpl(self.key) + mapper.class_manager.instrument_attribute(self.key, descriptor) + + def setup(self, context, entity, path, adapter, **kwargs): + pass + + def create_row_processor(self, selectcontext, path, mapper, row, adapter): + return None, None, None + + def merge(self, session, source_state, source_dict, + dest_state, dest_dict, load, _recursive): + pass + +class CompositeProperty(DescriptorProperty): + + def __init__(self, class_, *columns, **kwargs): + self.columns = columns + self.composite_class = class_ + self.active_history = kwargs.get('active_history', False) + self.deferred = kwargs.get('deferred', False) + self.group = kwargs.get('group', None) + + prop = self + def fget(instance): + return prop.composite_class( + *[getattr(instance, prop.parent._columntoproperty[col].key) + for col in prop.columns] + ) + def fset(instance, value): + if value is None: + fdel(instance) + else: + for col, value in zip(prop.columns, value.__composite_values__()): + setattr(instance, prop.parent._columntoproperty[col].key, value) + + def fdel(instance): + for col in prop.columns: + setattr(instance, prop.parent._columntoproperty[col].key, None) + self.descriptor = property(fget, fset, fdel) + + def get_history(self, state, dict_, **kw): + """Provided for userland code that uses attributes.get_history().""" + + added = [] + deleted = [] + + has_history = False + for col in self.columns: + key = self.parent._columntoproperty[col].key + hist = state.manager[key].impl.get_history(state, dict_) + if hist.has_changes(): + has_history = True + + added.extend(hist.non_deleted()) + if hist.deleted: + deleted.extend(hist.deleted) + else: + deleted.append(None) + + if has_history: + return attributes.History( + [self.composite_class(*added)], + (), + [self.composite_class(*deleted)] + ) + else: + return attributes.History( + (),[self.composite_class(*added)], () + ) + + def do_init(self): + for col in self.columns: + prop = self.parent._columntoproperty[col] + prop.active_history = self.active_history + if self.deferred: + prop.deferred = self.deferred + prop.strategy_class = strategies.DeferredColumnLoader + prop.group = self.group + # strategies ... + + def _comparator_factory(self, mapper): + return CompositeProperty.Comparator(self) + + class Comparator(PropComparator): + def __init__(self, prop, adapter=None): + self.prop = prop + self.adapter = adapter + + def __clause_element__(self): + if self.adapter: + # TODO: test coverage for adapted composite comparison + return expression.ClauseList( + *[self.adapter(x) for x in self.prop.columns]) + else: + return expression.ClauseList(*self.prop.columns) + + __hash__ = None + + def __eq__(self, other): + if other is None: + values = [None] * len(self.prop.columns) + else: + values = other.__composite_values__() + return sql.and_( + *[a==b for a, b in zip(self.prop.columns, values)]) + + def __ne__(self, other): + return sql.not_(self.__eq__(other)) + + def __str__(self): + return str(self.parent.class_.__name__) + "." + self.key + +class ConcreteInheritedProperty(DescriptorProperty): + """A 'do nothing' :class:`MapperProperty` that disables + an attribute on a concrete subclass that is only present + on the inherited mapper, not the concrete classes' mapper. + + Cases where this occurs include: + + * When the superclass mapper is mapped against a + "polymorphic union", which includes all attributes from + all subclasses. + * When a relationship() is configured on an inherited mapper, + but not on the subclass mapper. Concrete mappers require + that relationship() is configured explicitly on each + subclass. + + """ + + def _comparator_factory(self, mapper): + comparator_callable = None + + for m in self.parent.iterate_to_root(): + p = m._props[self.key] + if not isinstance(p, ConcreteInheritedProperty): + comparator_callable = p.comparator_factory + break + return comparator_callable + + def __init__(self): + def warn(): + raise AttributeError("Concrete %s does not implement " + "attribute %r at the instance level. Add this " + "property explicitly to %s." % + (self.parent, self.key, self.parent)) + + class NoninheritedConcreteProp(object): + def __set__(s, obj, value): + warn() + def __delete__(s, obj): + warn() + def __get__(s, obj, owner): + if obj is None: + return self.descriptor + warn() + self.descriptor = NoninheritedConcreteProp() + + +class SynonymProperty(DescriptorProperty): + + def __init__(self, name, map_column=None, + descriptor=None, comparator_factory=None, + doc=None): + self.name = name + self.map_column = map_column + self.descriptor = descriptor + self.comparator_factory = comparator_factory + self.doc = doc or (descriptor and descriptor.__doc__) or None + util.set_creation_order(self) + + def _comparator_factory(self, mapper): + prop = getattr(mapper.class_, self.name).property + + if self.comparator_factory: + comp = self.comparator_factory(prop, mapper) + else: + comp = prop.comparator_factory(prop, mapper) + return comp + + def set_parent(self, parent, init): + if self.map_column: + # implement the 'map_column' option. + if self.key not in parent.mapped_table.c: + raise sa_exc.ArgumentError( + "Can't compile synonym '%s': no column on table " + "'%s' named '%s'" + % (self.name, parent.mapped_table.description, self.key)) + elif parent.mapped_table.c[self.key] in \ + parent._columntoproperty and \ + parent._columntoproperty[ + parent.mapped_table.c[self.key] + ].key == self.name: + raise sa_exc.ArgumentError( + "Can't call map_column=True for synonym %r=%r, " + "a ColumnProperty already exists keyed to the name " + "%r for column %r" % + (self.key, self.name, self.name, self.key) + ) + p = properties.ColumnProperty(parent.mapped_table.c[self.key]) + parent._configure_property( + self.name, p, + init=init, + setparent=True) + p._mapped_by_synonym = self.key + + self.parent = parent + +class ComparableProperty(DescriptorProperty): + """Instruments a Python property for use in query expressions.""" + + def __init__(self, comparator_factory, descriptor=None, doc=None): + self.descriptor = descriptor + self.comparator_factory = comparator_factory + self.doc = doc or (descriptor and descriptor.__doc__) or None + util.set_creation_order(self) + + def _comparator_factory(self, mapper): + return self.comparator_factory(self, mapper) diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index f16376ce60..c7aa0eeea4 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -27,9 +27,10 @@ mapperlib = util.importlater("sqlalchemy.orm", "mapperlib") NoneType = type(None) __all__ = ('ColumnProperty', 'CompositeProperty', 'SynonymProperty', - 'ComparableProperty', 'RelationshipProperty', 'RelationProperty', - 'BackRef') + 'ComparableProperty', 'RelationshipProperty', 'RelationProperty') +from descriptor_props import CompositeProperty, SynonymProperty, \ + ComparableProperty,ConcreteInheritedProperty class ColumnProperty(StrategizedProperty): """Describes an object attribute that corresponds to a table column.""" @@ -163,287 +164,6 @@ class ColumnProperty(StrategizedProperty): log.class_logger(ColumnProperty) -class DescriptorProperty(MapperProperty): - """:class:`MapperProperty` which proxies access to a - user-defined descriptor.""" - - def instrument_class(self, mapper): - from sqlalchemy.ext import hybrid - - prop = self - - # hackety hack hack - class _ProxyImpl(object): - accepts_scalar_loader = False - expire_missing = True - - def __init__(self, key): - self.key = key - - if hasattr(prop, 'get_history'): - def get_history(self, state, dict_, **kw): - return prop.get_history(state, dict_, **kw) - - if self.descriptor is None: - desc = getattr(mapper.class_, self.key, None) - if mapper._is_userland_descriptor(desc): - self.descriptor = desc - - if self.descriptor is None: - def fset(obj, value): - setattr(obj, self.name, value) - def fdel(obj): - delattr(obj, self.name) - def fget(obj): - return getattr(obj, self.name) - fget.__doc__ = self.doc - - descriptor = hybrid.property_( - fget=fget, - fset=fset, - fdel=fdel, - ) - elif isinstance(self.descriptor, property): - descriptor = hybrid.property_( - fget=self.descriptor.fget, - fset=self.descriptor.fset, - fdel=self.descriptor.fdel, - ) - else: - descriptor = hybrid.property_( - fget=self.descriptor.__get__, - fset=self.descriptor.__set__, - fdel=self.descriptor.__delete__, - ) - - proxy_attr = attributes.\ - create_proxied_attribute(self.descriptor or descriptor)\ - ( - self.key, - self.descriptor or descriptor, - lambda: self._comparator_factory(mapper) - ) - def get_comparator(owner): - return util.update_wrapper(proxy_attr, descriptor) - descriptor.expr = get_comparator - descriptor.impl = _ProxyImpl(self.key) - mapper.class_manager.instrument_attribute(self.key, descriptor) - - def setup(self, context, entity, path, adapter, **kwargs): - pass - - def create_row_processor(self, selectcontext, path, mapper, row, adapter): - return None, None, None - - def merge(self, session, source_state, source_dict, - dest_state, dest_dict, load, _recursive): - pass - -class CompositeProperty(DescriptorProperty): - - def __init__(self, class_, *columns, **kwargs): - self.columns = columns - self.composite_class = class_ - self.active_history = kwargs.get('active_history', False) - self.deferred = kwargs.get('deferred', False) - self.group = kwargs.get('group', None) - - prop = self - def fget(instance): - return prop.composite_class( - *[getattr(instance, prop.parent._columntoproperty[col].key) - for col in prop.columns] - ) - def fset(instance, value): - if value is None: - fdel(instance) - else: - for col, value in zip(prop.columns, value.__composite_values__()): - setattr(instance, prop.parent._columntoproperty[col].key, value) - - def fdel(instance): - for col in prop.columns: - setattr(instance, prop.parent._columntoproperty[col].key, None) - self.descriptor = property(fget, fset, fdel) - - def get_history(self, state, dict_, **kw): - """Provided for userland code that uses attributes.get_history().""" - - added = [] - deleted = [] - - has_history = False - for col in self.columns: - key = self.parent._columntoproperty[col].key - hist = state.manager[key].impl.get_history(state, dict_) - if hist.has_changes(): - has_history = True - - added.extend(hist.non_deleted()) - if hist.deleted: - deleted.extend(hist.deleted) - else: - deleted.append(None) - - if has_history: - return attributes.History( - [self.composite_class(*added)], - (), - [self.composite_class(*deleted)] - ) - else: - return attributes.History( - (),[self.composite_class(*added)], () - ) - - def do_init(self): - for col in self.columns: - prop = self.parent._columntoproperty[col] - prop.active_history = self.active_history - if self.deferred: - prop.deferred = self.deferred - prop.strategy_class = strategies.DeferredColumnLoader - prop.group = self.group - # strategies ... - - def _comparator_factory(self, mapper): - return CompositeProperty.Comparator(self) - - class Comparator(PropComparator): - def __init__(self, prop, adapter=None): - self.prop = prop - self.adapter = adapter - - def __clause_element__(self): - if self.adapter: - # TODO: test coverage for adapted composite comparison - return expression.ClauseList( - *[self.adapter(x) for x in self.prop.columns]) - else: - return expression.ClauseList(*self.prop.columns) - - __hash__ = None - - def __eq__(self, other): - if other is None: - values = [None] * len(self.prop.columns) - else: - values = other.__composite_values__() - return sql.and_( - *[a==b for a, b in zip(self.prop.columns, values)]) - - def __ne__(self, other): - return sql.not_(self.__eq__(other)) - - def __str__(self): - return str(self.parent.class_.__name__) + "." + self.key - -class ConcreteInheritedProperty(DescriptorProperty): - """A 'do nothing' :class:`MapperProperty` that disables - an attribute on a concrete subclass that is only present - on the inherited mapper, not the concrete classes' mapper. - - Cases where this occurs include: - - * When the superclass mapper is mapped against a - "polymorphic union", which includes all attributes from - all subclasses. - * When a relationship() is configured on an inherited mapper, - but not on the subclass mapper. Concrete mappers require - that relationship() is configured explicitly on each - subclass. - - """ - - def _comparator_factory(self, mapper): - comparator_callable = None - - for m in self.parent.iterate_to_root(): - p = m._props[self.key] - if not isinstance(p, ConcreteInheritedProperty): - comparator_callable = p.comparator_factory - break - return comparator_callable - - def __init__(self): - def warn(): - raise AttributeError("Concrete %s does not implement " - "attribute %r at the instance level. Add this " - "property explicitly to %s." % - (self.parent, self.key, self.parent)) - - class NoninheritedConcreteProp(object): - def __set__(s, obj, value): - warn() - def __delete__(s, obj): - warn() - def __get__(s, obj, owner): - if obj is None: - return self.descriptor - warn() - self.descriptor = NoninheritedConcreteProp() - - -class SynonymProperty(DescriptorProperty): - - def __init__(self, name, map_column=None, - descriptor=None, comparator_factory=None, - doc=None): - self.name = name - self.map_column = map_column - self.descriptor = descriptor - self.comparator_factory = comparator_factory - self.doc = doc or (descriptor and descriptor.__doc__) or None - util.set_creation_order(self) - - def _comparator_factory(self, mapper): - prop = getattr(mapper.class_, self.name).property - - if self.comparator_factory: - comp = self.comparator_factory(prop, mapper) - else: - comp = prop.comparator_factory(prop, mapper) - return comp - - def set_parent(self, parent, init): - if self.map_column: - # implement the 'map_column' option. - if self.key not in parent.mapped_table.c: - raise sa_exc.ArgumentError( - "Can't compile synonym '%s': no column on table " - "'%s' named '%s'" - % (self.name, parent.mapped_table.description, self.key)) - elif parent.mapped_table.c[self.key] in \ - parent._columntoproperty and \ - parent._columntoproperty[ - parent.mapped_table.c[self.key] - ].key == self.name: - raise sa_exc.ArgumentError( - "Can't call map_column=True for synonym %r=%r, " - "a ColumnProperty already exists keyed to the name " - "%r for column %r" % - (self.key, self.name, self.name, self.key) - ) - p = ColumnProperty(parent.mapped_table.c[self.key]) - parent._configure_property( - self.name, p, - init=init, - setparent=True) - p._mapped_by_synonym = self.key - - self.parent = parent - -class ComparableProperty(DescriptorProperty): - """Instruments a Python property for use in query expressions.""" - - def __init__(self, comparator_factory, descriptor=None, doc=None): - self.descriptor = descriptor - self.comparator_factory = comparator_factory - self.doc = doc or (descriptor and descriptor.__doc__) or None - util.set_creation_order(self) - - def _comparator_factory(self, mapper): - return self.comparator_factory(self, mapper) class RelationshipProperty(StrategizedProperty):