From e76eb4697e8d19ffcd9778b27e93cdd98da14227 Mon Sep 17 00:00:00 2001 From: Polina Date: Thu, 2 Jul 2026 20:43:57 +0000 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?venv/Lib/site-packages/sqlalchemy/util=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../site-packages/sqlalchemy/util/__init__.py | 162 ++++ .../sqlalchemy/util/_collections.py | 712 ++++++++++++++++++ .../sqlalchemy/util/_concurrency_py3k.py | 288 +++++++ .../site-packages/sqlalchemy/util/_has_cy.py | 40 + .../sqlalchemy/util/_py_collections.py | 541 +++++++++++++ 5 files changed, 1743 insertions(+) create mode 100644 venv/Lib/site-packages/sqlalchemy/util/__init__.py create mode 100644 venv/Lib/site-packages/sqlalchemy/util/_collections.py create mode 100644 venv/Lib/site-packages/sqlalchemy/util/_concurrency_py3k.py create mode 100644 venv/Lib/site-packages/sqlalchemy/util/_has_cy.py create mode 100644 venv/Lib/site-packages/sqlalchemy/util/_py_collections.py diff --git a/venv/Lib/site-packages/sqlalchemy/util/__init__.py b/venv/Lib/site-packages/sqlalchemy/util/__init__.py new file mode 100644 index 0000000..820f4cb --- /dev/null +++ b/venv/Lib/site-packages/sqlalchemy/util/__init__.py @@ -0,0 +1,162 @@ +# util/__init__.py +# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: https://www.opensource.org/licenses/mit-license.php + + +from collections import defaultdict as defaultdict +from functools import partial as partial +from functools import update_wrapper as update_wrapper + +from . import preloaded as preloaded +from ._collections import coerce_generator_arg as coerce_generator_arg +from ._collections import coerce_to_immutabledict as coerce_to_immutabledict +from ._collections import column_dict as column_dict +from ._collections import column_set as column_set +from ._collections import EMPTY_DICT as EMPTY_DICT +from ._collections import EMPTY_SET as EMPTY_SET +from ._collections import FacadeDict as FacadeDict +from ._collections import flatten_iterator as flatten_iterator +from ._collections import has_dupes as has_dupes +from ._collections import has_intersection as has_intersection +from ._collections import IdentitySet as IdentitySet +from ._collections import immutabledict as immutabledict +from ._collections import LRUCache as LRUCache +from ._collections import merge_lists_w_ordering as merge_lists_w_ordering +from ._collections import NONE_SET as NONE_SET +from ._collections import ordered_column_set as ordered_column_set +from ._collections import OrderedDict as OrderedDict +from ._collections import OrderedIdentitySet as OrderedIdentitySet +from ._collections import OrderedProperties as OrderedProperties +from ._collections import OrderedSet as OrderedSet +from ._collections import PopulateDict as PopulateDict +from ._collections import Properties as Properties +from ._collections import ReadOnlyContainer as ReadOnlyContainer +from ._collections import ReadOnlyProperties as ReadOnlyProperties +from ._collections import ScopedRegistry as ScopedRegistry +from ._collections import sort_dictionary as sort_dictionary +from ._collections import ThreadLocalRegistry as ThreadLocalRegistry +from ._collections import to_column_set as to_column_set +from ._collections import to_list as to_list +from ._collections import to_set as to_set +from ._collections import unique_list as unique_list +from ._collections import UniqueAppender as UniqueAppender +from ._collections import update_copy as update_copy +from ._collections import WeakPopulateDict as WeakPopulateDict +from ._collections import WeakSequence as WeakSequence +from .compat import anext_ as anext_ +from .compat import arm as arm +from .compat import b as b +from .compat import b64decode as b64decode +from .compat import b64encode as b64encode +from .compat import cmp as cmp +from .compat import cpython as cpython +from .compat import dataclass_fields as dataclass_fields +from .compat import decode_backslashreplace as decode_backslashreplace +from .compat import dottedgetter as dottedgetter +from .compat import freethreading as freethreading +from .compat import get_annotations as get_annotations +from .compat import has_refcount_gc as has_refcount_gc +from .compat import inspect_getfullargspec as inspect_getfullargspec +from .compat import is64bit as is64bit +from .compat import local_dataclass_fields as local_dataclass_fields +from .compat import mini_gil as mini_gil +from .compat import osx as osx +from .compat import py310 as py310 +from .compat import py311 as py311 +from .compat import py312 as py312 +from .compat import py313 as py313 +from .compat import py314 as py314 +from .compat import py38 as py38 +from .compat import py39 as py39 +from .compat import pypy as pypy +from .compat import win32 as win32 +from .concurrency import await_fallback as await_fallback +from .concurrency import await_only as await_only +from .concurrency import greenlet_spawn as greenlet_spawn +from .concurrency import is_exit_exception as is_exit_exception +from .deprecations import became_legacy_20 as became_legacy_20 +from .deprecations import deprecated as deprecated +from .deprecations import deprecated_cls as deprecated_cls +from .deprecations import deprecated_params as deprecated_params +from .deprecations import moved_20 as moved_20 +from .deprecations import warn_deprecated as warn_deprecated +from .langhelpers import add_parameter_text as add_parameter_text +from .langhelpers import as_interface as as_interface +from .langhelpers import asbool as asbool +from .langhelpers import asint as asint +from .langhelpers import assert_arg_type as assert_arg_type +from .langhelpers import attrsetter as attrsetter +from .langhelpers import bool_or_str as bool_or_str +from .langhelpers import chop_traceback as chop_traceback +from .langhelpers import class_hierarchy as class_hierarchy +from .langhelpers import classproperty as classproperty +from .langhelpers import clsname_as_plain_name as clsname_as_plain_name +from .langhelpers import coerce_kw_type as coerce_kw_type +from .langhelpers import constructor_copy as constructor_copy +from .langhelpers import constructor_key as constructor_key +from .langhelpers import counter as counter +from .langhelpers import create_proxy_methods as create_proxy_methods +from .langhelpers import decode_slice as decode_slice +from .langhelpers import decorator as decorator +from .langhelpers import dictlike_iteritems as dictlike_iteritems +from .langhelpers import duck_type_collection as duck_type_collection +from .langhelpers import ellipses_string as ellipses_string +from .langhelpers import EnsureKWArg as EnsureKWArg +from .langhelpers import FastIntFlag as FastIntFlag +from .langhelpers import format_argspec_init as format_argspec_init +from .langhelpers import format_argspec_plus as format_argspec_plus +from .langhelpers import generic_fn_descriptor as generic_fn_descriptor +from .langhelpers import generic_repr as generic_repr +from .langhelpers import get_callable_argspec as get_callable_argspec +from .langhelpers import get_cls_kwargs as get_cls_kwargs +from .langhelpers import get_func_kwargs as get_func_kwargs +from .langhelpers import getargspec_init as getargspec_init +from .langhelpers import has_compiled_ext as has_compiled_ext +from .langhelpers import HasMemoized as HasMemoized +from .langhelpers import ( + HasMemoized_ro_memoized_attribute as HasMemoized_ro_memoized_attribute, +) +from .langhelpers import hybridmethod as hybridmethod +from .langhelpers import hybridproperty as hybridproperty +from .langhelpers import inject_docstring_text as inject_docstring_text +from .langhelpers import iterate_attributes as iterate_attributes +from .langhelpers import map_bits as map_bits +from .langhelpers import md5_hex as md5_hex +from .langhelpers import memoized_instancemethod as memoized_instancemethod +from .langhelpers import memoized_property as memoized_property +from .langhelpers import MemoizedSlots as MemoizedSlots +from .langhelpers import method_is_overridden as method_is_overridden +from .langhelpers import methods_equivalent as methods_equivalent +from .langhelpers import ( + monkeypatch_proxied_specials as monkeypatch_proxied_specials, +) +from .langhelpers import non_memoized_property as non_memoized_property +from .langhelpers import NoneType as NoneType +from .langhelpers import only_once as only_once +from .langhelpers import ( + parse_user_argument_for_enum as parse_user_argument_for_enum, +) +from .langhelpers import PluginLoader as PluginLoader +from .langhelpers import portable_instancemethod as portable_instancemethod +from .langhelpers import quoted_token_parser as quoted_token_parser +from .langhelpers import ro_memoized_property as ro_memoized_property +from .langhelpers import ro_non_memoized_property as ro_non_memoized_property +from .langhelpers import rw_hybridproperty as rw_hybridproperty +from .langhelpers import safe_reraise as safe_reraise +from .langhelpers import set_creation_order as set_creation_order +from .langhelpers import string_or_unprintable as string_or_unprintable +from .langhelpers import symbol as symbol +from .langhelpers import TypingOnly as TypingOnly +from .langhelpers import ( + unbound_method_to_callable as unbound_method_to_callable, +) +from .langhelpers import walk_subclasses as walk_subclasses +from .langhelpers import warn as warn +from .langhelpers import warn_exception as warn_exception +from .langhelpers import warn_limited as warn_limited +from .langhelpers import wrap_callable as wrap_callable +from .preloaded import preload_module as preload_module +from .typing import is_non_string_iterable as is_non_string_iterable diff --git a/venv/Lib/site-packages/sqlalchemy/util/_collections.py b/venv/Lib/site-packages/sqlalchemy/util/_collections.py new file mode 100644 index 0000000..14f71a2 --- /dev/null +++ b/venv/Lib/site-packages/sqlalchemy/util/_collections.py @@ -0,0 +1,712 @@ +# util/_collections.py +# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: https://www.opensource.org/licenses/mit-license.php +# mypy: allow-untyped-defs, allow-untyped-calls + +"""Collection classes and helpers.""" + +from __future__ import annotations + +import operator +import threading +import types +import typing +from typing import Any +from typing import Callable +from typing import cast +from typing import Container +from typing import Dict +from typing import FrozenSet +from typing import Generic +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Mapping +from typing import NoReturn +from typing import Optional +from typing import overload +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import TypeVar +from typing import Union +from typing import ValuesView +import weakref + +from ._has_cy import HAS_CYEXTENSION +from .typing import is_non_string_iterable +from .typing import Literal +from .typing import Protocol + +if typing.TYPE_CHECKING or not HAS_CYEXTENSION: + from ._py_collections import immutabledict as immutabledict + from ._py_collections import IdentitySet as IdentitySet + from ._py_collections import ReadOnlyContainer as ReadOnlyContainer + from ._py_collections import ImmutableDictBase as ImmutableDictBase + from ._py_collections import OrderedSet as OrderedSet + from ._py_collections import unique_list as unique_list +else: + from sqlalchemy.cyextension.immutabledict import ( + ReadOnlyContainer as ReadOnlyContainer, + ) + from sqlalchemy.cyextension.immutabledict import ( + ImmutableDictBase as ImmutableDictBase, + ) + from sqlalchemy.cyextension.immutabledict import ( + immutabledict as immutabledict, + ) + from sqlalchemy.cyextension.collections import IdentitySet as IdentitySet + from sqlalchemy.cyextension.collections import OrderedSet as OrderedSet + from sqlalchemy.cyextension.collections import ( # noqa + unique_list as unique_list, + ) + +_T = TypeVar("_T", bound=Any) +_KT = TypeVar("_KT", bound=Any) +_VT = TypeVar("_VT", bound=Any) +_T_co = TypeVar("_T_co", covariant=True) + +EMPTY_SET: FrozenSet[Any] = frozenset() +NONE_SET: FrozenSet[Any] = frozenset([None]) + + +def merge_lists_w_ordering(a: List[Any], b: List[Any]) -> List[Any]: + """merge two lists, maintaining ordering as much as possible. + + this is to reconcile vars(cls) with cls.__annotations__. + + Example:: + + >>> a = ["__tablename__", "id", "x", "created_at"] + >>> b = ["id", "name", "data", "y", "created_at"] + >>> merge_lists_w_ordering(a, b) + ['__tablename__', 'id', 'name', 'data', 'y', 'x', 'created_at'] + + This is not necessarily the ordering that things had on the class, + in this case the class is:: + + class User(Base): + __tablename__ = "users" + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] + data: Mapped[Optional[str]] + x = Column(Integer) + y: Mapped[int] + created_at: Mapped[datetime.datetime] = mapped_column() + + But things are *mostly* ordered. + + The algorithm could also be done by creating a partial ordering for + all items in both lists and then using topological_sort(), but that + is too much overhead. + + Background on how I came up with this is at: + https://gist.github.com/zzzeek/89de958cf0803d148e74861bd682ebae + + """ + overlap = set(a).intersection(b) + + result = [] + + current, other = iter(a), iter(b) + + while True: + for element in current: + if element in overlap: + overlap.discard(element) + other, current = current, other + break + + result.append(element) + else: + result.extend(other) + break + + return result + + +def coerce_to_immutabledict(d: Mapping[_KT, _VT]) -> immutabledict[_KT, _VT]: + if not d: + return EMPTY_DICT + elif isinstance(d, immutabledict): + return d + else: + return immutabledict(d) + + +EMPTY_DICT: immutabledict[Any, Any] = immutabledict() + + +class FacadeDict(ImmutableDictBase[_KT, _VT]): + """A dictionary that is not publicly mutable.""" + + def __new__(cls, *args: Any) -> FacadeDict[Any, Any]: + new = ImmutableDictBase.__new__(cls) + return new + + def copy(self) -> NoReturn: + raise NotImplementedError( + "an immutabledict shouldn't need to be copied. use dict(d) " + "if you need a mutable dictionary." + ) + + def __reduce__(self) -> Any: + return FacadeDict, (dict(self),) + + def _insert_item(self, key: _KT, value: _VT) -> None: + """insert an item into the dictionary directly.""" + dict.__setitem__(self, key, value) + + def __repr__(self) -> str: + return "FacadeDict(%s)" % dict.__repr__(self) + + +_DT = TypeVar("_DT", bound=Any) + +_F = TypeVar("_F", bound=Any) + + +class Properties(Generic[_T]): + """Provide a __getattr__/__setattr__ interface over a dict.""" + + __slots__ = ("_data",) + + _data: Dict[str, _T] + + def __init__(self, data: Dict[str, _T]): + object.__setattr__(self, "_data", data) + + def __len__(self) -> int: + return len(self._data) + + def __iter__(self) -> Iterator[_T]: + return iter(list(self._data.values())) + + def __dir__(self) -> List[str]: + return dir(super()) + [str(k) for k in self._data.keys()] + + def __add__(self, other: Properties[_F]) -> List[Union[_T, _F]]: + return list(self) + list(other) + + def __setitem__(self, key: str, obj: _T) -> None: + self._data[key] = obj + + def __getitem__(self, key: str) -> _T: + return self._data[key] + + def __delitem__(self, key: str) -> None: + del self._data[key] + + def __setattr__(self, key: str, obj: _T) -> None: + self._data[key] = obj + + def __getstate__(self) -> Dict[str, Any]: + return {"_data": self._data} + + def __setstate__(self, state: Dict[str, Any]) -> None: + object.__setattr__(self, "_data", state["_data"]) + + def __getattr__(self, key: str) -> _T: + try: + return self._data[key] + except KeyError: + raise AttributeError(key) + + def __contains__(self, key: str) -> bool: + return key in self._data + + def as_readonly(self) -> ReadOnlyProperties[_T]: + """Return an immutable proxy for this :class:`.Properties`.""" + + return ReadOnlyProperties(self._data) + + def update(self, value: Dict[str, _T]) -> None: + self._data.update(value) + + @overload + def get(self, key: str) -> Optional[_T]: ... + + @overload + def get(self, key: str, default: Union[_DT, _T]) -> Union[_DT, _T]: ... + + def get( + self, key: str, default: Optional[Union[_DT, _T]] = None + ) -> Optional[Union[_T, _DT]]: + if key in self: + return self[key] + else: + return default + + def keys(self) -> List[str]: + return list(self._data) + + def values(self) -> List[_T]: + return list(self._data.values()) + + def items(self) -> List[Tuple[str, _T]]: + return list(self._data.items()) + + def has_key(self, key: str) -> bool: + return key in self._data + + def clear(self) -> None: + self._data.clear() + + +class OrderedProperties(Properties[_T]): + """Provide a __getattr__/__setattr__ interface with an OrderedDict + as backing store.""" + + __slots__ = () + + def __init__(self): + Properties.__init__(self, OrderedDict()) + + +class ReadOnlyProperties(ReadOnlyContainer, Properties[_T]): + """Provide immutable dict/object attribute to an underlying dictionary.""" + + __slots__ = () + + +def _ordered_dictionary_sort(d, key=None): + """Sort an OrderedDict in-place.""" + + items = [(k, d[k]) for k in sorted(d, key=key)] + + d.clear() + + d.update(items) + + +OrderedDict = dict +sort_dictionary = _ordered_dictionary_sort + + +class WeakSequence(Sequence[_T]): + def __init__(self, __elements: Sequence[_T] = ()): + # adapted from weakref.WeakKeyDictionary, prevent reference + # cycles in the collection itself + def _remove(item, selfref=weakref.ref(self)): + self = selfref() + if self is not None: + self._storage.remove(item) + + self._remove = _remove + self._storage = [ + weakref.ref(element, _remove) for element in __elements + ] + + def append(self, item): + self._storage.append(weakref.ref(item, self._remove)) + + def __len__(self): + return len(self._storage) + + def __iter__(self): + return ( + obj for obj in (ref() for ref in self._storage) if obj is not None + ) + + def __getitem__(self, index): + return self._storage[index]() + + +class OrderedIdentitySet(IdentitySet): + def __init__(self, iterable: Optional[Iterable[Any]] = None): + IdentitySet.__init__(self) + self._members = OrderedDict() + if iterable: + for o in iterable: + self.add(o) + + +class PopulateDict(Dict[_KT, _VT]): + """A dict which populates missing values via a creation function. + + Note the creation function takes a key, unlike + collections.defaultdict. + + """ + + def __init__(self, creator: Callable[[_KT], _VT]): + self.creator = creator + + def __missing__(self, key: Any) -> Any: + self[key] = val = self.creator(key) + return val + + +class WeakPopulateDict(Dict[_KT, _VT]): + """Like PopulateDict, but assumes a self + a method and does not create + a reference cycle. + + """ + + def __init__(self, creator_method: types.MethodType): + self.creator = creator_method.__func__ + weakself = creator_method.__self__ + self.weakself = weakref.ref(weakself) + + def __missing__(self, key: Any) -> Any: + self[key] = val = self.creator(self.weakself(), key) + return val + + +# Define collections that are capable of storing +# ColumnElement objects as hashable keys/elements. +# At this point, these are mostly historical, things +# used to be more complicated. +column_set = set +column_dict = dict +ordered_column_set = OrderedSet + + +class UniqueAppender(Generic[_T]): + """Appends items to a collection ensuring uniqueness. + + Additional appends() of the same object are ignored. Membership is + determined by identity (``is a``) not equality (``==``). + """ + + __slots__ = "data", "_data_appender", "_unique" + + data: Union[Iterable[_T], Set[_T], List[_T]] + _data_appender: Callable[[_T], None] + _unique: Dict[int, Literal[True]] + + def __init__( + self, + data: Union[Iterable[_T], Set[_T], List[_T]], + via: Optional[str] = None, + ): + self.data = data + self._unique = {} + if via: + self._data_appender = getattr(data, via) + elif hasattr(data, "append"): + self._data_appender = cast("List[_T]", data).append + elif hasattr(data, "add"): + self._data_appender = cast("Set[_T]", data).add + + def append(self, item: _T) -> None: + id_ = id(item) + if id_ not in self._unique: + self._data_appender(item) + self._unique[id_] = True + + def __iter__(self) -> Iterator[_T]: + return iter(self.data) + + +def coerce_generator_arg(arg: Any) -> List[Any]: + if len(arg) == 1 and isinstance(arg[0], types.GeneratorType): + return list(arg[0]) + else: + return cast("List[Any]", arg) + + +def to_list(x: Any, default: Optional[List[Any]] = None) -> List[Any]: + if x is None: + return default # type: ignore + if not is_non_string_iterable(x): + return [x] + elif isinstance(x, list): + return x + else: + return list(x) + + +def has_intersection(set_: Container[Any], iterable: Iterable[Any]) -> bool: + r"""return True if any items of set\_ are present in iterable. + + Goes through special effort to ensure __hash__ is not called + on items in iterable that don't support it. + + """ + return any(i in set_ for i in iterable if i.__hash__) + + +def to_set(x): + if x is None: + return set() + if not isinstance(x, set): + return set(to_list(x)) + else: + return x + + +def to_column_set(x: Any) -> Set[Any]: + if x is None: + return column_set() + if not isinstance(x, column_set): + return column_set(to_list(x)) + else: + return x + + +def update_copy( + d: Dict[Any, Any], _new: Optional[Dict[Any, Any]] = None, **kw: Any +) -> Dict[Any, Any]: + """Copy the given dict and update with the given values.""" + + d = d.copy() + if _new: + d.update(_new) + d.update(**kw) + return d + + +def flatten_iterator(x: Iterable[_T]) -> Iterator[_T]: + """Given an iterator of which further sub-elements may also be + iterators, flatten the sub-elements into a single iterator. + + """ + elem: _T + for elem in x: + if not isinstance(elem, str) and hasattr(elem, "__iter__"): + yield from flatten_iterator(elem) + else: + yield elem + + +class LRUCache(typing.MutableMapping[_KT, _VT]): + """Dictionary with 'squishy' removal of least + recently used items. + + Note that either get() or [] should be used here, but + generally its not safe to do an "in" check first as the dictionary + can change subsequent to that call. + + """ + + __slots__ = ( + "capacity", + "threshold", + "size_alert", + "_data", + "_counter", + "_mutex", + ) + + capacity: int + threshold: float + size_alert: Optional[Callable[[LRUCache[_KT, _VT]], None]] + + def __init__( + self, + capacity: int = 100, + threshold: float = 0.5, + size_alert: Optional[Callable[..., None]] = None, + ): + self.capacity = capacity + self.threshold = threshold + self.size_alert = size_alert + self._counter = 0 + self._mutex = threading.Lock() + self._data: Dict[_KT, Tuple[_KT, _VT, List[int]]] = {} + + def _inc_counter(self): + self._counter += 1 + return self._counter + + @overload + def get(self, key: _KT) -> Optional[_VT]: ... + + @overload + def get(self, key: _KT, default: Union[_VT, _T]) -> Union[_VT, _T]: ... + + def get( + self, key: _KT, default: Optional[Union[_VT, _T]] = None + ) -> Optional[Union[_VT, _T]]: + item = self._data.get(key) + if item is not None: + item[2][0] = self._inc_counter() + return item[1] + else: + return default + + def __getitem__(self, key: _KT) -> _VT: + item = self._data[key] + item[2][0] = self._inc_counter() + return item[1] + + def __iter__(self) -> Iterator[_KT]: + return iter(self._data) + + def __len__(self) -> int: + return len(self._data) + + def values(self) -> ValuesView[_VT]: + return typing.ValuesView({k: i[1] for k, i in self._data.items()}) + + def __setitem__(self, key: _KT, value: _VT) -> None: + self._data[key] = (key, value, [self._inc_counter()]) + self._manage_size() + + def __delitem__(self, __v: _KT) -> None: + del self._data[__v] + + @property + def size_threshold(self) -> float: + return self.capacity + self.capacity * self.threshold + + def _manage_size(self) -> None: + if not self._mutex.acquire(False): + return + try: + size_alert = bool(self.size_alert) + while len(self) > self.capacity + self.capacity * self.threshold: + if size_alert: + size_alert = False + self.size_alert(self) # type: ignore + by_counter = sorted( + self._data.values(), + key=operator.itemgetter(2), + reverse=True, + ) + for item in by_counter[self.capacity :]: + try: + del self._data[item[0]] + except KeyError: + # deleted elsewhere; skip + continue + finally: + self._mutex.release() + + +class _CreateFuncType(Protocol[_T_co]): + def __call__(self) -> _T_co: ... + + +class _ScopeFuncType(Protocol): + def __call__(self) -> Any: ... + + +class ScopedRegistry(Generic[_T]): + """A Registry that can store one or multiple instances of a single + class on the basis of a "scope" function. + + The object implements ``__call__`` as the "getter", so by + calling ``myregistry()`` the contained object is returned + for the current scope. + + :param createfunc: + a callable that returns a new object to be placed in the registry + + :param scopefunc: + a callable that will return a key to store/retrieve an object. + """ + + __slots__ = "createfunc", "scopefunc", "registry" + + createfunc: _CreateFuncType[_T] + scopefunc: _ScopeFuncType + registry: Any + + def __init__( + self, createfunc: Callable[[], _T], scopefunc: Callable[[], Any] + ): + """Construct a new :class:`.ScopedRegistry`. + + :param createfunc: A creation function that will generate + a new value for the current scope, if none is present. + + :param scopefunc: A function that returns a hashable + token representing the current scope (such as, current + thread identifier). + + """ + self.createfunc = createfunc + self.scopefunc = scopefunc + self.registry = {} + + def __call__(self) -> _T: + key = self.scopefunc() + try: + return self.registry[key] # type: ignore[no-any-return] + except KeyError: + return self.registry.setdefault(key, self.createfunc()) # type: ignore[no-any-return] # noqa: E501 + + def has(self) -> bool: + """Return True if an object is present in the current scope.""" + + return self.scopefunc() in self.registry + + def set(self, obj: _T) -> None: + """Set the value for the current scope.""" + + self.registry[self.scopefunc()] = obj + + def clear(self) -> None: + """Clear the current scope, if any.""" + + try: + del self.registry[self.scopefunc()] + except KeyError: + pass + + +class ThreadLocalRegistry(ScopedRegistry[_T]): + """A :class:`.ScopedRegistry` that uses a ``threading.local()`` + variable for storage. + + """ + + def __init__(self, createfunc: Callable[[], _T]): + self.createfunc = createfunc + self.registry = threading.local() + + def __call__(self) -> _T: + try: + return self.registry.value # type: ignore[no-any-return] + except AttributeError: + val = self.registry.value = self.createfunc() + return val + + def has(self) -> bool: + return hasattr(self.registry, "value") + + def set(self, obj: _T) -> None: + self.registry.value = obj + + def clear(self) -> None: + try: + del self.registry.value + except AttributeError: + pass + + +def has_dupes(sequence, target): + """Given a sequence and search object, return True if there's more + than one, False if zero or one of them. + + + """ + # compare to .index version below, this version introduces less function + # overhead and is usually the same speed. At 15000 items (way bigger than + # a relationship-bound collection in memory usually is) it begins to + # fall behind the other version only by microseconds. + c = 0 + for item in sequence: + if item is target: + c += 1 + if c > 1: + return True + return False + + +# .index version. the two __contains__ calls as well +# as .index() and isinstance() slow this down. +# def has_dupes(sequence, target): +# if target not in sequence: +# return False +# elif not isinstance(sequence, collections_abc.Sequence): +# return False +# +# idx = sequence.index(target) +# return target in sequence[idx + 1:] diff --git a/venv/Lib/site-packages/sqlalchemy/util/_concurrency_py3k.py b/venv/Lib/site-packages/sqlalchemy/util/_concurrency_py3k.py new file mode 100644 index 0000000..2b23459 --- /dev/null +++ b/venv/Lib/site-packages/sqlalchemy/util/_concurrency_py3k.py @@ -0,0 +1,288 @@ +# util/_concurrency_py3k.py +# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: https://www.opensource.org/licenses/mit-license.php +# mypy: allow-untyped-defs, allow-untyped-calls + +from __future__ import annotations + +import asyncio +from contextvars import Context +import sys +import typing +from typing import Any +from typing import Awaitable +from typing import Callable +from typing import Coroutine +from typing import Optional +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +from .langhelpers import memoized_property +from .. import exc +from ..util import py311 +from ..util.typing import Literal +from ..util.typing import Protocol +from ..util.typing import Self +from ..util.typing import TypeGuard + +_T = TypeVar("_T") + +if typing.TYPE_CHECKING: + + class greenlet(Protocol): + dead: bool + gr_context: Optional[Context] + + def __init__(self, fn: Callable[..., Any], driver: greenlet): ... + + def throw(self, *arg: Any) -> Any: + return None + + def switch(self, value: Any) -> Any: + return None + + def getcurrent() -> greenlet: ... + +else: + from greenlet import getcurrent + from greenlet import greenlet + + +# If greenlet.gr_context is present in current version of greenlet, +# it will be set with the current context on creation. +# Refs: https://github.com/python-greenlet/greenlet/pull/198 +_has_gr_context = hasattr(getcurrent(), "gr_context") + + +def is_exit_exception(e: BaseException) -> bool: + # note asyncio.CancelledError is already BaseException + # so was an exit exception in any case + return not isinstance(e, Exception) or isinstance( + e, (asyncio.TimeoutError, asyncio.CancelledError) + ) + + +# implementation based on snaury gist at +# https://gist.github.com/snaury/202bf4f22c41ca34e56297bae5f33fef +# Issue for context: https://github.com/python-greenlet/greenlet/issues/173 + + +class _AsyncIoGreenlet(greenlet): + dead: bool + + __sqlalchemy_greenlet_provider__ = True + + def __init__(self, fn: Callable[..., Any], driver: greenlet): + greenlet.__init__(self, fn, driver) + if _has_gr_context: + self.gr_context = driver.gr_context + + +_T_co = TypeVar("_T_co", covariant=True) + +if TYPE_CHECKING: + + def iscoroutine( + awaitable: Awaitable[_T_co], + ) -> TypeGuard[Coroutine[Any, Any, _T_co]]: ... + +else: + iscoroutine = asyncio.iscoroutine + + +def _safe_cancel_awaitable(awaitable: Awaitable[Any]) -> None: + # https://docs.python.org/3/reference/datamodel.html#coroutine.close + + if iscoroutine(awaitable): + awaitable.close() + + +def in_greenlet() -> bool: + current = getcurrent() + return getattr(current, "__sqlalchemy_greenlet_provider__", False) + + +def await_only(awaitable: Awaitable[_T]) -> _T: + """Awaits an async function in a sync method. + + The sync method must be inside a :func:`greenlet_spawn` context. + :func:`await_only` calls cannot be nested. + + :param awaitable: The coroutine to call. + + """ + # this is called in the context greenlet while running fn + current = getcurrent() + if not getattr(current, "__sqlalchemy_greenlet_provider__", False): + _safe_cancel_awaitable(awaitable) + + raise exc.MissingGreenlet( + "greenlet_spawn has not been called; can't call await_only() " + "here. Was IO attempted in an unexpected place?" + ) + + # returns the control to the driver greenlet passing it + # a coroutine to run. Once the awaitable is done, the driver greenlet + # switches back to this greenlet with the result of awaitable that is + # then returned to the caller (or raised as error) + return current.parent.switch(awaitable) # type: ignore[no-any-return,attr-defined] # noqa: E501 + + +def await_fallback(awaitable: Awaitable[_T]) -> _T: + """Awaits an async function in a sync method. + + The sync method must be inside a :func:`greenlet_spawn` context. + :func:`await_fallback` calls cannot be nested. + + :param awaitable: The coroutine to call. + + .. deprecated:: 2.0.24 The ``await_fallback()`` function will be removed + in SQLAlchemy 2.1. Use :func:`_util.await_only` instead, running the + function / program / etc. within a top-level greenlet that is set up + using :func:`_util.greenlet_spawn`. + + """ + + # this is called in the context greenlet while running fn + current = getcurrent() + if not getattr(current, "__sqlalchemy_greenlet_provider__", False): + loop = get_event_loop() + if loop.is_running(): + _safe_cancel_awaitable(awaitable) + + raise exc.MissingGreenlet( + "greenlet_spawn has not been called and asyncio event " + "loop is already running; can't call await_fallback() here. " + "Was IO attempted in an unexpected place?" + ) + return loop.run_until_complete(awaitable) + + return current.parent.switch(awaitable) # type: ignore[no-any-return,attr-defined] # noqa: E501 + + +async def greenlet_spawn( + fn: Callable[..., _T], + *args: Any, + _require_await: bool = False, + **kwargs: Any, +) -> _T: + """Runs a sync function ``fn`` in a new greenlet. + + The sync function can then use :func:`await_only` to wait for async + functions. + + :param fn: The sync callable to call. + :param \\*args: Positional arguments to pass to the ``fn`` callable. + :param \\*\\*kwargs: Keyword arguments to pass to the ``fn`` callable. + """ + + result: Any + context = _AsyncIoGreenlet(fn, getcurrent()) + # runs the function synchronously in gl greenlet. If the execution + # is interrupted by await_only, context is not dead and result is a + # coroutine to wait. If the context is dead the function has + # returned, and its result can be returned. + switch_occurred = False + result = context.switch(*args, **kwargs) + while not context.dead: + switch_occurred = True + try: + # wait for a coroutine from await_only and then return its + # result back to it. + value = await result + except BaseException: + # this allows an exception to be raised within + # the moderated greenlet so that it can continue + # its expected flow. + result = context.throw(*sys.exc_info()) + else: + result = context.switch(value) + + if _require_await and not switch_occurred: + raise exc.AwaitRequired( + "The current operation required an async execution but none was " + "detected. This will usually happen when using a non compatible " + "DBAPI driver. Please ensure that an async DBAPI is used." + ) + return result # type: ignore[no-any-return] + + +class AsyncAdaptedLock: + @memoized_property + def mutex(self) -> asyncio.Lock: + # there should not be a race here for coroutines creating the + # new lock as we are not using await, so therefore no concurrency + return asyncio.Lock() + + def __enter__(self) -> bool: + # await is used to acquire the lock only after the first calling + # coroutine has created the mutex. + return await_fallback(self.mutex.acquire()) + + def __exit__(self, *arg: Any, **kw: Any) -> None: + self.mutex.release() + + +def get_event_loop() -> asyncio.AbstractEventLoop: + """vendor asyncio.get_event_loop() for python 3.7 and above. + + Python 3.10 deprecates get_event_loop() as a standalone. + + """ + try: + return asyncio.get_running_loop() + except RuntimeError: + # avoid "During handling of the above exception, another exception..." + pass + return asyncio.get_event_loop_policy().get_event_loop() + + +if not TYPE_CHECKING and py311: + _Runner = asyncio.Runner +else: + + class _Runner: + """Runner implementation for test only""" + + _loop: Union[None, asyncio.AbstractEventLoop, Literal[False]] + + def __init__(self) -> None: + self._loop = None + + def __enter__(self) -> Self: + self._lazy_init() + return self + + def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: + self.close() + + def close(self) -> None: + if self._loop: + try: + self._loop.run_until_complete( + self._loop.shutdown_asyncgens() + ) + finally: + self._loop.close() + self._loop = False + + def get_loop(self) -> asyncio.AbstractEventLoop: + """Return embedded event loop.""" + self._lazy_init() + assert self._loop + return self._loop + + def run(self, coro: Coroutine[Any, Any, _T]) -> _T: + self._lazy_init() + assert self._loop + return self._loop.run_until_complete(coro) + + def _lazy_init(self) -> None: + if self._loop is False: + raise RuntimeError("Runner is closed") + if self._loop is None: + self._loop = asyncio.new_event_loop() diff --git a/venv/Lib/site-packages/sqlalchemy/util/_has_cy.py b/venv/Lib/site-packages/sqlalchemy/util/_has_cy.py new file mode 100644 index 0000000..9bd53f8 --- /dev/null +++ b/venv/Lib/site-packages/sqlalchemy/util/_has_cy.py @@ -0,0 +1,40 @@ +# util/_has_cy.py +# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: https://www.opensource.org/licenses/mit-license.php +# mypy: ignore-errors + +import os +import typing + + +def _import_cy_extensions(): + # all cython extension extension modules are treated as optional by the + # setup, so to ensure that all are compiled, all should be imported here + from ..cyextension import collections + from ..cyextension import immutabledict + from ..cyextension import processors + from ..cyextension import resultproxy + from ..cyextension import util + + return (collections, immutabledict, processors, resultproxy, util) + + +_CYEXTENSION_MSG: str +if not typing.TYPE_CHECKING: + if os.environ.get("DISABLE_SQLALCHEMY_CEXT_RUNTIME"): + HAS_CYEXTENSION = False + _CYEXTENSION_MSG = "DISABLE_SQLALCHEMY_CEXT_RUNTIME is set" + else: + try: + _import_cy_extensions() + except ImportError as err: + HAS_CYEXTENSION = False + _CYEXTENSION_MSG = str(err) + else: + _CYEXTENSION_MSG = "Loaded" + HAS_CYEXTENSION = True +else: + HAS_CYEXTENSION = False diff --git a/venv/Lib/site-packages/sqlalchemy/util/_py_collections.py b/venv/Lib/site-packages/sqlalchemy/util/_py_collections.py new file mode 100644 index 0000000..bab0dbd --- /dev/null +++ b/venv/Lib/site-packages/sqlalchemy/util/_py_collections.py @@ -0,0 +1,541 @@ +# util/_py_collections.py +# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: https://www.opensource.org/licenses/mit-license.php +# mypy: allow-untyped-defs, allow-untyped-calls + +from __future__ import annotations + +from itertools import filterfalse +from typing import AbstractSet +from typing import Any +from typing import Callable +from typing import cast +from typing import Collection +from typing import Dict +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Mapping +from typing import NoReturn +from typing import Optional +from typing import Set +from typing import Tuple +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +from ..util.typing import Self + +_T = TypeVar("_T", bound=Any) +_S = TypeVar("_S", bound=Any) +_KT = TypeVar("_KT", bound=Any) +_VT = TypeVar("_VT", bound=Any) + + +class ReadOnlyContainer: + __slots__ = () + + def _readonly(self, *arg: Any, **kw: Any) -> NoReturn: + raise TypeError( + "%s object is immutable and/or readonly" % self.__class__.__name__ + ) + + def _immutable(self, *arg: Any, **kw: Any) -> NoReturn: + raise TypeError("%s object is immutable" % self.__class__.__name__) + + def __delitem__(self, key: Any) -> NoReturn: + self._readonly() + + def __setitem__(self, key: Any, value: Any) -> NoReturn: + self._readonly() + + def __setattr__(self, key: str, value: Any) -> NoReturn: + self._readonly() + + +class ImmutableDictBase(ReadOnlyContainer, Dict[_KT, _VT]): + if TYPE_CHECKING: + + def __new__(cls, *args: Any) -> Self: ... + + def __init__(cls, *args: Any): ... + + def _readonly(self, *arg: Any, **kw: Any) -> NoReturn: + self._immutable() + + def clear(self) -> NoReturn: + self._readonly() + + def pop(self, key: Any, default: Optional[Any] = None) -> NoReturn: + self._readonly() + + def popitem(self) -> NoReturn: + self._readonly() + + def setdefault(self, key: Any, default: Optional[Any] = None) -> NoReturn: + self._readonly() + + def update(self, *arg: Any, **kw: Any) -> NoReturn: + self._readonly() + + +class immutabledict(ImmutableDictBase[_KT, _VT]): + def __new__(cls, *args): + new = ImmutableDictBase.__new__(cls) + dict.__init__(new, *args) + return new + + def __init__( + self, *args: Union[Mapping[_KT, _VT], Iterable[Tuple[_KT, _VT]]] + ): + pass + + def __reduce__(self): + return immutabledict, (dict(self),) + + def union( + self, __d: Optional[Mapping[_KT, _VT]] = None + ) -> immutabledict[_KT, _VT]: + if not __d: + return self + + new = ImmutableDictBase.__new__(self.__class__) + dict.__init__(new, self) + dict.update(new, __d) + return new + + def _union_w_kw( + self, __d: Optional[Mapping[_KT, _VT]] = None, **kw: _VT + ) -> immutabledict[_KT, _VT]: + # not sure if C version works correctly w/ this yet + if not __d and not kw: + return self + + new = ImmutableDictBase.__new__(self.__class__) + dict.__init__(new, self) + if __d: + dict.update(new, __d) + dict.update(new, kw) + return new + + def merge_with( + self, *dicts: Optional[Mapping[_KT, _VT]] + ) -> immutabledict[_KT, _VT]: + new = None + for d in dicts: + if d: + if new is None: + new = ImmutableDictBase.__new__(self.__class__) + dict.__init__(new, self) + dict.update(new, d) + if new is None: + return self + + return new + + def __repr__(self) -> str: + return "immutabledict(%s)" % dict.__repr__(self) + + # PEP 584 + def __ior__(self, __value: Any) -> NoReturn: # type: ignore + self._readonly() + + def __or__( # type: ignore[override] + self, __value: Mapping[_KT, _VT] + ) -> immutabledict[_KT, _VT]: + return immutabledict( + super().__or__(__value), # type: ignore[call-overload,operator,unused-ignore] # noqa: E501 + ) + + def __ror__( # type: ignore[override] + self, __value: Mapping[_KT, _VT] + ) -> immutabledict[_KT, _VT]: + return immutabledict( + super().__ror__(__value), # type: ignore[call-overload,operator,unused-ignore] # noqa: E501 + ) + + +class OrderedSet(Set[_T]): + __slots__ = ("_list",) + + _list: List[_T] + + def __init__(self, d: Optional[Iterable[_T]] = None) -> None: + if d is not None: + self._list = unique_list(d) + super().update(self._list) + else: + self._list = [] + + def copy(self) -> OrderedSet[_T]: + cp = self.__class__() + cp._list = self._list.copy() + set.update(cp, cp._list) + return cp + + def add(self, element: _T) -> None: + if element not in self: + self._list.append(element) + super().add(element) + + def remove(self, element: _T) -> None: + super().remove(element) + self._list.remove(element) + + def pop(self) -> _T: + try: + value = self._list.pop() + except IndexError: + raise KeyError("pop from an empty set") from None + super().remove(value) + return value + + def insert(self, pos: int, element: _T) -> None: + if element not in self: + self._list.insert(pos, element) + super().add(element) + + def discard(self, element: _T) -> None: # type: ignore[override,unused-ignore] # noqa: E501 + if element in self: + self._list.remove(element) + super().remove(element) + + def clear(self) -> None: + super().clear() + self._list = [] + + def __getitem__(self, key: int) -> _T: + return self._list[key] + + def __iter__(self) -> Iterator[_T]: + return iter(self._list) + + def __add__(self, other: Iterator[_T]) -> OrderedSet[_T]: + return self.union(other) + + def __repr__(self) -> str: + return "%s(%r)" % (self.__class__.__name__, self._list) + + __str__ = __repr__ + + def update(self, *iterables: Iterable[_T]) -> None: + for iterable in iterables: + for e in iterable: + if e not in self: + self._list.append(e) + super().add(e) + + def __ior__(self, other: AbstractSet[_S]) -> OrderedSet[Union[_T, _S]]: + self.update(other) + return self + + def union(self, *other: Iterable[_S]) -> OrderedSet[Union[_T, _S]]: + result: OrderedSet[Union[_T, _S]] = self.copy() + result.update(*other) + return result + + def __or__(self, other: AbstractSet[_S]) -> OrderedSet[Union[_T, _S]]: + return self.union(other) + + def intersection(self, *other: Iterable[Any]) -> OrderedSet[_T]: + other_set: Set[Any] = set() + other_set.update(*other) + return self.__class__(a for a in self if a in other_set) + + def __and__(self, other: AbstractSet[object]) -> OrderedSet[_T]: + return self.intersection(other) + + def symmetric_difference(self, other: Iterable[_T]) -> OrderedSet[_T]: # type: ignore[override,unused-ignore] # noqa: E501 + collection: Collection[_T] + if isinstance(other, set): + collection = other_set = other + elif isinstance(other, Collection): + collection = other + other_set = set(other) + else: + collection = list(other) + other_set = set(collection) + result = self.__class__(a for a in self if a not in other_set) + result.update(a for a in collection if a not in self) + return result + + def __xor__(self, other: AbstractSet[_S]) -> OrderedSet[Union[_T, _S]]: + return cast(OrderedSet[Union[_T, _S]], self).symmetric_difference( + other + ) + + def difference(self, *other: Iterable[Any]) -> OrderedSet[_T]: + other_set = super().difference(*other) + return self.__class__(a for a in self._list if a in other_set) + + def __sub__(self, other: AbstractSet[Optional[_T]]) -> OrderedSet[_T]: # type: ignore[override,unused-ignore] # noqa: E501 + return self.difference(other) + + def intersection_update(self, *other: Iterable[Any]) -> None: + super().intersection_update(*other) + self._list = [a for a in self._list if a in self] + + def __iand__(self, other: AbstractSet[object]) -> OrderedSet[_T]: + self.intersection_update(other) + return self + + def symmetric_difference_update(self, other: Iterable[Any]) -> None: + collection = other if isinstance(other, Collection) else list(other) + super().symmetric_difference_update(collection) + self._list = [a for a in self._list if a in self] + self._list += [a for a in collection if a in self] + + def __ixor__(self, other: AbstractSet[_S]) -> OrderedSet[Union[_T, _S]]: + self.symmetric_difference_update(other) + return cast(OrderedSet[Union[_T, _S]], self) + + def difference_update(self, *other: Iterable[Any]) -> None: + super().difference_update(*other) + self._list = [a for a in self._list if a in self] + + def __isub__(self, other: AbstractSet[Optional[_T]]) -> OrderedSet[_T]: # type: ignore # noqa: E501 + self.difference_update(other) + return self + + +class IdentitySet: + """A set that considers only object id() for uniqueness. + + This strategy has edge cases for builtin types- it's possible to have + two 'foo' strings in one of these sets, for example. Use sparingly. + + """ + + _members: Dict[int, Any] + + def __init__(self, iterable: Optional[Iterable[Any]] = None): + self._members = dict() + if iterable: + self.update(iterable) + + def add(self, value: Any) -> None: + self._members[id(value)] = value + + def __contains__(self, value: Any) -> bool: + return id(value) in self._members + + def remove(self, value: Any) -> None: + del self._members[id(value)] + + def discard(self, value: Any) -> None: + try: + self.remove(value) + except KeyError: + pass + + def pop(self) -> Any: + try: + pair = self._members.popitem() + return pair[1] + except KeyError: + raise KeyError("pop from an empty set") + + def clear(self) -> None: + self._members.clear() + + def __eq__(self, other: Any) -> bool: + if isinstance(other, IdentitySet): + return self._members == other._members + else: + return False + + def __ne__(self, other: Any) -> bool: + if isinstance(other, IdentitySet): + return self._members != other._members + else: + return True + + def issubset(self, iterable: Iterable[Any]) -> bool: + if isinstance(iterable, self.__class__): + other = iterable + else: + other = self.__class__(iterable) + + if len(self) > len(other): + return False + for m in filterfalse( + other._members.__contains__, iter(self._members.keys()) + ): + return False + return True + + def __le__(self, other: Any) -> bool: + if not isinstance(other, IdentitySet): + return NotImplemented + return self.issubset(other) + + def __lt__(self, other: Any) -> bool: + if not isinstance(other, IdentitySet): + return NotImplemented + return len(self) < len(other) and self.issubset(other) + + def issuperset(self, iterable: Iterable[Any]) -> bool: + if isinstance(iterable, self.__class__): + other = iterable + else: + other = self.__class__(iterable) + + if len(self) < len(other): + return False + + for m in filterfalse( + self._members.__contains__, iter(other._members.keys()) + ): + return False + return True + + def __ge__(self, other: Any) -> bool: + if not isinstance(other, IdentitySet): + return NotImplemented + return self.issuperset(other) + + def __gt__(self, other: Any) -> bool: + if not isinstance(other, IdentitySet): + return NotImplemented + return len(self) > len(other) and self.issuperset(other) + + def union(self, iterable: Iterable[Any]) -> IdentitySet: + result = self.__class__() + members = self._members + result._members.update(members) + result._members.update((id(obj), obj) for obj in iterable) + return result + + def __or__(self, other: Any) -> IdentitySet: + if not isinstance(other, IdentitySet): + return NotImplemented + return self.union(other) + + def update(self, iterable: Iterable[Any]) -> None: + self._members.update((id(obj), obj) for obj in iterable) + + def __ior__(self, other: Any) -> IdentitySet: + if not isinstance(other, IdentitySet): + return NotImplemented + self.update(other) + return self + + def difference(self, iterable: Iterable[Any]) -> IdentitySet: + result = self.__new__(self.__class__) + other: Collection[Any] + + if isinstance(iterable, self.__class__): + other = iterable._members + else: + other = {id(obj) for obj in iterable} + result._members = { + k: v for k, v in self._members.items() if k not in other + } + return result + + def __sub__(self, other: IdentitySet) -> IdentitySet: + if not isinstance(other, IdentitySet): + return NotImplemented + return self.difference(other) + + def difference_update(self, iterable: Iterable[Any]) -> None: + self._members = self.difference(iterable)._members + + def __isub__(self, other: IdentitySet) -> IdentitySet: + if not isinstance(other, IdentitySet): + return NotImplemented + self.difference_update(other) + return self + + def intersection(self, iterable: Iterable[Any]) -> IdentitySet: + result = self.__new__(self.__class__) + + other: Collection[Any] + + if isinstance(iterable, self.__class__): + other = iterable._members + else: + other = {id(obj) for obj in iterable} + result._members = { + k: v for k, v in self._members.items() if k in other + } + return result + + def __and__(self, other: IdentitySet) -> IdentitySet: + if not isinstance(other, IdentitySet): + return NotImplemented + return self.intersection(other) + + def intersection_update(self, iterable: Iterable[Any]) -> None: + self._members = self.intersection(iterable)._members + + def __iand__(self, other: IdentitySet) -> IdentitySet: + if not isinstance(other, IdentitySet): + return NotImplemented + self.intersection_update(other) + return self + + def symmetric_difference(self, iterable: Iterable[Any]) -> IdentitySet: + result = self.__new__(self.__class__) + if isinstance(iterable, self.__class__): + other = iterable._members + else: + other = {id(obj): obj for obj in iterable} + result._members = { + k: v for k, v in self._members.items() if k not in other + } + result._members.update( + (k, v) for k, v in other.items() if k not in self._members + ) + return result + + def __xor__(self, other: IdentitySet) -> IdentitySet: + if not isinstance(other, IdentitySet): + return NotImplemented + return self.symmetric_difference(other) + + def symmetric_difference_update(self, iterable: Iterable[Any]) -> None: + self._members = self.symmetric_difference(iterable)._members + + def __ixor__(self, other: IdentitySet) -> IdentitySet: + if not isinstance(other, IdentitySet): + return NotImplemented + self.symmetric_difference(other) + return self + + def copy(self) -> IdentitySet: + result = self.__new__(self.__class__) + result._members = self._members.copy() + return result + + __copy__ = copy + + def __len__(self) -> int: + return len(self._members) + + def __iter__(self) -> Iterator[Any]: + return iter(self._members.values()) + + def __hash__(self) -> NoReturn: + raise TypeError("set objects are unhashable") + + def __repr__(self) -> str: + return "%s(%r)" % (type(self).__name__, list(self._members.values())) + + +def unique_list( + seq: Iterable[_T], hashfunc: Optional[Callable[[_T], int]] = None +) -> List[_T]: + seen: Set[Any] = set() + seen_add = seen.add + if not hashfunc: + return [x for x in seq if x not in seen and not seen_add(x)] + else: + return [ + x + for x in seq + if hashfunc(x) not in seen and not seen_add(hashfunc(x)) + ]