summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext/hybrid.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/ext/hybrid.py')
-rw-r--r--lib/sqlalchemy/ext/hybrid.py192
1 files changed, 96 insertions, 96 deletions
diff --git a/lib/sqlalchemy/ext/hybrid.py b/lib/sqlalchemy/ext/hybrid.py
index 8734181ea..ebf061645 100644
--- a/lib/sqlalchemy/ext/hybrid.py
+++ b/lib/sqlalchemy/ext/hybrid.py
@@ -10,8 +10,8 @@
class level and at the instance level.
The :mod:`~sqlalchemy.ext.hybrid` extension provides a special form of method
-decorator, is around 50 lines of code and has almost no dependencies on the rest
-of SQLAlchemy. It can, in theory, work with any descriptor-based expression
+decorator, is around 50 lines of code and has almost no dependencies on the rest
+of SQLAlchemy. It can, in theory, work with any descriptor-based expression
system.
Consider a mapping ``Interval``, representing integer ``start`` and ``end``
@@ -25,9 +25,9 @@ as the class itself::
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session, aliased
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
-
+
Base = declarative_base()
-
+
class Interval(Base):
__tablename__ = 'interval'
@@ -50,7 +50,7 @@ as the class itself::
@hybrid_method
def intersects(self, other):
return self.contains(other.start) | self.contains(other.end)
-
+
Above, the ``length`` property returns the difference between the ``end`` and
``start`` attributes. With an instance of ``Interval``, this subtraction occurs
in Python, using normal Python descriptor mechanics::
@@ -60,33 +60,33 @@ in Python, using normal Python descriptor mechanics::
5
When dealing with the ``Interval`` class itself, the :class:`.hybrid_property`
-descriptor evaluates the function body given the ``Interval`` class as
+descriptor evaluates the function body given the ``Interval`` class as
the argument, which when evaluated with SQLAlchemy expression mechanics
returns a new SQL expression::
-
+
>>> print Interval.length
interval."end" - interval.start
-
+
>>> print Session().query(Interval).filter(Interval.length > 10)
- SELECT interval.id AS interval_id, interval.start AS interval_start,
- interval."end" AS interval_end
- FROM interval
+ SELECT interval.id AS interval_id, interval.start AS interval_start,
+ interval."end" AS interval_end
+ FROM interval
WHERE interval."end" - interval.start > :param_1
-
-ORM methods such as :meth:`~.Query.filter_by` generally use ``getattr()`` to
+
+ORM methods such as :meth:`~.Query.filter_by` generally use ``getattr()`` to
locate attributes, so can also be used with hybrid attributes::
>>> print Session().query(Interval).filter_by(length=5)
- SELECT interval.id AS interval_id, interval.start AS interval_start,
- interval."end" AS interval_end
- FROM interval
+ SELECT interval.id AS interval_id, interval.start AS interval_start,
+ interval."end" AS interval_end
+ FROM interval
WHERE interval."end" - interval.start = :param_1
The ``Interval`` class example also illustrates two methods, ``contains()`` and ``intersects()``,
decorated with :class:`.hybrid_method`.
This decorator applies the same idea to methods that :class:`.hybrid_property` applies
-to attributes. The methods return boolean values, and take advantage
-of the Python ``|`` and ``&`` bitwise operators to produce equivalent instance-level and
+to attributes. The methods return boolean values, and take advantage
+of the Python ``|`` and ``&`` bitwise operators to produce equivalent instance-level and
SQL expression-level boolean behavior::
>>> i1.contains(6)
@@ -97,24 +97,24 @@ SQL expression-level boolean behavior::
True
>>> i1.intersects(Interval(25, 29))
False
-
+
>>> print Session().query(Interval).filter(Interval.contains(15))
- SELECT interval.id AS interval_id, interval.start AS interval_start,
- interval."end" AS interval_end
- FROM interval
+ SELECT interval.id AS interval_id, interval.start AS interval_start,
+ interval."end" AS interval_end
+ FROM interval
WHERE interval.start <= :start_1 AND interval."end" > :end_1
>>> ia = aliased(Interval)
>>> print Session().query(Interval, ia).filter(Interval.intersects(ia))
- SELECT interval.id AS interval_id, interval.start AS interval_start,
- interval."end" AS interval_end, interval_1.id AS interval_1_id,
- interval_1.start AS interval_1_start, interval_1."end" AS interval_1_end
- FROM interval, interval AS interval_1
- WHERE interval.start <= interval_1.start
- AND interval."end" > interval_1.start
- OR interval.start <= interval_1."end"
+ SELECT interval.id AS interval_id, interval.start AS interval_start,
+ interval."end" AS interval_end, interval_1.id AS interval_1_id,
+ interval_1.start AS interval_1_start, interval_1."end" AS interval_1_end
+ FROM interval, interval AS interval_1
+ WHERE interval.start <= interval_1.start
+ AND interval."end" > interval_1.start
+ OR interval.start <= interval_1."end"
AND interval."end" > interval_1."end"
-
+
Defining Expression Behavior Distinct from Attribute Behavior
--------------------------------------------------------------
@@ -122,18 +122,18 @@ Our usage of the ``&`` and ``|`` bitwise operators above was fortunate, consider
our functions operated on two boolean values to return a new one. In many cases, the construction
of an in-Python function and a SQLAlchemy SQL expression have enough differences that two
separate Python expressions should be defined. The :mod:`~sqlalchemy.ext.hybrid` decorators
-define the :meth:`.hybrid_property.expression` modifier for this purpose. As an example we'll
+define the :meth:`.hybrid_property.expression` modifier for this purpose. As an example we'll
define the radius of the interval, which requires the usage of the absolute value function::
from sqlalchemy import func
-
+
class Interval(object):
# ...
-
+
@hybrid_property
def radius(self):
return abs(self.length) / 2
-
+
@radius.expression
def radius(cls):
return func.abs(cls.length) / 2
@@ -143,22 +143,22 @@ Above the Python function ``abs()`` is used for instance-level operations, the S
>>> i1.radius
2
-
+
>>> print Session().query(Interval).filter(Interval.radius > 5)
- SELECT interval.id AS interval_id, interval.start AS interval_start,
- interval."end" AS interval_end
- FROM interval
+ SELECT interval.id AS interval_id, interval.start AS interval_start,
+ interval."end" AS interval_end
+ FROM interval
WHERE abs(interval."end" - interval.start) / :abs_1 > :param_1
Defining Setters
----------------
-Hybrid properties can also define setter methods. If we wanted ``length`` above, when
+Hybrid properties can also define setter methods. If we wanted ``length`` above, when
set, to modify the endpoint value::
class Interval(object):
# ...
-
+
@hybrid_property
def length(self):
return self.end - self.start
@@ -179,7 +179,7 @@ The ``length(self, value)`` method is now called upon set::
Working with Relationships
--------------------------
-There's no essential difference when creating hybrids that work with related objects as
+There's no essential difference when creating hybrids that work with related objects as
opposed to column-based data. The need for distinct expressions tends to be greater.
Consider the following declarative mapping which relates a ``User`` to a ``SavingsAccount``::
@@ -187,9 +187,9 @@ Consider the following declarative mapping which relates a ``User`` to a ``Savin
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
-
+
Base = declarative_base()
-
+
class SavingsAccount(Base):
__tablename__ = 'account'
id = Column(Integer, primary_key=True)
@@ -200,9 +200,9 @@ Consider the following declarative mapping which relates a ``User`` to a ``Savin
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
-
+
accounts = relationship("SavingsAccount", backref="owner")
-
+
@hybrid_property
def balance(self):
if self.accounts:
@@ -222,17 +222,17 @@ Consider the following declarative mapping which relates a ``User`` to a ``Savin
def balance(cls):
return SavingsAccount.balance
-The above hybrid property ``balance`` works with the first ``SavingsAccount`` entry in the list of
+The above hybrid property ``balance`` works with the first ``SavingsAccount`` entry in the list of
accounts for this user. The in-Python getter/setter methods can treat ``accounts`` as a Python
-list available on ``self``.
+list available on ``self``.
-However, at the expression level, we can't travel along relationships to column attributes
-directly since SQLAlchemy is explicit about joins. So here, it's expected that the ``User`` class will be
+However, at the expression level, we can't travel along relationships to column attributes
+directly since SQLAlchemy is explicit about joins. So here, it's expected that the ``User`` class will be
used in an appropriate context such that an appropriate join to ``SavingsAccount`` will be present::
>>> print Session().query(User, User.balance).join(User.accounts).filter(User.balance > 5000)
SELECT "user".id AS user_id, "user".name AS user_name, account.balance AS account_balance
- FROM "user" JOIN account ON "user".id = account.user_id
+ FROM "user" JOIN account ON "user".id = account.user_id
WHERE account.balance > :balance_1
Note however, that while the instance level accessors need to worry about whether ``self.accounts``
@@ -242,8 +242,8 @@ would use an outer join::
>>> from sqlalchemy import or_
>>> print (Session().query(User, User.balance).outerjoin(User.accounts).
... filter(or_(User.balance < 5000, User.balance == None)))
- SELECT "user".id AS user_id, "user".name AS user_name, account.balance AS account_balance
- FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id
+ SELECT "user".id AS user_id, "user".name AS user_name, account.balance AS account_balance
+ FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id
WHERE account.balance < :balance_1 OR account.balance IS NULL
.. _hybrid_custom_comparators:
@@ -253,7 +253,7 @@ Building Custom Comparators
The hybrid property also includes a helper that allows construction of custom comparators.
A comparator object allows one to customize the behavior of each SQLAlchemy expression
-operator individually. They are useful when creating custom types that have
+operator individually. They are useful when creating custom types that have
some highly idiosyncratic behavior on the SQL side.
The example class below allows case-insensitive comparisons on the attribute
@@ -263,9 +263,9 @@ named ``word_insensitive``::
from sqlalchemy import func, Column, Integer, String
from sqlalchemy.orm import Session
from sqlalchemy.ext.declarative import declarative_base
-
+
Base = declarative_base()
-
+
class CaseInsensitiveComparator(Comparator):
def __eq__(self, other):
return func.lower(self.__clause_element__()) == func.lower(other)
@@ -274,27 +274,27 @@ named ``word_insensitive``::
__tablename__ = 'searchword'
id = Column(Integer, primary_key=True)
word = Column(String(255), nullable=False)
-
+
@hybrid_property
def word_insensitive(self):
return self.word.lower()
-
+
@word_insensitive.comparator
def word_insensitive(cls):
return CaseInsensitiveComparator(cls.word)
-Above, SQL expressions against ``word_insensitive`` will apply the ``LOWER()``
+Above, SQL expressions against ``word_insensitive`` will apply the ``LOWER()``
SQL function to both sides::
>>> print Session().query(SearchWord).filter_by(word_insensitive="Trucks")
- SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
- FROM searchword
+ SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
+ FROM searchword
WHERE lower(searchword.word) = lower(:lower_1)
The ``CaseInsensitiveComparator`` above implements part of the :class:`.ColumnOperators`
interface. A "coercion" operation like lowercasing can be applied to all comparison operations
(i.e. ``eq``, ``lt``, ``gt``, etc.) using :meth:`.Operators.operate`::
-
+
class CaseInsensitiveComparator(Comparator):
def operate(self, op, other):
return op(func.lower(self.__clause_element__()), func.lower(other))
@@ -310,7 +310,7 @@ by ``@word_insensitive.comparator``, only applies to the SQL side.
A more comprehensive form of the custom comparator is to construct a *Hybrid Value Object*.
This technique applies the target value or expression to a value object which is then
returned by the accessor in all cases. The value object allows control
-of all operations upon the value as well as how compared values are treated, both
+of all operations upon the value as well as how compared values are treated, both
on the SQL expression side as well as the Python value side. Replacing the
previous ``CaseInsensitiveComparator`` class with a new ``CaseInsensitiveWord`` class::
@@ -342,8 +342,8 @@ previous ``CaseInsensitiveComparator`` class with a new ``CaseInsensitiveWord``
Above, the ``CaseInsensitiveWord`` object represents ``self.word``, which may be a SQL function,
or may be a Python native. By overriding ``operate()`` and ``__clause_element__()``
to work in terms of ``self.word``, all comparison operations will work against the
-"converted" form of ``word``, whether it be SQL side or Python side.
-Our ``SearchWord`` class can now deliver the ``CaseInsensitiveWord`` object unconditionally
+"converted" form of ``word``, whether it be SQL side or Python side.
+Our ``SearchWord`` class can now deliver the ``CaseInsensitiveWord`` object unconditionally
from a single hybrid call::
class SearchWord(Base):
@@ -356,12 +356,12 @@ from a single hybrid call::
return CaseInsensitiveWord(self.word)
The ``word_insensitive`` attribute now has case-insensitive comparison behavior
-universally, including SQL expression vs. Python expression (note the Python value is
+universally, including SQL expression vs. Python expression (note the Python value is
converted to lower case on the Python side here)::
>>> print Session().query(SearchWord).filter_by(word_insensitive="Trucks")
- SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
- FROM searchword
+ SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
+ FROM searchword
WHERE lower(searchword.word) = :lower_1
SQL expression versus SQL expression::
@@ -369,13 +369,13 @@ SQL expression versus SQL expression::
>>> sw1 = aliased(SearchWord)
>>> sw2 = aliased(SearchWord)
>>> print Session().query(
- ... sw1.word_insensitive,
+ ... sw1.word_insensitive,
... sw2.word_insensitive).\\
... filter(
... sw1.word_insensitive > sw2.word_insensitive
... )
- SELECT lower(searchword_1.word) AS lower_1, lower(searchword_2.word) AS lower_2
- FROM searchword AS searchword_1, searchword AS searchword_2
+ SELECT lower(searchword_1.word) AS lower_1, lower(searchword_2.word) AS lower_2
+ FROM searchword AS searchword_1, searchword AS searchword_2
WHERE lower(searchword_1.word) > lower(searchword_2.word)
Python only expression::
@@ -403,7 +403,7 @@ Building Transformers
----------------------
A *transformer* is an object which can receive a :class:`.Query` object and return a
-new one. The :class:`.Query` object includes a method :meth:`.with_transformation`
+new one. The :class:`.Query` object includes a method :meth:`.with_transformation`
that simply returns a new :class:`.Query` transformed by the given function.
We can combine this with the :class:`.Comparator` class to produce one type
@@ -412,18 +412,18 @@ filtering criterion.
Consider a mapped class ``Node``, which assembles using adjacency list into a hierarchical
tree pattern::
-
+
from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
-
+
class Node(Base):
__tablename__ = 'node'
id =Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('node.id'))
parent = relationship("Node", remote_side=id)
-
+
Suppose we wanted to add an accessor ``grandparent``. This would return the ``parent`` of
``Node.parent``. When we have an instance of ``Node``, this is simple::
@@ -431,7 +431,7 @@ Suppose we wanted to add an accessor ``grandparent``. This would return the ``p
class Node(Base):
# ...
-
+
@hybrid_property
def grandparent(self):
return self.parent.parent
@@ -460,7 +460,7 @@ attribute and filtered based on the given criterion::
id =Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('node.id'))
parent = relationship("Node", remote_side=id)
-
+
@hybrid_property
def grandparent(self):
return self.parent.parent
@@ -486,8 +486,8 @@ using :attr:`.Operators.eq` against the left and right sides, passing into
{sql}>>> session.query(Node).\\
... with_transformation(Node.grandparent==Node(id=5)).\\
... all()
- SELECT node.id AS node_id, node.parent_id AS node_parent_id
- FROM node JOIN node AS node_1 ON node_1.id = node.parent_id
+ SELECT node.id AS node_id, node.parent_id AS node_parent_id
+ FROM node JOIN node AS node_1 ON node_1.id = node.parent_id
WHERE :param_1 = node_1.parent_id
{stop}
@@ -529,14 +529,14 @@ with each class::
{sql}>>> session.query(Node).\\
... with_transformation(Node.grandparent.join).\\
... filter(Node.grandparent==Node(id=5))
- SELECT node.id AS node_id, node.parent_id AS node_parent_id
- FROM node JOIN node AS node_1 ON node_1.id = node.parent_id
+ SELECT node.id AS node_id, node.parent_id AS node_parent_id
+ FROM node JOIN node AS node_1 ON node_1.id = node.parent_id
WHERE :param_1 = node_1.parent_id
{stop}
The "transformer" pattern is an experimental pattern that starts
to make usage of some functional programming paradigms.
-While it's only recommended for advanced and/or patient developers,
+While it's only recommended for advanced and/or patient developers,
there's probably a whole lot of amazing things it can be used for.
"""
@@ -546,26 +546,26 @@ from sqlalchemy.orm import attributes, interfaces
class hybrid_method(object):
"""A decorator which allows definition of a Python object method with both
instance-level and class-level behavior.
-
+
"""
def __init__(self, func, expr=None):
"""Create a new :class:`.hybrid_method`.
-
+
Usage is typically via decorator::
-
+
from sqlalchemy.ext.hybrid import hybrid_method
-
+
class SomeClass(object):
@hybrid_method
def value(self, x, y):
return self._value + x + y
-
+
@value.expression
def value(self, x, y):
return func.some_function(self._value, x, y)
-
+
"""
self.func = func
self.expr = expr or func
@@ -585,25 +585,25 @@ class hybrid_method(object):
class hybrid_property(object):
"""A decorator which allows definition of a Python descriptor with both
instance-level and class-level behavior.
-
+
"""
def __init__(self, fget, fset=None, fdel=None, expr=None):
"""Create a new :class:`.hybrid_property`.
-
+
Usage is typically via decorator::
-
+
from sqlalchemy.ext.hybrid import hybrid_property
-
+
class SomeClass(object):
@hybrid_property
def value(self):
return self._value
-
+
@value.setter
def value(self, value):
self._value = value
-
+
"""
self.fget = fget
self.fset = fset
@@ -647,10 +647,10 @@ class hybrid_property(object):
def comparator(self, comparator):
"""Provide a modifying decorator that defines a custom comparator producing method.
-
+
The return value of the decorated method should be an instance of
:class:`~.hybrid.Comparator`.
-
+
"""
proxy_attr = attributes.\
@@ -660,11 +660,11 @@ class hybrid_property(object):
self.expr = expr
return self
-
class Comparator(interfaces.PropComparator):
"""A helper class that allows easy construction of custom :class:`~.orm.interfaces.PropComparator`
classes for usage with hybrids."""
+ property = None
def __init__(self, expression):
self.expression = expression