You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							365 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
	
	
							365 lines
						
					
					
						
							11 KiB
						
					
					
				# sql/annotation.py
 | 
						|
# Copyright (C) 2005-2022 the SQLAlchemy authors and contributors
 | 
						|
# <see AUTHORS file>
 | 
						|
#
 | 
						|
# This module is part of SQLAlchemy and is released under
 | 
						|
# the MIT License: https://www.opensource.org/licenses/mit-license.php
 | 
						|
 | 
						|
"""The :class:`.Annotated` class and related routines; creates hash-equivalent
 | 
						|
copies of SQL constructs which contain context-specific markers and
 | 
						|
associations.
 | 
						|
 | 
						|
"""
 | 
						|
 | 
						|
from . import operators
 | 
						|
from .base import HasCacheKey
 | 
						|
from .traversals import anon_map
 | 
						|
from .visitors import InternalTraversal
 | 
						|
from .. import util
 | 
						|
 | 
						|
EMPTY_ANNOTATIONS = util.immutabledict()
 | 
						|
 | 
						|
 | 
						|
class SupportsAnnotations(object):
 | 
						|
    _annotations = EMPTY_ANNOTATIONS
 | 
						|
 | 
						|
    @util.memoized_property
 | 
						|
    def _annotations_cache_key(self):
 | 
						|
        anon_map_ = anon_map()
 | 
						|
        return (
 | 
						|
            "_annotations",
 | 
						|
            tuple(
 | 
						|
                (
 | 
						|
                    key,
 | 
						|
                    value._gen_cache_key(anon_map_, [])
 | 
						|
                    if isinstance(value, HasCacheKey)
 | 
						|
                    else value,
 | 
						|
                )
 | 
						|
                for key, value in [
 | 
						|
                    (key, self._annotations[key])
 | 
						|
                    for key in sorted(self._annotations)
 | 
						|
                ]
 | 
						|
            ),
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
class SupportsCloneAnnotations(SupportsAnnotations):
 | 
						|
 | 
						|
    _clone_annotations_traverse_internals = [
 | 
						|
        ("_annotations", InternalTraversal.dp_annotations_key)
 | 
						|
    ]
 | 
						|
 | 
						|
    def _annotate(self, values):
 | 
						|
        """return a copy of this ClauseElement with annotations
 | 
						|
        updated by the given dictionary.
 | 
						|
 | 
						|
        """
 | 
						|
        new = self._clone()
 | 
						|
        new._annotations = new._annotations.union(values)
 | 
						|
        new.__dict__.pop("_annotations_cache_key", None)
 | 
						|
        new.__dict__.pop("_generate_cache_key", None)
 | 
						|
        return new
 | 
						|
 | 
						|
    def _with_annotations(self, values):
 | 
						|
        """return a copy of this ClauseElement with annotations
 | 
						|
        replaced by the given dictionary.
 | 
						|
 | 
						|
        """
 | 
						|
        new = self._clone()
 | 
						|
        new._annotations = util.immutabledict(values)
 | 
						|
        new.__dict__.pop("_annotations_cache_key", None)
 | 
						|
        new.__dict__.pop("_generate_cache_key", None)
 | 
						|
        return new
 | 
						|
 | 
						|
    def _deannotate(self, values=None, clone=False):
 | 
						|
        """return a copy of this :class:`_expression.ClauseElement`
 | 
						|
        with annotations
 | 
						|
        removed.
 | 
						|
 | 
						|
        :param values: optional tuple of individual values
 | 
						|
         to remove.
 | 
						|
 | 
						|
        """
 | 
						|
        if clone or self._annotations:
 | 
						|
            # clone is used when we are also copying
 | 
						|
            # the expression for a deep deannotation
 | 
						|
            new = self._clone()
 | 
						|
            new._annotations = util.immutabledict()
 | 
						|
            new.__dict__.pop("_annotations_cache_key", None)
 | 
						|
            return new
 | 
						|
        else:
 | 
						|
            return self
 | 
						|
 | 
						|
 | 
						|
class SupportsWrappingAnnotations(SupportsAnnotations):
 | 
						|
    def _annotate(self, values):
 | 
						|
        """return a copy of this ClauseElement with annotations
 | 
						|
        updated by the given dictionary.
 | 
						|
 | 
						|
        """
 | 
						|
        return Annotated(self, values)
 | 
						|
 | 
						|
    def _with_annotations(self, values):
 | 
						|
        """return a copy of this ClauseElement with annotations
 | 
						|
        replaced by the given dictionary.
 | 
						|
 | 
						|
        """
 | 
						|
        return Annotated(self, values)
 | 
						|
 | 
						|
    def _deannotate(self, values=None, clone=False):
 | 
						|
        """return a copy of this :class:`_expression.ClauseElement`
 | 
						|
        with annotations
 | 
						|
        removed.
 | 
						|
 | 
						|
        :param values: optional tuple of individual values
 | 
						|
         to remove.
 | 
						|
 | 
						|
        """
 | 
						|
        if clone:
 | 
						|
            s = self._clone()
 | 
						|
            return s
 | 
						|
        else:
 | 
						|
            return self
 | 
						|
 | 
						|
 | 
						|
class Annotated(object):
 | 
						|
    """clones a SupportsAnnotated and applies an 'annotations' dictionary.
 | 
						|
 | 
						|
    Unlike regular clones, this clone also mimics __hash__() and
 | 
						|
    __cmp__() of the original element so that it takes its place
 | 
						|
    in hashed collections.
 | 
						|
 | 
						|
    A reference to the original element is maintained, for the important
 | 
						|
    reason of keeping its hash value current.  When GC'ed, the
 | 
						|
    hash value may be reused, causing conflicts.
 | 
						|
 | 
						|
    .. note::  The rationale for Annotated producing a brand new class,
 | 
						|
       rather than placing the functionality directly within ClauseElement,
 | 
						|
       is **performance**.  The __hash__() method is absent on plain
 | 
						|
       ClauseElement which leads to significantly reduced function call
 | 
						|
       overhead, as the use of sets and dictionaries against ClauseElement
 | 
						|
       objects is prevalent, but most are not "annotated".
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    _is_column_operators = False
 | 
						|
 | 
						|
    def __new__(cls, *args):
 | 
						|
        if not args:
 | 
						|
            # clone constructor
 | 
						|
            return object.__new__(cls)
 | 
						|
        else:
 | 
						|
            element, values = args
 | 
						|
            # pull appropriate subclass from registry of annotated
 | 
						|
            # classes
 | 
						|
            try:
 | 
						|
                cls = annotated_classes[element.__class__]
 | 
						|
            except KeyError:
 | 
						|
                cls = _new_annotation_type(element.__class__, cls)
 | 
						|
            return object.__new__(cls)
 | 
						|
 | 
						|
    def __init__(self, element, values):
 | 
						|
        self.__dict__ = element.__dict__.copy()
 | 
						|
        self.__dict__.pop("_annotations_cache_key", None)
 | 
						|
        self.__dict__.pop("_generate_cache_key", None)
 | 
						|
        self.__element = element
 | 
						|
        self._annotations = util.immutabledict(values)
 | 
						|
        self._hash = hash(element)
 | 
						|
 | 
						|
    def _annotate(self, values):
 | 
						|
        _values = self._annotations.union(values)
 | 
						|
        return self._with_annotations(_values)
 | 
						|
 | 
						|
    def _with_annotations(self, values):
 | 
						|
        clone = self.__class__.__new__(self.__class__)
 | 
						|
        clone.__dict__ = self.__dict__.copy()
 | 
						|
        clone.__dict__.pop("_annotations_cache_key", None)
 | 
						|
        clone.__dict__.pop("_generate_cache_key", None)
 | 
						|
        clone._annotations = values
 | 
						|
        return clone
 | 
						|
 | 
						|
    def _deannotate(self, values=None, clone=True):
 | 
						|
        if values is None:
 | 
						|
            return self.__element
 | 
						|
        else:
 | 
						|
            return self._with_annotations(
 | 
						|
                util.immutabledict(
 | 
						|
                    {
 | 
						|
                        key: value
 | 
						|
                        for key, value in self._annotations.items()
 | 
						|
                        if key not in values
 | 
						|
                    }
 | 
						|
                )
 | 
						|
            )
 | 
						|
 | 
						|
    def _compiler_dispatch(self, visitor, **kw):
 | 
						|
        return self.__element.__class__._compiler_dispatch(self, visitor, **kw)
 | 
						|
 | 
						|
    @property
 | 
						|
    def _constructor(self):
 | 
						|
        return self.__element._constructor
 | 
						|
 | 
						|
    def _clone(self, **kw):
 | 
						|
        clone = self.__element._clone(**kw)
 | 
						|
        if clone is self.__element:
 | 
						|
            # detect immutable, don't change anything
 | 
						|
            return self
 | 
						|
        else:
 | 
						|
            # update the clone with any changes that have occurred
 | 
						|
            # to this object's __dict__.
 | 
						|
            clone.__dict__.update(self.__dict__)
 | 
						|
            return self.__class__(clone, self._annotations)
 | 
						|
 | 
						|
    def __reduce__(self):
 | 
						|
        return self.__class__, (self.__element, self._annotations)
 | 
						|
 | 
						|
    def __hash__(self):
 | 
						|
        return self._hash
 | 
						|
 | 
						|
    def __eq__(self, other):
 | 
						|
        if self._is_column_operators:
 | 
						|
            return self.__element.__class__.__eq__(self, other)
 | 
						|
        else:
 | 
						|
            return hash(other) == hash(self)
 | 
						|
 | 
						|
    @property
 | 
						|
    def entity_namespace(self):
 | 
						|
        if "entity_namespace" in self._annotations:
 | 
						|
            return self._annotations["entity_namespace"].entity_namespace
 | 
						|
        else:
 | 
						|
            return self.__element.entity_namespace
 | 
						|
 | 
						|
 | 
						|
# hard-generate Annotated subclasses.  this technique
 | 
						|
# is used instead of on-the-fly types (i.e. type.__new__())
 | 
						|
# so that the resulting objects are pickleable; additionally, other
 | 
						|
# decisions can be made up front about the type of object being annotated
 | 
						|
# just once per class rather than per-instance.
 | 
						|
annotated_classes = {}
 | 
						|
 | 
						|
 | 
						|
def _deep_annotate(
 | 
						|
    element, annotations, exclude=None, detect_subquery_cols=False
 | 
						|
):
 | 
						|
    """Deep copy the given ClauseElement, annotating each element
 | 
						|
    with the given annotations dictionary.
 | 
						|
 | 
						|
    Elements within the exclude collection will be cloned but not annotated.
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    # annotated objects hack the __hash__() method so if we want to
 | 
						|
    # uniquely process them we have to use id()
 | 
						|
 | 
						|
    cloned_ids = {}
 | 
						|
 | 
						|
    def clone(elem, **kw):
 | 
						|
        kw["detect_subquery_cols"] = detect_subquery_cols
 | 
						|
        id_ = id(elem)
 | 
						|
 | 
						|
        if id_ in cloned_ids:
 | 
						|
            return cloned_ids[id_]
 | 
						|
 | 
						|
        if (
 | 
						|
            exclude
 | 
						|
            and hasattr(elem, "proxy_set")
 | 
						|
            and elem.proxy_set.intersection(exclude)
 | 
						|
        ):
 | 
						|
            newelem = elem._clone(clone=clone, **kw)
 | 
						|
        elif annotations != elem._annotations:
 | 
						|
            if detect_subquery_cols and elem._is_immutable:
 | 
						|
                newelem = elem._clone(clone=clone, **kw)._annotate(annotations)
 | 
						|
            else:
 | 
						|
                newelem = elem._annotate(annotations)
 | 
						|
        else:
 | 
						|
            newelem = elem
 | 
						|
        newelem._copy_internals(clone=clone)
 | 
						|
        cloned_ids[id_] = newelem
 | 
						|
        return newelem
 | 
						|
 | 
						|
    if element is not None:
 | 
						|
        element = clone(element)
 | 
						|
    clone = None  # remove gc cycles
 | 
						|
    return element
 | 
						|
 | 
						|
 | 
						|
def _deep_deannotate(element, values=None):
 | 
						|
    """Deep copy the given element, removing annotations."""
 | 
						|
 | 
						|
    cloned = {}
 | 
						|
 | 
						|
    def clone(elem, **kw):
 | 
						|
        if values:
 | 
						|
            key = id(elem)
 | 
						|
        else:
 | 
						|
            key = elem
 | 
						|
 | 
						|
        if key not in cloned:
 | 
						|
            newelem = elem._deannotate(values=values, clone=True)
 | 
						|
            newelem._copy_internals(clone=clone)
 | 
						|
            cloned[key] = newelem
 | 
						|
            return newelem
 | 
						|
        else:
 | 
						|
            return cloned[key]
 | 
						|
 | 
						|
    if element is not None:
 | 
						|
        element = clone(element)
 | 
						|
    clone = None  # remove gc cycles
 | 
						|
    return element
 | 
						|
 | 
						|
 | 
						|
def _shallow_annotate(element, annotations):
 | 
						|
    """Annotate the given ClauseElement and copy its internals so that
 | 
						|
    internal objects refer to the new annotated object.
 | 
						|
 | 
						|
    Basically used to apply a "don't traverse" annotation to a
 | 
						|
    selectable, without digging throughout the whole
 | 
						|
    structure wasting time.
 | 
						|
    """
 | 
						|
    element = element._annotate(annotations)
 | 
						|
    element._copy_internals()
 | 
						|
    return element
 | 
						|
 | 
						|
 | 
						|
def _new_annotation_type(cls, base_cls):
 | 
						|
    if issubclass(cls, Annotated):
 | 
						|
        return cls
 | 
						|
    elif cls in annotated_classes:
 | 
						|
        return annotated_classes[cls]
 | 
						|
 | 
						|
    for super_ in cls.__mro__:
 | 
						|
        # check if an Annotated subclass more specific than
 | 
						|
        # the given base_cls is already registered, such
 | 
						|
        # as AnnotatedColumnElement.
 | 
						|
        if super_ in annotated_classes:
 | 
						|
            base_cls = annotated_classes[super_]
 | 
						|
            break
 | 
						|
 | 
						|
    annotated_classes[cls] = anno_cls = type(
 | 
						|
        "Annotated%s" % cls.__name__, (base_cls, cls), {}
 | 
						|
    )
 | 
						|
    globals()["Annotated%s" % cls.__name__] = anno_cls
 | 
						|
 | 
						|
    if "_traverse_internals" in cls.__dict__:
 | 
						|
        anno_cls._traverse_internals = list(cls._traverse_internals) + [
 | 
						|
            ("_annotations", InternalTraversal.dp_annotations_key)
 | 
						|
        ]
 | 
						|
    elif cls.__dict__.get("inherit_cache", False):
 | 
						|
        anno_cls._traverse_internals = list(cls._traverse_internals) + [
 | 
						|
            ("_annotations", InternalTraversal.dp_annotations_key)
 | 
						|
        ]
 | 
						|
 | 
						|
    # some classes include this even if they have traverse_internals
 | 
						|
    # e.g. BindParameter, add it if present.
 | 
						|
    if cls.__dict__.get("inherit_cache", False):
 | 
						|
        anno_cls.inherit_cache = True
 | 
						|
 | 
						|
    anno_cls._is_column_operators = issubclass(cls, operators.ColumnOperators)
 | 
						|
 | 
						|
    return anno_cls
 | 
						|
 | 
						|
 | 
						|
def _prepare_annotations(target_hierarchy, base_cls):
 | 
						|
    for cls in util.walk_subclasses(target_hierarchy):
 | 
						|
        _new_annotation_type(cls, base_cls)
 |