diff options
Diffstat (limited to 'lib/sqlalchemy/ext')
| -rw-r--r-- | lib/sqlalchemy/ext/hybrid.py | 149 |
1 files changed, 0 insertions, 149 deletions
diff --git a/lib/sqlalchemy/ext/hybrid.py b/lib/sqlalchemy/ext/hybrid.py index cfc6bd73b..de9ab52be 100644 --- a/lib/sqlalchemy/ext/hybrid.py +++ b/lib/sqlalchemy/ext/hybrid.py @@ -657,155 +657,6 @@ measurement, currencies and encrypted passwords. <https://techspot.zzzeek.org/2011/10/29/value-agnostic-types-part-ii/>`_ - on the techspot.zzzeek.org blog -.. _hybrid_transformers: - -Building Transformers ----------------------- - -A *transformer* is an object which can receive a :class:`_query.Query` -object and -return a new one. The :class:`_query.Query` object includes a method -:meth:`.with_transformation` that returns a new :class:`_query.Query` -transformed by -the given function. - -We can combine this with the :class:`.Comparator` class to produce one type -of recipe which can both set up the FROM clause of a query as well as assign -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:: - - from sqlalchemy.ext.hybrid import hybrid_property - - class Node(Base): - # ... - - @hybrid_property - def grandparent(self): - return self.parent.parent - -For the expression, things are not so clear. We'd need to construct a -:class:`_query.Query` where we :meth:`_query.Query.join` twice along -``Node.parent`` to get to the ``grandparent``. We can instead return a -transforming callable that we'll combine with the :class:`.Comparator` class to -receive any :class:`_query.Query` object, and return a new one that's joined to -the ``Node.parent`` attribute and filtered based on the given criterion:: - - from sqlalchemy.ext.hybrid import Comparator - - class GrandparentTransformer(Comparator): - def operate(self, op, other, **kwargs): - def transform(q): - cls = self.__clause_element__() - parent_alias = aliased(cls) - return q.join(parent_alias, cls.parent).filter( - op(parent_alias.parent, other, **kwargs) - ) - - return transform - - 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) - - @hybrid_property - def grandparent(self): - return self.parent.parent - - @grandparent.comparator - def grandparent(cls): - return GrandparentTransformer(cls) - -The ``GrandparentTransformer`` overrides the core :meth:`.Operators.operate` -method at the base of the :class:`.Comparator` hierarchy to return a query- -transforming callable, which then runs the given comparison operation in a -particular context. Such as, in the example above, the ``operate`` method is -called, given the :attr:`.Operators.eq` callable as well as the right side of -the comparison ``Node(id=5)``. A function ``transform`` is then returned which -will transform a :class:`_query.Query` first to join to ``Node.parent``, -then to -compare ``parent_alias`` using :attr:`.Operators.eq` against the left and right -sides, passing into :meth:`_query.Query.filter`: - -.. sourcecode:: pycon+sql - - >>> from sqlalchemy.orm import Session - >>> session = Session() - {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 - WHERE :param_1 = node_1.parent_id - {stop} - -We can modify the pattern to be more verbose but flexible by separating the -"join" step from the "filter" step. The tricky part here is ensuring that -successive instances of ``GrandparentTransformer`` use the same -:class:`.AliasedClass` object against ``Node``. Below we use a simple -memoizing approach that associates a ``GrandparentTransformer`` with each -class:: - - class Node(Base): - - # ... - - @grandparent.comparator - def grandparent(cls): - # memoize a GrandparentTransformer - # per class - if '_gp' not in cls.__dict__: - cls._gp = GrandparentTransformer(cls) - return cls._gp - - class GrandparentTransformer(Comparator): - - def __init__(self, cls): - self.parent_alias = aliased(cls) - - @property - def join(self): - def go(q): - return q.join(self.parent_alias, Node.parent) - return go - - def operate(self, op, other, **kwargs): - return op(self.parent_alias.parent, other, **kwargs) - -.. sourcecode:: pycon+sql - - {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 - 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, there's probably a whole lot of amazing -things it can be used for. """ # noqa |
