diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/sqlalchemy/orm/__init__.py | 3 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/descriptor_props.py | 27 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 77 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/properties.py | 43 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/compiler.py | 11 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/expression.py | 19 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/operators.py | 16 | ||||
-rw-r--r-- | lib/sqlalchemy/types.py | 75 |
8 files changed, 226 insertions, 45 deletions
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 1a22fe3d1..2078b2396 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -760,6 +760,9 @@ def composite(class_, *cols, **kwargs): See the mapping documentation section :ref:`mapper_composite` for a full usage example. + The :class:`.MapperProperty` returned by :func:`.composite` + is the :class:`.CompositeProperty`. + :param class\_: The "composite type" class. diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py index 91717974d..f4c2e1a90 100644 --- a/lib/sqlalchemy/orm/descriptor_props.py +++ b/lib/sqlalchemy/orm/descriptor_props.py @@ -73,7 +73,17 @@ class DescriptorProperty(MapperProperty): class CompositeProperty(DescriptorProperty): + """Defines a "composite" mapped attribute, representing a collection + of columns as one attribute. + :class:`.CompositeProperty` is constructed using the :func:`.composite` + function. + + See also: + + :ref:`mapper_composite` + + """ def __init__(self, class_, *attrs, **kwargs): self.attrs = attrs self.composite_class = class_ @@ -279,6 +289,23 @@ class CompositeProperty(DescriptorProperty): return self.comparator_factory(self) class Comparator(PropComparator): + """Produce boolean, comparison, and other operators for + :class:`.CompositeProperty` attributes. + + See the example in :ref:`composite_operations` for an overview + of usage , as well as the documentation for :class:`.PropComparator`. + + See also: + + :class:`.PropComparator` + + :class:`.ColumnOperators` + + :ref:`types_operators` + + :attr:`.TypeEngine.comparator_factory` + + """ def __init__(self, prop, adapter=None): self.prop = self.property = prop self.adapter = adapter diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index d0732b913..f41c5894e 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -196,28 +196,91 @@ class MapperProperty(_InspectionAttr): return operator(self.comparator, value) class PropComparator(operators.ColumnOperators): - """Defines comparison operations for MapperProperty objects. + """Defines boolean, comparison, and other operators for + :class:`.MapperProperty` objects. + + SQLAlchemy allows for operators to + be redefined at both the Core and ORM level. :class:`.PropComparator` + is the base class of operator redefinition for ORM-level operations, + including those of :class:`.ColumnProperty`, :class:`.RelationshipProperty`, + and :class:`.CompositeProperty`. + + .. note:: With the advent of Hybrid properties introduced in SQLAlchemy + 0.7, as well as Core-level operator redefinition in + SQLAlchemy 0.8, the use case for user-defined :class:`.PropComparator` + instances is extremely rare. See :ref:`hybrids_toplevel` as well + as :ref:`types_operators`. User-defined subclasses of :class:`.PropComparator` may be created. The built-in Python comparison and math operator methods, such as - ``__eq__()``, ``__lt__()``, ``__add__()``, can be overridden to provide + :meth:`.operators.ColumnOperators.__eq__`, + :meth:`.operators.ColumnOperators.__lt__`, and + :meth:`.operators.ColumnOperators.__add__`, can be overridden to provide new operator behavior. The custom :class:`.PropComparator` is passed to - the mapper property via the ``comparator_factory`` argument. In each case, + the :class:`.MapperProperty` instance via the ``comparator_factory`` + argument. In each case, the appropriate subclass of :class:`.PropComparator` should be used:: + # definition of custom PropComparator subclasses + from sqlalchemy.orm.properties import \\ ColumnProperty,\\ CompositeProperty,\\ RelationshipProperty class MyColumnComparator(ColumnProperty.Comparator): - pass + def __eq__(self, other): + return self.__clause_element__() == other + + class MyRelationshipComparator(RelationshipProperty.Comparator): + def any(self, expression): + "define the 'any' operation" + # ... class MyCompositeComparator(CompositeProperty.Comparator): - pass + def __gt__(self, other): + "redefine the 'greater than' operation" - class MyRelationshipComparator(RelationshipProperty.Comparator): - pass + return sql.and_(*[a>b for a, b in + zip(self.__clause_element__().clauses, + other.__composite_values__())]) + + + # application of custom PropComparator subclasses + + from sqlalchemy.orm import column_property, relationship, composite + from sqlalchemy import Column, String + + class SomeMappedClass(Base): + some_column = column_property(Column("some_column", String), + comparator_factory=MyColumnComparator) + + some_relationship = relationship(SomeOtherClass, + comparator_factory=MyRelationshipComparator) + + some_composite = composite( + Column("a", String), Column("b", String), + comparator_factory=MyCompositeComparator + ) + + Note that for column-level operator redefinition, it's usually + simpler to define the operators at the Core level, using the + :attr:`.TypeEngine.comparator_factory` attribute. See + :ref:`types_operators` for more detail. + + See also: + + :class:`.ColumnProperty.Comparator` + + :class:`.RelationshipProperty.Comparator` + + :class:`.CompositeProperty.Comparator` + + :class:`.ColumnOperators` + + :ref:`types_operators` + + :attr:`.TypeEngine.comparator_factory` """ diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 62e4672d3..f52e914f7 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -161,6 +161,22 @@ class ColumnProperty(StrategizedProperty): dest_state._expire_attributes(dest_dict, [self.key]) class Comparator(PropComparator): + """Produce boolean, comparison, and other operators for + :class:`.ColumnProperty` attributes. + + See the documentation for :class:`.PropComparator` for a brief overview. + + See also: + + :class:`.PropComparator` + + :class:`.ColumnOperators` + + :ref:`types_operators` + + :attr:`.TypeEngine.comparator_factory` + + """ @util.memoized_instancemethod def __clause_element__(self): if self.adapter: @@ -198,9 +214,9 @@ class RelationshipProperty(StrategizedProperty): Public constructor is the :func:`.orm.relationship` function. - Of note here is the :class:`.RelationshipProperty.Comparator` - class, which implements comparison operations for scalar- - and collection-referencing mapped attributes. + See also: + + :ref:`relationship_config_toplevel` """ @@ -304,8 +320,25 @@ class RelationshipProperty(StrategizedProperty): ) class Comparator(PropComparator): - """Produce comparison operations for :func:`~.orm.relationship`-based - attributes.""" + """Produce boolean, comparison, and other operators for + :class:`.RelationshipProperty` attributes. + + See the documentation for :class:`.PropComparator` for a brief overview + of ORM level operator definition. + + See also: + + :class:`.PropComparator` + + :class:`.ColumnProperty.Comparator` + + :class:`.ColumnOperators` + + :ref:`types_operators` + + :attr:`.TypeEngine.comparator_factory` + + """ def __init__(self, prop, mapper, of_type=None, adapter=None): """Construction of :class:`.RelationshipProperty.Comparator` diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index f975225d6..49df9322e 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -948,8 +948,15 @@ class SQLCompiler(engine.Compiled): else: add_to_result_map = None - if isinstance(col_expr, sql.Label): - result_expr = col_expr + if isinstance(column, sql.Label): + if col_expr is not column: + result_expr = _CompileLabel( + col_expr, + column.name, + alt_names=(column.element,) + ) + else: + result_expr = col_expr elif select is not None and \ select.use_labels and \ diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 63fa23c15..e8905ccec 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -174,8 +174,8 @@ def select(columns=None, whereclause=None, from_obj=[], **kwargs): See also: - :ref:`coretutorial_selecting` - Core Tutorial description - of :func:`.select`. + :ref:`coretutorial_selecting` - Core Tutorial description of + :func:`.select`. :param columns: A list of :class:`.ClauseElement` objects, typically @@ -899,9 +899,14 @@ def type_coerce(expr, type_): ) """ + type_ = sqltypes.to_instance(type_) + if hasattr(expr, '__clause_expr__'): return type_coerce(expr.__clause_expr__()) - + elif isinstance(expr, BindParameter): + bp = expr._clone() + bp.type = type_ + return bp elif not isinstance(expr, Visitable): if expr is None: return null() @@ -1177,7 +1182,7 @@ def over(func, partition_by=None, order_by=None): Would produce "ROW_NUMBER() OVER(ORDER BY x)". :param func: a :class:`.FunctionElement` construct, typically - generated by :attr:`~.expression.func`. + generated by :data:`~.expression.func`. :param partition_by: a column element or string, or a list of such, that will be used as the PARTITION BY clause of the OVER construct. @@ -1185,7 +1190,7 @@ def over(func, partition_by=None, order_by=None): of such, that will be used as the ORDER BY clause of the OVER construct. - This function is also available from the :attr:`~.expression.func` + This function is also available from the :data:`~.expression.func` construct itself via the :meth:`.FunctionElement.over` method. .. versionadded:: 0.7 @@ -2867,7 +2872,7 @@ class BindParameter(ColumnElement): if type_ is None: if _compared_to_type is not None: self.type = \ - _compared_to_type._coerce_compared_value( + _compared_to_type.coerce_compared_value( _compared_to_operator, value) else: self.type = sqltypes._type_map.get(type(value), @@ -3470,7 +3475,7 @@ class Function(FunctionElement): def __init__(self, name, *clauses, **kw): """Construct a :class:`.Function`. - The :attr:`.func` construct is normally used to construct + The :data:`.func` construct is normally used to construct new :class:`.Function` instances. """ diff --git a/lib/sqlalchemy/sql/operators.py b/lib/sqlalchemy/sql/operators.py index ba1117ef6..07fb417fb 100644 --- a/lib/sqlalchemy/sql/operators.py +++ b/lib/sqlalchemy/sql/operators.py @@ -205,7 +205,8 @@ class custom_op(object): class ColumnOperators(Operators): - """Defines comparison and math operations. + """Defines boolean, comparison, and other operators for + :class:`.ColumnElement` expressions. By default, all methods call down to :meth:`.operate` or :meth:`.reverse_operate`, @@ -229,10 +230,15 @@ class ColumnOperators(Operators): so that the ``==`` operation above is replaced by a clause construct. - The docstrings here will describe column-oriented - behavior of each operator. For ORM-based operators - on related objects and collections, see - :class:`.RelationshipProperty.Comparator`. + See also: + + :ref:`types_operators` + + :attr:`.TypeEngine.comparator_factory` + + :class:`.ColumnOperators` + + :class:`.PropComparator` """ diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py index ee262b56b..87d656546 100644 --- a/lib/sqlalchemy/types.py +++ b/lib/sqlalchemy/types.py @@ -164,7 +164,24 @@ class TypeEngine(AbstractType): return None def column_expression(self, colexpr): - """Given a SELECT column expression, return a wrapping SQL expression.""" + """Given a SELECT column expression, return a wrapping SQL expression. + + This is typically a SQL function that wraps a column expression + as rendered in the columns clause of a SELECT statement. + It is used for special data types that require + columns to be wrapped in some special database function in order + to coerce the value before being sent back to the application. + It is the SQL analogue of the :meth:`.TypeEngine.result_processor` + method. + + The method is evaluated at statement compile time, as opposed + to statement construction time. + + See also: + + :ref:`types_sql_value_processing` + + """ return None @@ -177,17 +194,24 @@ class TypeEngine(AbstractType): """"Given a bind value (i.e. a :class:`.BindParameter` instance), return a SQL expression in its place. - This is typically a SQL function that wraps the existing value - in a bind. It is used for special data types that require - literals being wrapped in some special database function in all - cases, such as Postgis GEOMETRY types. + This is typically a SQL function that wraps the existing bound + parameter within the statement. It is used for special data types + that require literals being wrapped in some special database function + in order to coerce an application-level value into a database-specific + format. It is the SQL analogue of the :meth:`.TypeEngine.bind_processor` + method. The method is evaluated at statement compile time, as opposed to statement construction time. Note that this method, when implemented, should always return the exact same structure, without any conditional logic, as it - will be used during executemany() calls as well. + may be used in an executemany() call against an arbitrary number + of bound parameter sets. + + See also: + + :ref:`types_sql_value_processing` """ return None @@ -334,7 +358,7 @@ class TypeEngine(AbstractType): """ return util.constructor_copy(self, cls, **kw) - def _coerce_compared_value(self, op, value): + def coerce_compared_value(self, op, value): """Suggest a type for a 'coerced' Python value in an expression. Given an operator and value, gives the type a chance @@ -460,6 +484,23 @@ class UserDefinedType(TypeEngine): comparator_factory = Comparator + def coerce_compared_value(self, op, value): + """Suggest a type for a 'coerced' Python value in an expression. + + Default behavior for :class:`.UserDefinedType` is the + same as that of :class:`.TypeDecorator`; by default it returns + ``self``, assuming the compared value should be coerced into + the same type as this one. See :meth:`.TypeDecorator.coerce_compared_value` + for more detail. + + .. versionchanged:: 0.8 :meth:`.UserDefinedType.coerce_compared_value` + now returns ``self`` by default, rather than falling onto the + more fundamental behavior of :meth:`.TypeEngine.coerce_compared_value`. + + """ + + return self + class TypeDecorator(TypeEngine): """Allows the creation of types which add additional functionality @@ -602,7 +643,8 @@ class TypeDecorator(TypeEngine): return self.impl._type_affinity def type_engine(self, dialect): - """Return a dialect-specific :class:`.TypeEngine` instance for this :class:`.TypeDecorator`. + """Return a dialect-specific :class:`.TypeEngine` instance + for this :class:`.TypeDecorator`. In most cases this returns a dialect-adapted form of the :class:`.TypeEngine` type represented by ``self.impl``. @@ -778,11 +820,6 @@ class TypeDecorator(TypeEngine): """ return self - def _coerce_compared_value(self, op, value): - """See :meth:`.TypeEngine._coerce_compared_value` for a description.""" - - return self.coerce_compared_value(op, value) - def copy(self): """Produce a copy of this :class:`.TypeDecorator` instance. @@ -1672,13 +1709,13 @@ class _Binary(TypeEngine): return process # end Py2K - def _coerce_compared_value(self, op, value): - """See :meth:`.TypeEngine._coerce_compared_value` for a description.""" + def coerce_compared_value(self, op, value): + """See :meth:`.TypeEngine.coerce_compared_value` for a description.""" if isinstance(value, basestring): return self else: - return super(_Binary, self)._coerce_compared_value(op, value) + return super(_Binary, self).coerce_compared_value(op, value) def get_dbapi_type(self, dbapi): return dbapi.BINARY @@ -2193,10 +2230,10 @@ class Interval(_DateAffinity, TypeDecorator): def _type_affinity(self): return Interval - def _coerce_compared_value(self, op, value): - """See :meth:`.TypeEngine._coerce_compared_value` for a description.""" + def coerce_compared_value(self, op, value): + """See :meth:`.TypeEngine.coerce_compared_value` for a description.""" - return self.impl._coerce_compared_value(op, value) + return self.impl.coerce_compared_value(op, value) class REAL(Float): |