diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-07-23 18:22:06 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-07-23 18:22:06 -0400 |
| commit | 65bdf245c6cfd4381f8463714fbec1880a950fbb (patch) | |
| tree | 8ecbc4326e4379eb6c6dd964171a8686cc1be770 /lib/sqlalchemy | |
| parent | ee0f80b4f028e27f35ae851e37e4070a9179b2a1 (diff) | |
| download | sqlalchemy-65bdf245c6cfd4381f8463714fbec1880a950fbb.tar.gz | |
- [feature] ORM entities can be passed
to select() as well as the select_from(),
correlate(), and correlate_except()
methods, where they will be unwrapped
into selectables. [ticket:2245]
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 13 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/properties.py | 1 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/query.py | 13 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/util.py | 7 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/expression.py | 54 |
6 files changed, 63 insertions, 27 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 47349e64a..045a9465d 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -112,9 +112,11 @@ PASSIVE_ONLY_PERSISTENT = util.symbol("PASSIVE_ONLY_PERSISTENT", ) -class QueryableAttribute(interfaces.PropComparator): +class QueryableAttribute(interfaces._InspectionAttr, interfaces.PropComparator): """Base class for class-bound attributes. """ + is_attribute = True + def __init__(self, class_, key, impl=None, comparator=None, parententity=None, of_type=None): @@ -149,6 +151,10 @@ class QueryableAttribute(interfaces.PropComparator): # TODO: conditionally attach this method based on clause_element ? return self + @property + def expression(self): + return self.comparator.__clause_element__() + def __clause_element__(self): return self.comparator.__clause_element__() @@ -191,10 +197,7 @@ class QueryableAttribute(interfaces.PropComparator): def property(self): return self.comparator.property - -@inspection._inspects(QueryableAttribute) -def _get_prop(source): - return source.property +inspection._self_inspects(QueryableAttribute) class InstrumentedAttribute(QueryableAttribute): """Class bound instrumented attribute which adds descriptor methods.""" diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 84c75525a..d0732b913 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -62,6 +62,8 @@ class _InspectionAttr(object): is_instance = False is_mapper = False is_property = False + is_attribute = False + is_clause_element = False class MapperProperty(_InspectionAttr): """Manage the relationship of a ``Mapper`` to a single class diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 4533bbdb0..53ee1b5fd 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -102,6 +102,7 @@ class ColumnProperty(StrategizedProperty): else: self.strategy_class = strategies.ColumnLoader + @property def expression(self): """Return the primary column or expression for this ColumnProperty. diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 35c70d51e..0a345f284 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -26,10 +26,11 @@ from . import ( ) from .util import ( AliasedClass, ORMAdapter, _entity_descriptor, PathRegistry, - _is_aliased_class, _is_mapped_class, _orm_columns, _orm_selectable, + _is_aliased_class, _is_mapped_class, _orm_columns, join as orm_join,with_parent, aliased ) -from .. import sql, util, log, exc as sa_exc, inspect +from .. import sql, util, log, exc as sa_exc, inspect, inspection +from ..sql.expression import _interpret_as_from from ..sql import ( util as sql_util, expression, visitors @@ -539,6 +540,9 @@ class Query(object): return self.enable_eagerloads(False).statement.as_scalar() + @property + def selectable(self): + return self.__clause_element__() def __clause_element__(self): return self.enable_eagerloads(False).with_labels().statement @@ -798,7 +802,8 @@ class Query(object): """ self._correlate = self._correlate.union( - _orm_selectable(s) + _interpret_as_from(s) + if s is not None else None for s in args) @_generative() @@ -2672,7 +2677,6 @@ class Query(object): statement.append_order_by(*context.eager_order_by) return statement - def _adjust_for_single_inheritance(self, context): """Apply single-table-inheritance filtering. @@ -2696,6 +2700,7 @@ class Query(object): def __str__(self): return str(self._compile_context().statement) +inspection._self_inspects(Query) class _QueryEntity(object): """represent an entity column returned within a Query result.""" diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index 8f340d366..27d9b1b69 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -1074,13 +1074,6 @@ def _orm_columns(entity): else: return [entity] -def _orm_selectable(entity): - insp = inspection.inspect(entity, False) - if hasattr(insp, 'selectable'): - return insp.selectable - else: - return entity - def has_identity(object): state = attributes.instance_state(object) return state.has_identity diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index b9c149954..a518852d8 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -1426,6 +1426,19 @@ def _literal_as_text(element): "SQL expression object or string expected." ) +def _interpret_as_from(element): + insp = inspection.inspect(element, raiseerr=False) + if insp is None: + if isinstance(element, (util.NoneType, bool)): + return _const_expr(element) + elif isinstance(element, basestring): + return TextClause(unicode(element)) + elif hasattr(insp, "selectable"): + return insp.selectable + else: + raise exc.ArgumentError("FROM expression expected") + + def _const_expr(element): if element is None: return null() @@ -1445,12 +1458,15 @@ def _clause_element_as_expr(element): return element def _literal_as_column(element): - if isinstance(element, Visitable): - return element - elif hasattr(element, '__clause_element__'): - return element.__clause_element__() - else: - return literal_column(str(element)) + insp = inspection.inspect(element, raiseerr=False) + if insp is not None: + if hasattr(insp, "expression"): + return insp.expression + elif hasattr(insp, "selectable"): + return insp.selectable + elif insp.is_clause_element: + return insp + return literal_column(str(element)) def _literal_as_binds(element, name=None, type_=None): if hasattr(element, '__clause_element__'): @@ -1539,6 +1555,7 @@ class ClauseElement(Visitable): bind = None _is_clone_of = None is_selectable = False + is_clause_element = True def _clone(self): """Create a shallow copy of this ClauseElement. @@ -2174,6 +2191,15 @@ class ColumnElement(ClauseElement, CompareMixin): _alt_names = () @property + def expression(self): + """Return a column expression. + + Part of the inspection interface; returns self. + + """ + return self + + @property def _select_iterable(self): return (self, ) @@ -2973,6 +2999,10 @@ class TextClause(Executable, ClauseElement): def _select_iterable(self): return (self,) + @property + def selectable(self): + return self + _hide_froms = [] def __init__( @@ -5315,7 +5345,8 @@ class Select(SelectBase): if fromclauses and fromclauses[0] is None: self._correlate = () else: - self._correlate = set(self._correlate).union(fromclauses) + self._correlate = set(self._correlate).union( + _interpret_as_from(f) for f in fromclauses) @_generative def correlate_except(self, *fromclauses): @@ -5323,15 +5354,16 @@ class Select(SelectBase): if fromclauses and fromclauses[0] is None: self._correlate_except = () else: - self._correlate_except = set(self._correlate_except - ).union(fromclauses) + self._correlate_except = set(self._correlate_except).union( + _interpret_as_from(f) for f in fromclauses) def append_correlation(self, fromclause): """append the given correlation expression to this select() construct.""" self._should_correlate = False - self._correlate = set(self._correlate).union([fromclause]) + self._correlate = set(self._correlate).union( + _interpret_as_from(f) for f in fromclause) def append_column(self, column): """append the given column expression to the columns clause of this @@ -5387,7 +5419,7 @@ class Select(SelectBase): """ self._reset_exported() - fromclause = _literal_as_text(fromclause) + fromclause = _interpret_as_from(fromclause) self._from_obj = self._from_obj.union([fromclause]) def _populate_column_collection(self): |
