Updated SqlAlchemy
This commit is contained in:
@@ -1,4 +1,11 @@
|
||||
from sqlalchemy.sql.expression import (
|
||||
# sql/__init__.py
|
||||
# Copyright (C) 2005-2017 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from .expression import (
|
||||
Alias,
|
||||
ClauseElement,
|
||||
ColumnCollection,
|
||||
@@ -11,9 +18,12 @@ from sqlalchemy.sql.expression import (
|
||||
Select,
|
||||
Selectable,
|
||||
TableClause,
|
||||
TableSample,
|
||||
Update,
|
||||
alias,
|
||||
and_,
|
||||
any_,
|
||||
all_,
|
||||
asc,
|
||||
between,
|
||||
bindparam,
|
||||
@@ -28,12 +38,16 @@ from sqlalchemy.sql.expression import (
|
||||
except_all,
|
||||
exists,
|
||||
extract,
|
||||
false,
|
||||
False_,
|
||||
func,
|
||||
funcfilter,
|
||||
insert,
|
||||
intersect,
|
||||
intersect_all,
|
||||
join,
|
||||
label,
|
||||
lateral,
|
||||
literal,
|
||||
literal_column,
|
||||
modifier,
|
||||
@@ -42,17 +56,43 @@ from sqlalchemy.sql.expression import (
|
||||
or_,
|
||||
outerjoin,
|
||||
outparam,
|
||||
over,
|
||||
select,
|
||||
subquery,
|
||||
table,
|
||||
tablesample,
|
||||
text,
|
||||
true,
|
||||
True_,
|
||||
tuple_,
|
||||
type_coerce,
|
||||
union,
|
||||
union_all,
|
||||
update,
|
||||
)
|
||||
within_group
|
||||
)
|
||||
|
||||
from sqlalchemy.sql.visitors import ClauseVisitor
|
||||
from .visitors import ClauseVisitor
|
||||
|
||||
__tmp = locals().keys()
|
||||
__all__ = sorted([i for i in __tmp if not i.startswith('__')])
|
||||
|
||||
def __go(lcls):
|
||||
global __all__
|
||||
from .. import util as _sa_util
|
||||
|
||||
import inspect as _inspect
|
||||
|
||||
__all__ = sorted(name for name, obj in lcls.items()
|
||||
if not (name.startswith('_') or _inspect.ismodule(obj)))
|
||||
|
||||
from .annotation import _prepare_annotations, Annotated
|
||||
from .elements import AnnotatedColumnElement, ClauseList
|
||||
from .selectable import AnnotatedFromClause
|
||||
_prepare_annotations(ColumnElement, AnnotatedColumnElement)
|
||||
_prepare_annotations(FromClause, AnnotatedFromClause)
|
||||
_prepare_annotations(ClauseList, Annotated)
|
||||
|
||||
_sa_util.dependencies.resolve_all("sqlalchemy.sql")
|
||||
|
||||
from . import naming
|
||||
|
||||
__go(locals())
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,104 +1,813 @@
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.sql.expression import (
|
||||
ClauseList, Function, _literal_as_binds, text, _type_from_args
|
||||
)
|
||||
from sqlalchemy.sql import operators
|
||||
from sqlalchemy.sql.visitors import VisitableType
|
||||
# sql/functions.py
|
||||
# Copyright (C) 2005-2017 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""SQL function API, factories, and built-in functions.
|
||||
|
||||
"""
|
||||
from . import sqltypes, schema
|
||||
from .base import Executable, ColumnCollection
|
||||
from .elements import ClauseList, Cast, Extract, _literal_as_binds, \
|
||||
literal_column, _type_from_args, ColumnElement, _clone,\
|
||||
Over, BindParameter, FunctionFilter, Grouping, WithinGroup
|
||||
from .selectable import FromClause, Select, Alias
|
||||
from . import util as sqlutil
|
||||
from . import operators
|
||||
from .visitors import VisitableType
|
||||
from .. import util
|
||||
from . import annotation
|
||||
|
||||
_registry = util.defaultdict(dict)
|
||||
|
||||
|
||||
def register_function(identifier, fn, package="_default"):
|
||||
"""Associate a callable with a particular func. name.
|
||||
|
||||
This is normally called by _GenericMeta, but is also
|
||||
available by itself so that a non-Function construct
|
||||
can be associated with the :data:`.func` accessor (i.e.
|
||||
CAST, EXTRACT).
|
||||
|
||||
"""
|
||||
reg = _registry[package]
|
||||
reg[identifier] = fn
|
||||
|
||||
|
||||
class FunctionElement(Executable, ColumnElement, FromClause):
|
||||
"""Base for SQL function-oriented constructs.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:class:`.Function` - named SQL function.
|
||||
|
||||
:data:`.func` - namespace which produces registered or ad-hoc
|
||||
:class:`.Function` instances.
|
||||
|
||||
:class:`.GenericFunction` - allows creation of registered function
|
||||
types.
|
||||
|
||||
"""
|
||||
|
||||
packagenames = ()
|
||||
|
||||
def __init__(self, *clauses, **kwargs):
|
||||
"""Construct a :class:`.FunctionElement`.
|
||||
"""
|
||||
args = [_literal_as_binds(c, self.name) for c in clauses]
|
||||
self.clause_expr = ClauseList(
|
||||
operator=operators.comma_op,
|
||||
group_contents=True, *args).\
|
||||
self_group()
|
||||
|
||||
def _execute_on_connection(self, connection, multiparams, params):
|
||||
return connection._execute_function(self, multiparams, params)
|
||||
|
||||
@property
|
||||
def columns(self):
|
||||
"""The set of columns exported by this :class:`.FunctionElement`.
|
||||
|
||||
Function objects currently have no result column names built in;
|
||||
this method returns a single-element column collection with
|
||||
an anonymously named column.
|
||||
|
||||
An interim approach to providing named columns for a function
|
||||
as a FROM clause is to build a :func:`.select` with the
|
||||
desired columns::
|
||||
|
||||
from sqlalchemy.sql import column
|
||||
|
||||
stmt = select([column('x'), column('y')]).\
|
||||
select_from(func.myfunction())
|
||||
|
||||
|
||||
"""
|
||||
return ColumnCollection(self.label(None))
|
||||
|
||||
@util.memoized_property
|
||||
def clauses(self):
|
||||
"""Return the underlying :class:`.ClauseList` which contains
|
||||
the arguments for this :class:`.FunctionElement`.
|
||||
|
||||
"""
|
||||
return self.clause_expr.element
|
||||
|
||||
def over(self, partition_by=None, order_by=None, rows=None, range_=None):
|
||||
"""Produce an OVER clause against this function.
|
||||
|
||||
Used against aggregate or so-called "window" functions,
|
||||
for database backends that support window functions.
|
||||
|
||||
The expression::
|
||||
|
||||
func.row_number().over(order_by='x')
|
||||
|
||||
is shorthand for::
|
||||
|
||||
from sqlalchemy import over
|
||||
over(func.row_number(), order_by='x')
|
||||
|
||||
See :func:`~.expression.over` for a full description.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
"""
|
||||
return Over(
|
||||
self,
|
||||
partition_by=partition_by,
|
||||
order_by=order_by,
|
||||
rows=rows,
|
||||
range_=range_
|
||||
)
|
||||
|
||||
def within_group(self, *order_by):
|
||||
"""Produce a WITHIN GROUP (ORDER BY expr) clause against this function.
|
||||
|
||||
Used against so-called "ordered set aggregate" and "hypothetical
|
||||
set aggregate" functions, including :class:`.percentile_cont`,
|
||||
:class:`.rank`, :class:`.dense_rank`, etc.
|
||||
|
||||
See :func:`~.expression.within_group` for a full description.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
|
||||
"""
|
||||
return WithinGroup(self, *order_by)
|
||||
|
||||
def filter(self, *criterion):
|
||||
"""Produce a FILTER clause against this function.
|
||||
|
||||
Used against aggregate and window functions,
|
||||
for database backends that support the "FILTER" clause.
|
||||
|
||||
The expression::
|
||||
|
||||
func.count(1).filter(True)
|
||||
|
||||
is shorthand for::
|
||||
|
||||
from sqlalchemy import funcfilter
|
||||
funcfilter(func.count(1), True)
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. seealso::
|
||||
|
||||
:class:`.FunctionFilter`
|
||||
|
||||
:func:`.funcfilter`
|
||||
|
||||
|
||||
"""
|
||||
if not criterion:
|
||||
return self
|
||||
return FunctionFilter(self, *criterion)
|
||||
|
||||
@property
|
||||
def _from_objects(self):
|
||||
return self.clauses._from_objects
|
||||
|
||||
def get_children(self, **kwargs):
|
||||
return self.clause_expr,
|
||||
|
||||
def _copy_internals(self, clone=_clone, **kw):
|
||||
self.clause_expr = clone(self.clause_expr, **kw)
|
||||
self._reset_exported()
|
||||
FunctionElement.clauses._reset(self)
|
||||
|
||||
def within_group_type(self, within_group):
|
||||
"""For types that define their return type as based on the criteria
|
||||
within a WITHIN GROUP (ORDER BY) expression, called by the
|
||||
:class:`.WithinGroup` construct.
|
||||
|
||||
Returns None by default, in which case the function's normal ``.type``
|
||||
is used.
|
||||
|
||||
"""
|
||||
|
||||
return None
|
||||
|
||||
def alias(self, name=None, flat=False):
|
||||
r"""Produce a :class:`.Alias` construct against this
|
||||
:class:`.FunctionElement`.
|
||||
|
||||
This construct wraps the function in a named alias which
|
||||
is suitable for the FROM clause, in the style accepted for example
|
||||
by PostgreSQL.
|
||||
|
||||
e.g.::
|
||||
|
||||
from sqlalchemy.sql import column
|
||||
|
||||
stmt = select([column('data_view')]).\
|
||||
select_from(SomeTable).\
|
||||
select_from(func.unnest(SomeTable.data).alias('data_view')
|
||||
)
|
||||
|
||||
Would produce:
|
||||
|
||||
.. sourcecode:: sql
|
||||
|
||||
SELECT data_view
|
||||
FROM sometable, unnest(sometable.data) AS data_view
|
||||
|
||||
.. versionadded:: 0.9.8 The :meth:`.FunctionElement.alias` method
|
||||
is now supported. Previously, this method's behavior was
|
||||
undefined and did not behave consistently across versions.
|
||||
|
||||
"""
|
||||
|
||||
return Alias(self, name)
|
||||
|
||||
def select(self):
|
||||
"""Produce a :func:`~.expression.select` construct
|
||||
against this :class:`.FunctionElement`.
|
||||
|
||||
This is shorthand for::
|
||||
|
||||
s = select([function_element])
|
||||
|
||||
"""
|
||||
s = Select([self])
|
||||
if self._execution_options:
|
||||
s = s.execution_options(**self._execution_options)
|
||||
return s
|
||||
|
||||
def scalar(self):
|
||||
"""Execute this :class:`.FunctionElement` against an embedded
|
||||
'bind' and return a scalar value.
|
||||
|
||||
This first calls :meth:`~.FunctionElement.select` to
|
||||
produce a SELECT construct.
|
||||
|
||||
Note that :class:`.FunctionElement` can be passed to
|
||||
the :meth:`.Connectable.scalar` method of :class:`.Connection`
|
||||
or :class:`.Engine`.
|
||||
|
||||
"""
|
||||
return self.select().execute().scalar()
|
||||
|
||||
def execute(self):
|
||||
"""Execute this :class:`.FunctionElement` against an embedded
|
||||
'bind'.
|
||||
|
||||
This first calls :meth:`~.FunctionElement.select` to
|
||||
produce a SELECT construct.
|
||||
|
||||
Note that :class:`.FunctionElement` can be passed to
|
||||
the :meth:`.Connectable.execute` method of :class:`.Connection`
|
||||
or :class:`.Engine`.
|
||||
|
||||
"""
|
||||
return self.select().execute()
|
||||
|
||||
def _bind_param(self, operator, obj, type_=None):
|
||||
return BindParameter(None, obj, _compared_to_operator=operator,
|
||||
_compared_to_type=self.type, unique=True,
|
||||
type_=type_)
|
||||
|
||||
def self_group(self, against=None):
|
||||
# for the moment, we are parenthesizing all array-returning
|
||||
# expressions against getitem. This may need to be made
|
||||
# more portable if in the future we support other DBs
|
||||
# besides postgresql.
|
||||
if against is operators.getitem and \
|
||||
isinstance(self.type, sqltypes.ARRAY):
|
||||
return Grouping(self)
|
||||
else:
|
||||
return super(FunctionElement, self).self_group(against=against)
|
||||
|
||||
|
||||
class _FunctionGenerator(object):
|
||||
"""Generate :class:`.Function` objects based on getattr calls."""
|
||||
|
||||
def __init__(self, **opts):
|
||||
self.__names = []
|
||||
self.opts = opts
|
||||
|
||||
def __getattr__(self, name):
|
||||
# passthru __ attributes; fixes pydoc
|
||||
if name.startswith('__'):
|
||||
try:
|
||||
return self.__dict__[name]
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
|
||||
elif name.endswith('_'):
|
||||
name = name[0:-1]
|
||||
f = _FunctionGenerator(**self.opts)
|
||||
f.__names = list(self.__names) + [name]
|
||||
return f
|
||||
|
||||
def __call__(self, *c, **kwargs):
|
||||
o = self.opts.copy()
|
||||
o.update(kwargs)
|
||||
|
||||
tokens = len(self.__names)
|
||||
|
||||
if tokens == 2:
|
||||
package, fname = self.__names
|
||||
elif tokens == 1:
|
||||
package, fname = "_default", self.__names[0]
|
||||
else:
|
||||
package = None
|
||||
|
||||
if package is not None:
|
||||
func = _registry[package].get(fname)
|
||||
if func is not None:
|
||||
return func(*c, **o)
|
||||
|
||||
return Function(self.__names[-1],
|
||||
packagenames=self.__names[0:-1], *c, **o)
|
||||
|
||||
|
||||
func = _FunctionGenerator()
|
||||
"""Generate SQL function expressions.
|
||||
|
||||
:data:`.func` is a special object instance which generates SQL
|
||||
functions based on name-based attributes, e.g.::
|
||||
|
||||
>>> print(func.count(1))
|
||||
count(:param_1)
|
||||
|
||||
The element is a column-oriented SQL element like any other, and is
|
||||
used in that way::
|
||||
|
||||
>>> print(select([func.count(table.c.id)]))
|
||||
SELECT count(sometable.id) FROM sometable
|
||||
|
||||
Any name can be given to :data:`.func`. If the function name is unknown to
|
||||
SQLAlchemy, it will be rendered exactly as is. For common SQL functions
|
||||
which SQLAlchemy is aware of, the name may be interpreted as a *generic
|
||||
function* which will be compiled appropriately to the target database::
|
||||
|
||||
>>> print(func.current_timestamp())
|
||||
CURRENT_TIMESTAMP
|
||||
|
||||
To call functions which are present in dot-separated packages,
|
||||
specify them in the same manner::
|
||||
|
||||
>>> print(func.stats.yield_curve(5, 10))
|
||||
stats.yield_curve(:yield_curve_1, :yield_curve_2)
|
||||
|
||||
SQLAlchemy can be made aware of the return type of functions to enable
|
||||
type-specific lexical and result-based behavior. For example, to ensure
|
||||
that a string-based function returns a Unicode value and is similarly
|
||||
treated as a string in expressions, specify
|
||||
:class:`~sqlalchemy.types.Unicode` as the type:
|
||||
|
||||
>>> print(func.my_string(u'hi', type_=Unicode) + ' ' +
|
||||
... func.my_string(u'there', type_=Unicode))
|
||||
my_string(:my_string_1) || :my_string_2 || my_string(:my_string_3)
|
||||
|
||||
The object returned by a :data:`.func` call is usually an instance of
|
||||
:class:`.Function`.
|
||||
This object meets the "column" interface, including comparison and labeling
|
||||
functions. The object can also be passed the :meth:`~.Connectable.execute`
|
||||
method of a :class:`.Connection` or :class:`.Engine`, where it will be
|
||||
wrapped inside of a SELECT statement first::
|
||||
|
||||
print(connection.execute(func.current_timestamp()).scalar())
|
||||
|
||||
In a few exception cases, the :data:`.func` accessor
|
||||
will redirect a name to a built-in expression such as :func:`.cast`
|
||||
or :func:`.extract`, as these names have well-known meaning
|
||||
but are not exactly the same as "functions" from a SQLAlchemy
|
||||
perspective.
|
||||
|
||||
.. versionadded:: 0.8 :data:`.func` can return non-function expression
|
||||
constructs for common quasi-functional names like :func:`.cast`
|
||||
and :func:`.extract`.
|
||||
|
||||
Functions which are interpreted as "generic" functions know how to
|
||||
calculate their return type automatically. For a listing of known generic
|
||||
functions, see :ref:`generic_functions`.
|
||||
|
||||
.. note::
|
||||
|
||||
The :data:`.func` construct has only limited support for calling
|
||||
standalone "stored procedures", especially those with special
|
||||
parameterization concerns.
|
||||
|
||||
See the section :ref:`stored_procedures` for details on how to use
|
||||
the DBAPI-level ``callproc()`` method for fully traditional stored
|
||||
procedures.
|
||||
|
||||
"""
|
||||
|
||||
modifier = _FunctionGenerator(group=False)
|
||||
|
||||
|
||||
class Function(FunctionElement):
|
||||
"""Describe a named SQL function.
|
||||
|
||||
See the superclass :class:`.FunctionElement` for a description
|
||||
of public methods.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:data:`.func` - namespace which produces registered or ad-hoc
|
||||
:class:`.Function` instances.
|
||||
|
||||
:class:`.GenericFunction` - allows creation of registered function
|
||||
types.
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'function'
|
||||
|
||||
def __init__(self, name, *clauses, **kw):
|
||||
"""Construct a :class:`.Function`.
|
||||
|
||||
The :data:`.func` construct is normally used to construct
|
||||
new :class:`.Function` instances.
|
||||
|
||||
"""
|
||||
self.packagenames = kw.pop('packagenames', None) or []
|
||||
self.name = name
|
||||
self._bind = kw.get('bind', None)
|
||||
self.type = sqltypes.to_instance(kw.get('type_', None))
|
||||
|
||||
FunctionElement.__init__(self, *clauses, **kw)
|
||||
|
||||
def _bind_param(self, operator, obj, type_=None):
|
||||
return BindParameter(self.name, obj,
|
||||
_compared_to_operator=operator,
|
||||
_compared_to_type=self.type,
|
||||
type_=type_,
|
||||
unique=True)
|
||||
|
||||
|
||||
class _GenericMeta(VisitableType):
|
||||
def __call__(self, *args, **kwargs):
|
||||
args = [_literal_as_binds(c) for c in args]
|
||||
return type.__call__(self, *args, **kwargs)
|
||||
def __init__(cls, clsname, bases, clsdict):
|
||||
if annotation.Annotated not in cls.__mro__:
|
||||
cls.name = name = clsdict.get('name', clsname)
|
||||
cls.identifier = identifier = clsdict.get('identifier', name)
|
||||
package = clsdict.pop('package', '_default')
|
||||
# legacy
|
||||
if '__return_type__' in clsdict:
|
||||
cls.type = clsdict['__return_type__']
|
||||
register_function(identifier, cls, package)
|
||||
super(_GenericMeta, cls).__init__(clsname, bases, clsdict)
|
||||
|
||||
class GenericFunction(Function):
|
||||
__metaclass__ = _GenericMeta
|
||||
|
||||
def __init__(self, type_=None, args=(), **kwargs):
|
||||
class GenericFunction(util.with_metaclass(_GenericMeta, Function)):
|
||||
"""Define a 'generic' function.
|
||||
|
||||
A generic function is a pre-established :class:`.Function`
|
||||
class that is instantiated automatically when called
|
||||
by name from the :data:`.func` attribute. Note that
|
||||
calling any name from :data:`.func` has the effect that
|
||||
a new :class:`.Function` instance is created automatically,
|
||||
given that name. The primary use case for defining
|
||||
a :class:`.GenericFunction` class is so that a function
|
||||
of a particular name may be given a fixed return type.
|
||||
It can also include custom argument parsing schemes as well
|
||||
as additional methods.
|
||||
|
||||
Subclasses of :class:`.GenericFunction` are automatically
|
||||
registered under the name of the class. For
|
||||
example, a user-defined function ``as_utc()`` would
|
||||
be available immediately::
|
||||
|
||||
from sqlalchemy.sql.functions import GenericFunction
|
||||
from sqlalchemy.types import DateTime
|
||||
|
||||
class as_utc(GenericFunction):
|
||||
type = DateTime
|
||||
|
||||
print select([func.as_utc()])
|
||||
|
||||
User-defined generic functions can be organized into
|
||||
packages by specifying the "package" attribute when defining
|
||||
:class:`.GenericFunction`. Third party libraries
|
||||
containing many functions may want to use this in order
|
||||
to avoid name conflicts with other systems. For example,
|
||||
if our ``as_utc()`` function were part of a package
|
||||
"time"::
|
||||
|
||||
class as_utc(GenericFunction):
|
||||
type = DateTime
|
||||
package = "time"
|
||||
|
||||
The above function would be available from :data:`.func`
|
||||
using the package name ``time``::
|
||||
|
||||
print select([func.time.as_utc()])
|
||||
|
||||
A final option is to allow the function to be accessed
|
||||
from one name in :data:`.func` but to render as a different name.
|
||||
The ``identifier`` attribute will override the name used to
|
||||
access the function as loaded from :data:`.func`, but will retain
|
||||
the usage of ``name`` as the rendered name::
|
||||
|
||||
class GeoBuffer(GenericFunction):
|
||||
type = Geometry
|
||||
package = "geo"
|
||||
name = "ST_Buffer"
|
||||
identifier = "buffer"
|
||||
|
||||
The above function will render as follows::
|
||||
|
||||
>>> print func.geo.buffer()
|
||||
ST_Buffer()
|
||||
|
||||
.. versionadded:: 0.8 :class:`.GenericFunction` now supports
|
||||
automatic registration of new functions as well as package
|
||||
and custom naming support.
|
||||
|
||||
.. versionchanged:: 0.8 The attribute name ``type`` is used
|
||||
to specify the function's return type at the class level.
|
||||
Previously, the name ``__return_type__`` was used. This
|
||||
name is still recognized for backwards-compatibility.
|
||||
|
||||
"""
|
||||
|
||||
coerce_arguments = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
parsed_args = kwargs.pop('_parsed_args', None)
|
||||
if parsed_args is None:
|
||||
parsed_args = [_literal_as_binds(c, self.name) for c in args]
|
||||
self.packagenames = []
|
||||
self.name = self.__class__.__name__
|
||||
self._bind = kwargs.get('bind', None)
|
||||
self.clause_expr = ClauseList(
|
||||
operator=operators.comma_op,
|
||||
group_contents=True, *args).self_group()
|
||||
operator=operators.comma_op,
|
||||
group_contents=True, *parsed_args).self_group()
|
||||
self.type = sqltypes.to_instance(
|
||||
type_ or getattr(self, '__return_type__', None))
|
||||
kwargs.pop("type_", None) or getattr(self, 'type', None))
|
||||
|
||||
register_function("cast", Cast)
|
||||
register_function("extract", Extract)
|
||||
|
||||
|
||||
class next_value(GenericFunction):
|
||||
"""Represent the 'next value', given a :class:`.Sequence`
|
||||
as its single argument.
|
||||
|
||||
Compiles into the appropriate function on each backend,
|
||||
or will raise NotImplementedError if used on a backend
|
||||
that does not provide support for sequences.
|
||||
|
||||
"""
|
||||
type = sqltypes.Integer()
|
||||
name = "next_value"
|
||||
|
||||
def __init__(self, seq, **kw):
|
||||
assert isinstance(seq, schema.Sequence), \
|
||||
"next_value() accepts a Sequence object as input."
|
||||
self._bind = kw.get('bind', None)
|
||||
self.sequence = seq
|
||||
|
||||
@property
|
||||
def _from_objects(self):
|
||||
return []
|
||||
|
||||
|
||||
class AnsiFunction(GenericFunction):
|
||||
def __init__(self, **kwargs):
|
||||
GenericFunction.__init__(self, **kwargs)
|
||||
|
||||
|
||||
class ReturnTypeFromArgs(GenericFunction):
|
||||
"""Define a function whose return type is the same as its arguments."""
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
args = [_literal_as_binds(c, self.name) for c in args]
|
||||
kwargs.setdefault('type_', _type_from_args(args))
|
||||
GenericFunction.__init__(self, args=args, **kwargs)
|
||||
kwargs['_parsed_args'] = args
|
||||
super(ReturnTypeFromArgs, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class coalesce(ReturnTypeFromArgs):
|
||||
pass
|
||||
|
||||
|
||||
class max(ReturnTypeFromArgs):
|
||||
pass
|
||||
|
||||
|
||||
class min(ReturnTypeFromArgs):
|
||||
pass
|
||||
|
||||
|
||||
class sum(ReturnTypeFromArgs):
|
||||
pass
|
||||
|
||||
|
||||
class now(GenericFunction):
|
||||
__return_type__ = sqltypes.DateTime
|
||||
type = sqltypes.DateTime
|
||||
|
||||
|
||||
class concat(GenericFunction):
|
||||
__return_type__ = sqltypes.String
|
||||
def __init__(self, *args, **kwargs):
|
||||
GenericFunction.__init__(self, args=args, **kwargs)
|
||||
type = sqltypes.String
|
||||
|
||||
|
||||
class char_length(GenericFunction):
|
||||
__return_type__ = sqltypes.Integer
|
||||
type = sqltypes.Integer
|
||||
|
||||
def __init__(self, arg, **kwargs):
|
||||
GenericFunction.__init__(self, args=[arg], **kwargs)
|
||||
GenericFunction.__init__(self, arg, **kwargs)
|
||||
|
||||
|
||||
class random(GenericFunction):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('type_', None)
|
||||
GenericFunction.__init__(self, args=args, **kwargs)
|
||||
pass
|
||||
|
||||
|
||||
class count(GenericFunction):
|
||||
"""The ANSI COUNT aggregate function. With no arguments, emits COUNT \*."""
|
||||
r"""The ANSI COUNT aggregate function. With no arguments,
|
||||
emits COUNT \*.
|
||||
|
||||
__return_type__ = sqltypes.Integer
|
||||
"""
|
||||
type = sqltypes.Integer
|
||||
|
||||
def __init__(self, expression=None, **kwargs):
|
||||
if expression is None:
|
||||
expression = text('*')
|
||||
GenericFunction.__init__(self, args=(expression,), **kwargs)
|
||||
expression = literal_column('*')
|
||||
super(count, self).__init__(expression, **kwargs)
|
||||
|
||||
|
||||
class current_date(AnsiFunction):
|
||||
__return_type__ = sqltypes.Date
|
||||
type = sqltypes.Date
|
||||
|
||||
|
||||
class current_time(AnsiFunction):
|
||||
__return_type__ = sqltypes.Time
|
||||
type = sqltypes.Time
|
||||
|
||||
|
||||
class current_timestamp(AnsiFunction):
|
||||
__return_type__ = sqltypes.DateTime
|
||||
type = sqltypes.DateTime
|
||||
|
||||
|
||||
class current_user(AnsiFunction):
|
||||
__return_type__ = sqltypes.String
|
||||
type = sqltypes.String
|
||||
|
||||
|
||||
class localtime(AnsiFunction):
|
||||
__return_type__ = sqltypes.DateTime
|
||||
type = sqltypes.DateTime
|
||||
|
||||
|
||||
class localtimestamp(AnsiFunction):
|
||||
__return_type__ = sqltypes.DateTime
|
||||
type = sqltypes.DateTime
|
||||
|
||||
|
||||
class session_user(AnsiFunction):
|
||||
__return_type__ = sqltypes.String
|
||||
type = sqltypes.String
|
||||
|
||||
|
||||
class sysdate(AnsiFunction):
|
||||
__return_type__ = sqltypes.DateTime
|
||||
type = sqltypes.DateTime
|
||||
|
||||
|
||||
class user(AnsiFunction):
|
||||
__return_type__ = sqltypes.String
|
||||
type = sqltypes.String
|
||||
|
||||
|
||||
class array_agg(GenericFunction):
|
||||
"""support for the ARRAY_AGG function.
|
||||
|
||||
The ``func.array_agg(expr)`` construct returns an expression of
|
||||
type :class:`.types.ARRAY`.
|
||||
|
||||
e.g.::
|
||||
|
||||
stmt = select([func.array_agg(table.c.values)[2:5]])
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
.. seealso::
|
||||
|
||||
:func:`.postgresql.array_agg` - PostgreSQL-specific version that
|
||||
returns :class:`.postgresql.ARRAY`, which has PG-specific operators added.
|
||||
|
||||
"""
|
||||
|
||||
type = sqltypes.ARRAY
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
args = [_literal_as_binds(c) for c in args]
|
||||
kwargs.setdefault('type_', self.type(_type_from_args(args)))
|
||||
kwargs['_parsed_args'] = args
|
||||
super(array_agg, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class OrderedSetAgg(GenericFunction):
|
||||
"""Define a function where the return type is based on the sort
|
||||
expression type as defined by the expression passed to the
|
||||
:meth:`.FunctionElement.within_group` method."""
|
||||
|
||||
array_for_multi_clause = False
|
||||
|
||||
def within_group_type(self, within_group):
|
||||
func_clauses = self.clause_expr.element
|
||||
order_by = sqlutil.unwrap_order_by(within_group.order_by)
|
||||
if self.array_for_multi_clause and len(func_clauses.clauses) > 1:
|
||||
return sqltypes.ARRAY(order_by[0].type)
|
||||
else:
|
||||
return order_by[0].type
|
||||
|
||||
|
||||
class mode(OrderedSetAgg):
|
||||
"""implement the ``mode`` ordered-set aggregate function.
|
||||
|
||||
This function must be used with the :meth:`.FunctionElement.within_group`
|
||||
modifier to supply a sort expression to operate upon.
|
||||
|
||||
The return type of this function is the same as the sort expression.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class percentile_cont(OrderedSetAgg):
|
||||
"""implement the ``percentile_cont`` ordered-set aggregate function.
|
||||
|
||||
This function must be used with the :meth:`.FunctionElement.within_group`
|
||||
modifier to supply a sort expression to operate upon.
|
||||
|
||||
The return type of this function is the same as the sort expression,
|
||||
or if the arguments are an array, an :class:`.types.ARRAY` of the sort
|
||||
expression's type.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
"""
|
||||
|
||||
array_for_multi_clause = True
|
||||
|
||||
|
||||
class percentile_disc(OrderedSetAgg):
|
||||
"""implement the ``percentile_disc`` ordered-set aggregate function.
|
||||
|
||||
This function must be used with the :meth:`.FunctionElement.within_group`
|
||||
modifier to supply a sort expression to operate upon.
|
||||
|
||||
The return type of this function is the same as the sort expression,
|
||||
or if the arguments are an array, an :class:`.types.ARRAY` of the sort
|
||||
expression's type.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
"""
|
||||
|
||||
array_for_multi_clause = True
|
||||
|
||||
|
||||
class rank(GenericFunction):
|
||||
"""Implement the ``rank`` hypothetical-set aggregate function.
|
||||
|
||||
This function must be used with the :meth:`.FunctionElement.within_group`
|
||||
modifier to supply a sort expression to operate upon.
|
||||
|
||||
The return type of this function is :class:`.Integer`.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
"""
|
||||
type = sqltypes.Integer()
|
||||
|
||||
|
||||
class dense_rank(GenericFunction):
|
||||
"""Implement the ``dense_rank`` hypothetical-set aggregate function.
|
||||
|
||||
This function must be used with the :meth:`.FunctionElement.within_group`
|
||||
modifier to supply a sort expression to operate upon.
|
||||
|
||||
The return type of this function is :class:`.Integer`.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
"""
|
||||
type = sqltypes.Integer()
|
||||
|
||||
|
||||
class percent_rank(GenericFunction):
|
||||
"""Implement the ``percent_rank`` hypothetical-set aggregate function.
|
||||
|
||||
This function must be used with the :meth:`.FunctionElement.within_group`
|
||||
modifier to supply a sort expression to operate upon.
|
||||
|
||||
The return type of this function is :class:`.Numeric`.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
"""
|
||||
type = sqltypes.Numeric()
|
||||
|
||||
|
||||
class cume_dist(GenericFunction):
|
||||
"""Implement the ``cume_dist`` hypothetical-set aggregate function.
|
||||
|
||||
This function must be used with the :meth:`.FunctionElement.within_group`
|
||||
modifier to supply a sort expression to operate upon.
|
||||
|
||||
The return type of this function is :class:`.Numeric`.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
"""
|
||||
type = sqltypes.Numeric()
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,90 +1,137 @@
|
||||
# sql/visitors.py
|
||||
# Copyright (C) 2005-2017 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Visitor/traversal interface and library functions.
|
||||
|
||||
SQLAlchemy schema and expression constructs rely on a Python-centric
|
||||
version of the classic "visitor" pattern as the primary way in which
|
||||
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
|
||||
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.
|
||||
|
||||
For many examples of how the visit system is used, see the
|
||||
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/?p=19 .
|
||||
http://techspot.zzzeek.org/2008/01/23/expression-transformations/
|
||||
|
||||
"""
|
||||
|
||||
from collections import deque
|
||||
import re
|
||||
from sqlalchemy import util
|
||||
from .. import util
|
||||
import operator
|
||||
from .. import exc
|
||||
|
||||
__all__ = ['VisitableType', 'Visitable', 'ClauseVisitor',
|
||||
'CloningVisitor', 'ReplacingCloningVisitor', 'iterate',
|
||||
'iterate_depthfirst', 'traverse_using', 'traverse',
|
||||
'traverse_depthfirst',
|
||||
'cloned_traverse', 'replacement_traverse']
|
||||
|
||||
|
||||
__all__ = ['VisitableType', 'Visitable', 'ClauseVisitor',
|
||||
'CloningVisitor', 'ReplacingCloningVisitor', 'iterate',
|
||||
'iterate_depthfirst', 'traverse_using', 'traverse',
|
||||
'cloned_traverse', 'replacement_traverse']
|
||||
|
||||
class VisitableType(type):
|
||||
"""Metaclass which checks for a `__visit_name__` attribute and
|
||||
applies `_compiler_dispatch` method to classes.
|
||||
|
||||
"""Metaclass which assigns a `_compiler_dispatch` method to classes
|
||||
having a `__visit_name__` attribute.
|
||||
|
||||
The _compiler_dispatch attribute becomes an instance method which
|
||||
looks approximately like the following::
|
||||
|
||||
def _compiler_dispatch (self, visitor, **kw):
|
||||
'''Look for an attribute named "visit_" + self.__visit_name__
|
||||
on the visitor, and call it with the same kw params.'''
|
||||
visit_attr = 'visit_%s' % self.__visit_name__
|
||||
return getattr(visitor, visit_attr)(self, **kw)
|
||||
|
||||
Classes having no __visit_name__ attribute will remain unaffected.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(cls, clsname, bases, clsdict):
|
||||
if cls.__name__ == 'Visitable' or not hasattr(cls, '__visit_name__'):
|
||||
super(VisitableType, cls).__init__(clsname, bases, clsdict)
|
||||
return
|
||||
|
||||
# set up an optimized visit dispatch function
|
||||
# for use by the compiler
|
||||
visit_name = cls.__visit_name__
|
||||
if isinstance(visit_name, str):
|
||||
getter = operator.attrgetter("visit_%s" % visit_name)
|
||||
def _compiler_dispatch(self, visitor, **kw):
|
||||
return getter(visitor)(self, **kw)
|
||||
else:
|
||||
def _compiler_dispatch(self, visitor, **kw):
|
||||
return getattr(visitor, 'visit_%s' % self.__visit_name__)(self, **kw)
|
||||
|
||||
cls._compiler_dispatch = _compiler_dispatch
|
||||
|
||||
if clsname != 'Visitable' and \
|
||||
hasattr(cls, '__visit_name__'):
|
||||
_generate_dispatch(cls)
|
||||
|
||||
super(VisitableType, cls).__init__(clsname, bases, clsdict)
|
||||
|
||||
class Visitable(object):
|
||||
|
||||
def _generate_dispatch(cls):
|
||||
"""Return an optimized visit dispatch function for the cls
|
||||
for use by the compiler.
|
||||
"""
|
||||
if '__visit_name__' in cls.__dict__:
|
||||
visit_name = cls.__visit_name__
|
||||
if isinstance(visit_name, str):
|
||||
# There is an optimization opportunity here because the
|
||||
# the string name of the class's __visit_name__ is known at
|
||||
# this early stage (import time) so it can be pre-constructed.
|
||||
getter = operator.attrgetter("visit_%s" % visit_name)
|
||||
|
||||
def _compiler_dispatch(self, visitor, **kw):
|
||||
try:
|
||||
meth = getter(visitor)
|
||||
except AttributeError:
|
||||
raise exc.UnsupportedCompilationError(visitor, cls)
|
||||
else:
|
||||
return meth(self, **kw)
|
||||
else:
|
||||
# The optimization opportunity is lost for this case because the
|
||||
# __visit_name__ is not yet a string. As a result, the visit
|
||||
# string has to be recalculated with each compilation.
|
||||
def _compiler_dispatch(self, visitor, **kw):
|
||||
visit_attr = 'visit_%s' % self.__visit_name__
|
||||
try:
|
||||
meth = getattr(visitor, visit_attr)
|
||||
except AttributeError:
|
||||
raise exc.UnsupportedCompilationError(visitor, cls)
|
||||
else:
|
||||
return meth(self, **kw)
|
||||
|
||||
_compiler_dispatch.__doc__ = \
|
||||
"""Look for an attribute named "visit_" + self.__visit_name__
|
||||
on the visitor, and call it with the same kw params.
|
||||
"""
|
||||
cls._compiler_dispatch = _compiler_dispatch
|
||||
|
||||
|
||||
class Visitable(util.with_metaclass(VisitableType, object)):
|
||||
"""Base class for visitable objects, applies the
|
||||
``VisitableType`` metaclass.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
__metaclass__ = VisitableType
|
||||
|
||||
class ClauseVisitor(object):
|
||||
"""Base class for visitor objects which can traverse using
|
||||
"""Base class for visitor objects which can traverse using
|
||||
the traverse() function.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
||||
__traverse_options__ = {}
|
||||
|
||||
def traverse_single(self, obj):
|
||||
|
||||
def traverse_single(self, obj, **kw):
|
||||
for v in self._visitor_iterator:
|
||||
meth = getattr(v, "visit_%s" % obj.__visit_name__, None)
|
||||
if meth:
|
||||
return meth(obj)
|
||||
|
||||
def iterate(self, obj):
|
||||
"""traverse the given expression structure, returning an iterator of all elements."""
|
||||
return meth(obj, **kw)
|
||||
|
||||
def iterate(self, obj):
|
||||
"""traverse the given expression structure, returning an iterator
|
||||
of all elements.
|
||||
|
||||
"""
|
||||
return iterate(obj, self.__traverse_options__)
|
||||
|
||||
|
||||
def traverse(self, obj):
|
||||
"""traverse and visit the given expression structure."""
|
||||
|
||||
return traverse(obj, self.__traverse_options__, self._visitor_dict)
|
||||
|
||||
|
||||
@util.memoized_property
|
||||
def _visitor_dict(self):
|
||||
visitors = {}
|
||||
@@ -93,11 +140,11 @@ class ClauseVisitor(object):
|
||||
if name.startswith('visit_'):
|
||||
visitors[name[6:]] = getattr(self, name)
|
||||
return visitors
|
||||
|
||||
|
||||
@property
|
||||
def _visitor_iterator(self):
|
||||
"""iterate through this visitor and each 'chained' visitor."""
|
||||
|
||||
|
||||
v = self
|
||||
while v:
|
||||
yield v
|
||||
@@ -105,41 +152,46 @@ class ClauseVisitor(object):
|
||||
|
||||
def chain(self, visitor):
|
||||
"""'chain' an additional ClauseVisitor onto this ClauseVisitor.
|
||||
|
||||
|
||||
the chained visitor will receive all visit events after this one.
|
||||
|
||||
|
||||
"""
|
||||
tail = list(self._visitor_iterator)[-1]
|
||||
tail._next = visitor
|
||||
return self
|
||||
|
||||
|
||||
class CloningVisitor(ClauseVisitor):
|
||||
"""Base class for visitor objects which can traverse using
|
||||
"""Base class for visitor objects which can traverse using
|
||||
the cloned_traverse() function.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def copy_and_process(self, list_):
|
||||
"""Apply cloned traversal to the given list of elements, and return the new list."""
|
||||
"""Apply cloned traversal to the given list of elements, and return
|
||||
the new list.
|
||||
|
||||
"""
|
||||
return [self.traverse(x) for x in list_]
|
||||
|
||||
def traverse(self, obj):
|
||||
"""traverse and visit the given expression structure."""
|
||||
|
||||
return cloned_traverse(obj, self.__traverse_options__, self._visitor_dict)
|
||||
return cloned_traverse(
|
||||
obj, self.__traverse_options__, self._visitor_dict)
|
||||
|
||||
|
||||
class ReplacingCloningVisitor(CloningVisitor):
|
||||
"""Base class for visitor objects which can traverse using
|
||||
"""Base class for visitor objects which can traverse using
|
||||
the replacement_traverse() function.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def replace(self, elem):
|
||||
"""receive pre-copied elements during a cloning traversal.
|
||||
|
||||
If the method returns a new element, the element is used
|
||||
instead of creating a simple copy of the element. Traversal
|
||||
|
||||
If the method returns a new element, the element is used
|
||||
instead of creating a simple copy of the element. Traversal
|
||||
will halt on the newly returned element if it is re-encountered.
|
||||
"""
|
||||
return None
|
||||
@@ -154,25 +206,39 @@ class ReplacingCloningVisitor(CloningVisitor):
|
||||
return e
|
||||
return replacement_traverse(obj, self.__traverse_options__, replace)
|
||||
|
||||
|
||||
def iterate(obj, opts):
|
||||
"""traverse the given expression structure, returning an iterator.
|
||||
|
||||
|
||||
traversal is configured to be breadth-first.
|
||||
|
||||
|
||||
"""
|
||||
# fasttrack for atomic elements like columns
|
||||
children = obj.get_children(**opts)
|
||||
if not children:
|
||||
return [obj]
|
||||
|
||||
traversal = deque()
|
||||
stack = deque([obj])
|
||||
while stack:
|
||||
t = stack.popleft()
|
||||
yield t
|
||||
traversal.append(t)
|
||||
for c in t.get_children(**opts):
|
||||
stack.append(c)
|
||||
return iter(traversal)
|
||||
|
||||
|
||||
def iterate_depthfirst(obj, opts):
|
||||
"""traverse the given expression structure, returning an iterator.
|
||||
|
||||
|
||||
traversal is configured to be depth-first.
|
||||
|
||||
|
||||
"""
|
||||
# fasttrack for atomic elements like columns
|
||||
children = obj.get_children(**opts)
|
||||
if not children:
|
||||
return [obj]
|
||||
|
||||
stack = deque([obj])
|
||||
traversal = deque()
|
||||
while stack:
|
||||
@@ -182,75 +248,81 @@ def iterate_depthfirst(obj, opts):
|
||||
stack.append(c)
|
||||
return iter(traversal)
|
||||
|
||||
def traverse_using(iterator, obj, visitors):
|
||||
"""visit the given expression structure using the given iterator of objects."""
|
||||
|
||||
def traverse_using(iterator, obj, visitors):
|
||||
"""visit the given expression structure using the given iterator of
|
||||
objects.
|
||||
|
||||
"""
|
||||
for target in iterator:
|
||||
meth = visitors.get(target.__visit_name__, None)
|
||||
if meth:
|
||||
meth(target)
|
||||
return obj
|
||||
|
||||
def traverse(obj, opts, visitors):
|
||||
"""traverse and visit the given expression structure using the default iterator."""
|
||||
|
||||
|
||||
def traverse(obj, opts, visitors):
|
||||
"""traverse and visit the given expression structure using the default
|
||||
iterator.
|
||||
|
||||
"""
|
||||
return traverse_using(iterate(obj, opts), obj, visitors)
|
||||
|
||||
def traverse_depthfirst(obj, opts, visitors):
|
||||
"""traverse and visit the given expression structure using the depth-first iterator."""
|
||||
|
||||
def traverse_depthfirst(obj, opts, visitors):
|
||||
"""traverse and visit the given expression structure using the
|
||||
depth-first iterator.
|
||||
|
||||
"""
|
||||
return traverse_using(iterate_depthfirst(obj, opts), obj, visitors)
|
||||
|
||||
|
||||
def cloned_traverse(obj, opts, visitors):
|
||||
"""clone the given expression structure, allowing modifications by visitors."""
|
||||
|
||||
cloned = util.column_dict()
|
||||
"""clone the given expression structure, allowing
|
||||
modifications by visitors."""
|
||||
|
||||
def clone(element):
|
||||
if element not in cloned:
|
||||
cloned[element] = element._clone()
|
||||
return cloned[element]
|
||||
cloned = {}
|
||||
stop_on = set(opts.get('stop_on', []))
|
||||
|
||||
obj = clone(obj)
|
||||
stack = [obj]
|
||||
def clone(elem):
|
||||
if elem in stop_on:
|
||||
return elem
|
||||
else:
|
||||
if id(elem) not in cloned:
|
||||
cloned[id(elem)] = newelem = elem._clone()
|
||||
newelem._copy_internals(clone=clone)
|
||||
meth = visitors.get(newelem.__visit_name__, None)
|
||||
if meth:
|
||||
meth(newelem)
|
||||
return cloned[id(elem)]
|
||||
|
||||
while stack:
|
||||
t = stack.pop()
|
||||
if t in cloned:
|
||||
continue
|
||||
t._copy_internals(clone=clone)
|
||||
|
||||
meth = visitors.get(t.__visit_name__, None)
|
||||
if meth:
|
||||
meth(t)
|
||||
|
||||
for c in t.get_children(**opts):
|
||||
stack.append(c)
|
||||
if obj is not None:
|
||||
obj = clone(obj)
|
||||
return obj
|
||||
|
||||
|
||||
def replacement_traverse(obj, opts, replace):
|
||||
"""clone the given expression structure, allowing element replacement by a given replacement function."""
|
||||
|
||||
cloned = util.column_dict()
|
||||
stop_on = util.column_set(opts.get('stop_on', []))
|
||||
"""clone the given expression structure, allowing element
|
||||
replacement by a given replacement function."""
|
||||
|
||||
def clone(element):
|
||||
newelem = replace(element)
|
||||
if newelem is not None:
|
||||
stop_on.add(newelem)
|
||||
return newelem
|
||||
cloned = {}
|
||||
stop_on = set([id(x) for x in opts.get('stop_on', [])])
|
||||
|
||||
if element not in cloned:
|
||||
cloned[element] = element._clone()
|
||||
return cloned[element]
|
||||
def clone(elem, **kw):
|
||||
if id(elem) in stop_on or \
|
||||
'no_replacement_traverse' in elem._annotations:
|
||||
return elem
|
||||
else:
|
||||
newelem = replace(elem)
|
||||
if newelem is not None:
|
||||
stop_on.add(id(newelem))
|
||||
return newelem
|
||||
else:
|
||||
if elem not in cloned:
|
||||
cloned[elem] = newelem = elem._clone()
|
||||
newelem._copy_internals(clone=clone, **kw)
|
||||
return cloned[elem]
|
||||
|
||||
obj = clone(obj)
|
||||
stack = [obj]
|
||||
while stack:
|
||||
t = stack.pop()
|
||||
if t in stop_on:
|
||||
continue
|
||||
t._copy_internals(clone=clone)
|
||||
for c in t.get_children(**opts):
|
||||
stack.append(c)
|
||||
if obj is not None:
|
||||
obj = clone(obj, **opts)
|
||||
return obj
|
||||
|
Reference in New Issue
Block a user