]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Document visitors module
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 12 Sep 2019 16:37:38 +0000 (12:37 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 12 Sep 2019 16:38:36 +0000 (12:38 -0400)
As we are going to be adding a lot of new features to the visitors
module which may impact end-user custom constructs, start documenting
the package for now.

Change-Id: Ibae471c2cb861f6280f601b7b2382d61968825cd
(cherry picked from commit ed02d924dd0bd1bce56b40bea74e1d382c352833)

doc/build/core/expression_api.rst
doc/build/core/visitors.rst [new file with mode: 0644]
lib/sqlalchemy/sql/visitors.py

index b32fa0e2390c502bfc6b4fc1df7dd68e85929382..c080b3a6335bbcb01c79142b09d0d529620e01fe 100644 (file)
@@ -18,3 +18,4 @@ see :ref:`sqlexpression_toplevel`.
     functions
     compiler
     serializer
+    visitors
diff --git a/doc/build/core/visitors.rst b/doc/build/core/visitors.rst
new file mode 100644 (file)
index 0000000..02f6e24
--- /dev/null
@@ -0,0 +1,25 @@
+Visitor and Traversal Utilities
+================================
+
+The :mod:`sqlalchemy.sql.visitors` module consists of classes and functions
+that serve the purpose of generically **traversing** a Core SQL expression
+structure.   This is not unlike the Python ``ast`` module in that is presents
+a system by which a program can operate upon each component of a SQL
+expression.   Common purposes this serves are locating various kinds of
+elements such as :class:`.Table` or :class:`.BindParameter` objects,
+as well as altering the state of the structure such as replacing certain FROM
+clauses with others.
+
+.. note:: the :mod:`sqlalchemy.sql.visitors` module is an internal API and
+   is not fully public.    It is subject to change and may additionally not
+   function as expected for use patterns that aren't considered within
+   SQLAlchemy's own internals.
+
+The :mod:`sqlalchemy.sql.visitors` module is part of the **internals** of
+SQLAlchemy and it is not usually used by calling application code.  It is
+however used in certain edge cases such as when constructing caching routines
+as well as when building out custom SQL expressions using the
+:ref:`Custom SQL Constructs and Compilation Extension <sqlalchemy.ext.compiler_toplevel>`.
+
+.. automodule:: sqlalchemy.sql.visitors
+   :members:
index 64dc0908b7d0760ee50d95249c4a4dd9f2b9a8a8..150d95538f02fc9009ef9bd4f20be093818abadb 100644 (file)
@@ -13,14 +13,13 @@ they apply functionality.  The most common use of this pattern
 is statement compilation, where individual expression classes match
 up to rendering methods that produce a string result.   Beyond this,
 the visitor system is also used to inspect expressions for various
-information and patterns, as well as for usage in
-some kinds of expression transformation.  Other kinds of transformation
-use a non-visitor traversal system.
+information and patterns, as well as for the purposes of applying
+transformations to expressions.
 
-For many examples of how the visit system is used, see the
-sqlalchemy.sql.util and the sqlalchemy.sql.compiler modules.
-For an introduction to clause adaption, see
-http://techspot.zzzeek.org/2008/01/23/expression-transformations/
+Examples of how the visit system is used can be seen in the source code
+of for example the ``sqlalchemy.sql.util`` and the ``sqlalchemy.sql.compiler``
+modules.  Some background on clause adaption is also at
+http://techspot.zzzeek.org/2008/01/23/expression-transformations/ .
 
 """
 
@@ -37,10 +36,10 @@ __all__ = ['VisitableType', 'Visitable', 'ClauseVisitor',
 
 
 class VisitableType(type):
-    """Metaclass which assigns a `_compiler_dispatch` method to classes
-    having a `__visit_name__` attribute.
+    """Metaclass which assigns a ``_compiler_dispatch`` method to classes
+    having a ``__visit_name__`` attribute.
 
-    The _compiler_dispatch attribute becomes an instance method which
+    The ``_compiler_dispatch`` attribute becomes an instance method which
     looks approximately like the following::
 
         def _compiler_dispatch (self, visitor, **kw):
@@ -49,7 +48,8 @@ class VisitableType(type):
             visit_attr = 'visit_%s' % self.__visit_name__
             return getattr(visitor, visit_attr)(self, **kw)
 
-    Classes having no __visit_name__ attribute will remain unaffected.
+    Classes having no ``__visit_name__`` attribute will remain unaffected.
+
     """
 
     def __init__(cls, clsname, bases, clsdict):
@@ -101,14 +101,20 @@ def _generate_dispatch(cls):
 
 class Visitable(util.with_metaclass(VisitableType, object)):
     """Base class for visitable objects, applies the
-    ``VisitableType`` metaclass.
+    :class:`.visitors.VisitableType` metaclass.
+
+    The :class:`.Visitable` class is essentially at the base of the
+    :class:`.ClauseElement` hierarchy.
 
     """
 
 
 class ClauseVisitor(object):
     """Base class for visitor objects which can traverse using
-    the traverse() function.
+    the :func:`.visitors.traverse` function.
+
+    Direct usage of the :func:`.visitors.traverse` function is usually
+    preferred.
 
     """
 
@@ -163,7 +169,11 @@ class ClauseVisitor(object):
 
 class CloningVisitor(ClauseVisitor):
     """Base class for visitor objects which can traverse using
-    the cloned_traverse() function.
+    the :func:`.visitors.cloned_traverse` function.
+
+    Direct usage of the :func:`.visitors.cloned_traverse` function is usually
+    preferred.
+
 
     """
 
@@ -183,7 +193,10 @@ class CloningVisitor(ClauseVisitor):
 
 class ReplacingCloningVisitor(CloningVisitor):
     """Base class for visitor objects which can traverse using
-    the replacement_traverse() function.
+    the :func:`.visitors.replacement_traverse` function.
+
+    Direct usage of the :func:`.visitors.replacement_traverse` function is
+    usually preferred.
 
     """
 
@@ -208,10 +221,24 @@ class ReplacingCloningVisitor(CloningVisitor):
 
 
 def iterate(obj, opts):
-    """traverse the given expression structure, returning an iterator.
+    r"""traverse the given expression structure, returning an iterator.
 
     traversal is configured to be breadth-first.
 
+    The central API feature used by the :func:`.visitors.iterate` and
+    :func:`.visitors.iterate_depthfirst` functions is the
+    :meth:`.ClauseElement.get_children` method of :class:`.ClauseElement`
+    objects.  This method should return all the :class:`.ClauseElement` objects
+    which are associated with a particular :class:`.ClauseElement` object.
+    For example, a :class:`.Case` structure will refer to a series of
+    :class:`.ColumnElement` objects within its "whens" and "else\_" member
+    variables.
+
+    :param obj: :class:`.ClauseElement` structure to be traversed
+
+    :param opts: dictionary of iteration options.   This dictionary is usually
+     empty in modern usage.
+
     """
     # fasttrack for atomic elements like columns
     children = obj.get_children(**opts)
@@ -233,6 +260,15 @@ def iterate_depthfirst(obj, opts):
 
     traversal is configured to be depth-first.
 
+    :param obj: :class:`.ClauseElement` structure to be traversed
+
+    :param opts: dictionary of iteration options.   This dictionary is usually
+     empty in modern usage.
+
+    .. seealso::
+
+        :func:`.visitors.iterate` - includes a general overview of iteration.
+
     """
     # fasttrack for atomic elements like columns
     children = obj.get_children(**opts)
@@ -253,6 +289,27 @@ def traverse_using(iterator, obj, visitors):
     """visit the given expression structure using the given iterator of
     objects.
 
+    :func:`.visitors.traverse_using` is usually called internally as the result
+    of the :func:`.visitors.traverse` or :func:`.visitors.traverse_depthfirst`
+    functions.
+
+    :param iterator: an iterable or sequence which will yield
+     :class:`.ClauseElement` structures; the iterator is assumed to be the
+     product of the :func:`.visitors.iterate` or
+     :func:`.visitors.iterate_depthfirst` functions.
+
+    :param obj: the :class:`.ClauseElement` that was used as the target of the
+     :func:`.iterate` or :func:`.iterate_depthfirst` function.
+
+    :param visitors: dictionary of visit functions.  See :func:`.traverse`
+     for details on this dictionary.
+
+    .. seealso::
+
+        :func:`.traverse`
+
+        :func:`.traverse_depthfirst`
+
     """
     for target in iterator:
         meth = visitors.get(target.__visit_name__, None)
@@ -263,7 +320,32 @@ def traverse_using(iterator, obj, visitors):
 
 def traverse(obj, opts, visitors):
     """traverse and visit the given expression structure using the default
-     iterator.
+    iterator.
+
+     e.g.::
+
+        from sqlalchemy.sql import visitors
+
+        stmt = select([some_table]).where(some_table.c.foo == 'bar')
+
+        def visit_bindparam(bind_param):
+            print("found bound value: %s" % bind_param.value)
+
+        visitors.traverse(stmt, {}, {"bindparam": visit_bindparam})
+
+    The iteration of objects uses the :func:`.visitors.iterate` function,
+    which does a breadth-first traversal using a stack.
+
+    :param obj: :class:`.ClauseElement` structure to be traversed
+
+    :param opts: dictionary of iteration options.   This dictionary is usually
+     empty in modern usage.
+
+    :param visitors: dictionary of visit functions.   The dictionary should
+     have strings as keys, each of which would correspond to the
+     ``__visit_name__`` of a particular kind of SQL expression object, and
+     callable functions  as values, each of which represents a visitor function
+     for that kind of object.
 
     """
     return traverse_using(iterate(obj, opts), obj, visitors)
@@ -273,13 +355,39 @@ def traverse_depthfirst(obj, opts, visitors):
     """traverse and visit the given expression structure using the
     depth-first iterator.
 
+    The iteration of objects uses the :func:`.visitors.iterate_depthfirst`
+    function, which does a depth-first traversal using a stack.
+
+    Usage is the same as that of :func:`.visitors.traverse` function.
+
+
     """
     return traverse_using(iterate_depthfirst(obj, opts), obj, visitors)
 
 
 def cloned_traverse(obj, opts, visitors):
-    """clone the given expression structure, allowing
-    modifications by visitors."""
+    """clone the given expression structure, allowing modifications by
+    visitors.
+
+    Traversal usage is the same as that of :func:`.visitors.traverse`.
+    The visitor functions present in the ``visitors`` dictionary may also
+    modify the internals of the given structure as the traversal proceeds.
+
+    The central API feature used by the :func:`.visitors.cloned_traverse`
+    and :func:`.visitors.replacement_traverse` functions, in addition to the
+    :meth:`.ClauseElement.get_children` function that is used to achieve
+    the iteration, is the :meth:`.ClauseElement._copy_internals` method.
+    For a :class:`.ClauseElement` structure to support cloning and replacement
+    traversals correctly, it needs to be able to pass a cloning function into
+    its internal members in order to make copies of them.
+
+    .. seealso::
+
+        :func:`.visitors.traverse`
+
+        :func:`.visitors.replacement_traverse`
+
+    """
 
     cloned = {}
     stop_on = set(opts.get('stop_on', []))
@@ -303,7 +411,27 @@ def cloned_traverse(obj, opts, visitors):
 
 def replacement_traverse(obj, opts, replace):
     """clone the given expression structure, allowing element
-    replacement by a given replacement function."""
+    replacement by a given replacement function.
+
+    This function is very similar to the :func:`.visitors.cloned_traverse`
+    function, except instead of being passed a dictionary of visitors, all
+    elements are unconditionally passed into the given replace function.
+    The replace function then has the option to return an entirely new object
+    which will replace the one given.  if it returns ``None``, then the object
+    is kept in place.
+
+    The difference in usage between :func:`.visitors.cloned_traverse` and
+    :func:`.visitors.replacement_traverse` is that in the former case, an
+    already-cloned object is passed to the visitor function, and the visitor
+    function can then manipulate the internal state of the object.
+    In the case of the latter, the visitor function should only return an
+    entirely different object, or do nothing.
+
+    The use case for :func:`.visitors.replacement_traverse` is that of
+    replacing a FROM clause inside of a SQL structure with a different one,
+    as is a common use case within the ORM.
+
+    """
 
     cloned = {}
     stop_on = set([id(x) for x in opts.get('stop_on', [])])