This commit is contained in:
macbook_max
2026-10-15 20:36:59 +08:00
commit 6bf949949c
7923 changed files with 1369582 additions and 0 deletions
@@ -0,0 +1,166 @@
# util/__init__.py
# Copyright (C) 2005-2026 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
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 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 py312 as py312
from .compat import py313 as py313
from .compat import py314 as py314
from .compat import pypy as pypy
from .compat import win32 as win32
from .concurrency import await_ as await_
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 dunders_re as dunders_re
from .langhelpers import ellipses_string as ellipses_string
from .langhelpers import EnsureKWArg as EnsureKWArg
from .langhelpers import exec_code_in_env as exec_code_in_env
from .langhelpers import FastIntFlag as FastIntFlag
from .langhelpers import find_matching_paren as find_matching_paren
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 GenericRepr as GenericRepr
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 only_once as only_once
from .langhelpers import (
parse_user_argument_for_enum as parse_user_argument_for_enum,
)
from .langhelpers import (
parse_version_from_metadata as parse_version_from_metadata,
)
from .langhelpers import parse_version_string as parse_version_string
from .langhelpers import PluginLoader as PluginLoader
from .langhelpers import quoted_token_parser as quoted_token_parser
from .langhelpers import restore_annotations as restore_annotations
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 strip_outer_parens as strip_outer_parens
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 VersionInfo as VersionInfo
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
@@ -0,0 +1,688 @@
# util/_collections.py
# Copyright (C) 2005-2026 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
# 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 Literal
from typing import Mapping
from typing import NoReturn
from typing import Optional
from typing import overload
from typing import Protocol
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 ._collections_cy import IdentitySet as IdentitySet
from ._collections_cy import OrderedSet as OrderedSet
from ._collections_cy import unique_list as unique_list # noqa: F401
from ._immutabledict_cy import immutabledict as immutabledict
from ._immutabledict_cy import ImmutableDictBase as ImmutableDictBase
from ._immutabledict_cy import ReadOnlyContainer as ReadOnlyContainer
from .typing import is_non_string_iterable
_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: FacadeDict[Any, Any] = 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]()
OrderedIdentitySet = IdentitySet
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[return-value]
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[misc]
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:]
@@ -0,0 +1,8 @@
# util/_collections_cy.pxd
# Copyright (C) 2005-2024 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
cdef unsigned long long _get_id(item: object)
@@ -0,0 +1,516 @@
# util/_collections_cy.py
# Copyright (C) 2010-2026 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
# mypy: disable-error-code="misc, no-any-return, no-untyped-def, override"
# mypy: disable-error-code="untyped-decorator"
from __future__ import annotations
from typing import AbstractSet
from typing import Any
from typing import Dict
from typing import Hashable
from typing import Iterable
from typing import Iterator
from typing import List
from typing import NoReturn
from typing import Optional
from typing import Set
from typing import Tuple
from typing import TypeVar
from typing import Union
from .typing import Self
# START GENERATED CYTHON IMPORT
# This section is automatically generated by the script tools/cython_imports.py
try:
# NOTE: the cython compiler needs this "import cython" in the file, it
# can't be only "from sqlalchemy.util import cython" with the fallback
# in that module
import cython
except ModuleNotFoundError:
from sqlalchemy.util import cython
def _is_compiled() -> bool:
"""Utility function to indicate if this module is compiled or not."""
return cython.compiled # type: ignore[no-any-return,unused-ignore]
# END GENERATED CYTHON IMPORT
_T = TypeVar("_T")
_S = TypeVar("_S")
@cython.ccall
def unique_list(seq: Iterable[_T]) -> List[_T]:
# this version seems somewhat faster for smaller sizes, but it's
# significantly slower on larger sizes
# w = {x:None for x in seq}
# return PyDict_Keys(w) if cython.compiled else list(w)
if cython.compiled:
seen: Set[_T] = set()
return [x for x in seq if x not in seen and not set.add(seen, x)]
else:
return list(dict.fromkeys(seq))
# In case passing an hashfunc is required in the future two version were
# tested:
# - this version is faster but returns the *last* element matching the
# hash.
# from cython.cimports.cpython.dict import PyDict_Values
# w: dict = {hashfunc(x): x for x in seq}
# return PyDict_Values(w) if cython.compiled else list(w.values())
# - this version is slower but returns the *first* element matching the
# hash.
# seen: set = set()
# res: list = []
# for x in seq:
# h = hashfunc(x)
# if h not in seen:
# res.append(x)
# seen.add(h)
# return res
@cython.cclass
class OrderedSet(Set[_T]):
"""A set implementation that maintains insertion order."""
__slots__ = ("_list",)
_list: List[_T]
@classmethod
def __class_getitem__(cls, key: Any) -> type[Self]:
return cls
def __init__(self, d: Optional[Iterable[_T]] = None) -> None:
if d is not None:
if isinstance(d, set) or isinstance(d, dict):
self._list = list(d)
else:
self._list = unique_list(d)
set.__init__(self, self._list)
else:
self._list = []
set.__init__(self)
def copy(self) -> OrderedSet[_T]:
return self._from_list(list(self._list))
@cython.final
@cython.cfunc
@cython.inline
def _from_list(self, new_list: List[_T]) -> OrderedSet: # type: ignore[type-arg] # noqa: E501
new: OrderedSet = OrderedSet.__new__(OrderedSet) # type: ignore[type-arg] # noqa: E501
new._list = new_list
set.update(new, new_list)
return new
def add(self, element: _T, /) -> None:
if element not in self:
self._list.append(element)
set.add(self, element)
def remove(self, element: _T, /) -> None:
# set.remove will raise if element is not in self
set.remove(self, 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
set.remove(self, value)
return value
def insert(self, pos: cython.Py_ssize_t, element: _T, /) -> None:
if element not in self:
self._list.insert(pos, element)
set.add(self, element)
def discard(self, element: _T, /) -> None:
if element in self:
set.remove(self, element)
self._list.remove(element)
def clear(self) -> None:
set.clear(self)
self._list = []
def __getitem__(self, key: cython.Py_ssize_t) -> _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__
# @cython.ccall # cdef function cannot have star argument
def update(self, *iterables: Iterable[_T]) -> None:
for iterable in iterables:
for element in iterable:
# inline of add. mainly for python, since for cython we
# could create an @cfunc @inline _add function that would
# perform the same
if element not in self:
self._list.append(element)
set.add(self, element)
def __ior__(
self: OrderedSet[Union[_T, _S]], iterable: AbstractSet[_S]
) -> OrderedSet[Union[_T, _S]]:
self.update(iterable)
return self
# @cython.ccall # cdef function cannot have star argument
def union(self, *other: Iterable[_S]) -> OrderedSet[Union[_T, _S]]:
result: OrderedSet[Union[_T, _S]] = self._from_list(list(self._list))
result.update(*other)
return result
def __or__(self, other: AbstractSet[_S]) -> OrderedSet[Union[_T, _S]]:
return self.union(other)
# @cython.ccall # cdef function cannot have star argument
def intersection(self, *other: Iterable[Hashable]) -> OrderedSet[_T]:
other_set: Set[Any] = set.intersection(self, *other)
return self._from_list([a for a in self._list if a in other_set])
def __and__(self, other: AbstractSet[Hashable]) -> OrderedSet[_T]:
return self.intersection(other)
@cython.ccall
@cython.annotation_typing(False) # avoid cython crash from generic return
def symmetric_difference(
self, other: Iterable[_S], /
) -> OrderedSet[Union[_T, _S]]:
collection: Iterable[Any]
other_set: Set[_S]
if isinstance(other, set):
other_set = cython.cast(set, other)
collection = other_set
elif hasattr(other, "__len__"):
collection = other
other_set = set(other)
else:
collection = list(other)
other_set = set(collection)
result: OrderedSet[Union[_T, _S]] = self._from_list(
[a for a in self._list 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 self.symmetric_difference(other)
# @cython.ccall # cdef function cannot have star argument
def difference(self, *other: Iterable[Hashable]) -> OrderedSet[_T]:
other_set: Set[Any] = set.difference(self, *other)
return self._from_list([a for a in self._list if a in other_set])
def __sub__(self, other: AbstractSet[Hashable]) -> OrderedSet[_T]:
return self.difference(other)
# @cython.ccall # cdef function cannot have star argument
def intersection_update(self, *other: Iterable[Hashable]) -> None:
set.intersection_update(self, *other)
self._list = [a for a in self._list if a in self]
def __iand__(self, other: AbstractSet[Hashable]) -> OrderedSet[_T]:
self.intersection_update(other)
return self
@cython.ccall
@cython.annotation_typing(False) # avoid cython crash from generic return
def symmetric_difference_update(self, other: Iterable[_T], /) -> None:
collection = other if hasattr(other, "__len__") else list(other)
set.symmetric_difference_update(self, 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: OrderedSet[Union[_T, _S]], other: AbstractSet[_S]
) -> OrderedSet[Union[_T, _S]]:
self.symmetric_difference_update(other)
return self
# @cython.ccall # cdef function cannot have star argument
def difference_update(self, *other: Iterable[Hashable]) -> None:
set.difference_update(self, *other)
self._list = [a for a in self._list if a in self]
def __isub__(self, other: AbstractSet[Hashable]) -> OrderedSet[_T]:
self.difference_update(other)
return self
if cython.compiled:
@cython.cfunc
@cython.inline
def _get_id(item: object, /) -> cython.ulonglong:
return cython.cast(
cython.ulonglong,
cython.cast(cython.pointer(cython.void), item),
)
else:
_get_id = id
@cython.cclass
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.
"""
__slots__ = ("_members",)
_members: Dict[int, Any]
def __init__(self, iterable: Optional[Iterable[Any]] = None):
# the code assumes this class is ordered
self._members = {}
if iterable:
self.update(iterable)
def add(self, value: Any, /) -> None:
self._members[_get_id(value)] = value
def __contains__(self, value) -> bool:
return _get_id(value) in self._members
@cython.ccall
def remove(self, value: Any, /):
del self._members[_get_id(value)]
def discard(self, value, /) -> None:
try:
self.remove(value)
except KeyError:
pass
def pop(self) -> Any:
pair: Tuple[Any, 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:
other_: IdentitySet
if isinstance(other, IdentitySet):
other_ = other
return self._members == other_._members
else:
return False
def __ne__(self, other: Any) -> bool:
other_: IdentitySet
if isinstance(other, IdentitySet):
other_ = other
return self._members != other_._members
else:
return True
@cython.ccall
def issubset(self, iterable: Iterable[Any], /) -> cython.bint:
other: IdentitySet
if isinstance(iterable, IdentitySet):
other = iterable
else:
other = self.__class__(iterable)
return self._members.keys() <= other._members.keys()
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)
@cython.ccall
def issuperset(self, iterable: Iterable[Any], /) -> cython.bint:
other: IdentitySet
if isinstance(iterable, IdentitySet):
other = iterable
else:
other = self.__class__(iterable)
return self._members.keys() >= other._members.keys()
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)
@cython.ccall
def union(self, iterable: Iterable[Any], /) -> IdentitySet:
result: IdentitySet = self.__class__()
result._members.update(self._members)
result.update(iterable)
return result
def __or__(self, other: Any) -> IdentitySet:
if not isinstance(other, IdentitySet):
return NotImplemented
return self.union(other)
@cython.ccall
def update(self, iterable: Iterable[Any], /):
members: Dict[int, Any] = self._members
if isinstance(iterable, IdentitySet):
members.update(cython.cast(IdentitySet, iterable)._members)
else:
for obj in iterable:
members[_get_id(obj)] = obj
def __ior__(self, other: Any) -> IdentitySet:
if not isinstance(other, IdentitySet):
return NotImplemented
self.update(other)
return self
@cython.ccall
def difference(self, iterable: Iterable[Any], /) -> IdentitySet:
result: IdentitySet = self.__new__(self.__class__)
if isinstance(iterable, IdentitySet):
other = cython.cast(IdentitySet, iterable)._members.keys()
else:
other = {_get_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:
@cython.ccall
def difference_update(self, iterable: Iterable[Any], /):
other: IdentitySet = self.difference(iterable)
self._members = other._members
def __isub__(self, other: IdentitySet) -> IdentitySet:
if not isinstance(other, IdentitySet):
return NotImplemented
self.difference_update(other)
return self
@cython.ccall
def intersection(self, iterable: Iterable[Any], /) -> IdentitySet:
result: IdentitySet = self.__new__(self.__class__)
if isinstance(iterable, IdentitySet):
other = cython.cast(IdentitySet, iterable)._members
else:
other = {_get_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):
if not isinstance(other, IdentitySet):
return NotImplemented
return self.intersection(other)
# def intersection_update(self, iterable: Iterable[Any]) -> None:
@cython.ccall
def intersection_update(self, iterable: Iterable[Any], /):
other: IdentitySet = self.intersection(iterable)
self._members = other._members
def __iand__(self, other: IdentitySet) -> IdentitySet:
if not isinstance(other, IdentitySet):
return NotImplemented
self.intersection_update(other)
return self
@cython.ccall
def symmetric_difference(self, iterable: Iterable[Any], /) -> IdentitySet:
result: IdentitySet = self.__new__(self.__class__)
other: Dict[int, Any]
if isinstance(iterable, IdentitySet):
other = cython.cast(IdentitySet, iterable)._members
else:
other = {_get_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:
@cython.ccall
def symmetric_difference_update(self, iterable: Iterable[Any], /):
other: IdentitySet = self.symmetric_difference(iterable)
self._members = other._members
def __ixor__(self, other: IdentitySet) -> IdentitySet:
if not isinstance(other, IdentitySet):
return NotImplemented
self.symmetric_difference(other)
return self
@cython.ccall
def copy(self) -> IdentitySet:
cp: IdentitySet = self.__new__(self.__class__)
cp._members = self._members.copy()
return cp
def __copy__(self) -> IdentitySet:
return self.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)" % (
self.__class__.__name__,
list(self._members.values()),
)
@@ -0,0 +1,48 @@
# util/_has_cython.py
# Copyright (C) 2005-2026 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
# mypy: ignore-errors
import typing
def _all_cython_modules():
"""Returns all modules that can be compiled using cython.
Call ``_is_compiled()`` to check if the module is compiled or not.
"""
from . import _collections_cy
from . import _immutabledict_cy
from ..engine import _processors_cy
from ..engine import _result_cy
from ..engine import _row_cy
from ..engine import _util_cy as engine_util
from ..sql import _cache_key_cy as sql_cache_key
from ..sql import _util_cy as sql_util
return (
_collections_cy,
_immutabledict_cy,
_processors_cy,
_result_cy,
_row_cy,
engine_util,
sql_cache_key,
sql_util,
)
_CYEXTENSION_MSG: str
if not typing.TYPE_CHECKING:
HAS_CYEXTENSION = all(m._is_compiled() for m in _all_cython_modules())
if HAS_CYEXTENSION:
_CYEXTENSION_MSG = "Loaded"
else:
_CYEXTENSION_MSG = ", ".join(
m.__name__ for m in _all_cython_modules() if not m._is_compiled()
)
_CYEXTENSION_MSG = f"Modules {_CYEXTENSION_MSG} are not compiled"
else:
HAS_CYEXTENSION = False
@@ -0,0 +1,240 @@
# util/_immutabledict_cy.py
# Copyright (C) 2010-2026 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
# mypy: disable-error-code="misc, arg-type, type-arg, untyped-decorator"
from __future__ import annotations
from typing import Any
from typing import Dict
from typing import Hashable
from typing import Literal
from typing import Mapping
from typing import NoReturn
from typing import Optional
from typing import TypeVar
from .typing import Self
# START GENERATED CYTHON IMPORT
# This section is automatically generated by the script tools/cython_imports.py
try:
# NOTE: the cython compiler needs this "import cython" in the file, it
# can't be only "from sqlalchemy.util import cython" with the fallback
# in that module
import cython
except ModuleNotFoundError:
from sqlalchemy.util import cython
def _is_compiled() -> bool:
"""Utility function to indicate if this module is compiled or not."""
return cython.compiled # type: ignore[no-any-return,unused-ignore]
# END GENERATED CYTHON IMPORT
if cython.compiled:
from cython.cimports.cpython.dict import PyDict_Update
else:
PyDict_Update = dict.update
def _immutable_fn(obj: object) -> NoReturn:
raise TypeError(f"{obj.__class__.__name__} object is immutable")
class ReadOnlyContainer:
__slots__ = ()
def _readonly(self) -> NoReturn:
raise TypeError(
f"{self.__class__.__name__} object is immutable and/or readonly"
)
def __delitem__(self, key: Any) -> NoReturn:
self._readonly()
def __setitem__(self, key: Any, value: Any) -> NoReturn:
self._readonly()
def __setattr__(self, key: Any, value: Any) -> NoReturn:
self._readonly()
_KT = TypeVar("_KT", bound=Hashable)
_VT = TypeVar("_VT", bound=Any)
@cython.cclass
class ImmutableDictBase(Dict[_KT, _VT]):
# NOTE: this method is required in 3.9 and speeds up the use case
# ImmutableDictBase[str,int](a_dict) significantly
@classmethod
def __class_getitem__( # type: ignore[override]
cls, key: Any
) -> type[Self]:
return cls
def __delitem__(self, key: Any) -> NoReturn:
_immutable_fn(self)
def __setitem__(self, key: Any, value: Any) -> NoReturn:
_immutable_fn(self)
def __setattr__(self, key: Any, value: Any) -> NoReturn:
_immutable_fn(self)
def clear(self) -> NoReturn:
_immutable_fn(self)
def pop(self, key: Any, default: Optional[Any] = None) -> NoReturn:
_immutable_fn(self)
def popitem(self) -> NoReturn:
_immutable_fn(self)
def setdefault(self, key: Any, default: Optional[Any] = None) -> NoReturn:
_immutable_fn(self)
def update(self, *arg: Any, **kw: Any) -> NoReturn:
_immutable_fn(self)
# NOTE: can't extend from ImmutableDictBase[_KT, _VT] due to a compiler
# crash in doing so. Extending from ImmutableDictBase is ok, but requires
# a type checking section and other workaround for the crash
@cython.cclass
class immutabledict(Dict[_KT, _VT]):
"""An immutable version of a dict."""
# ImmutableDictBase start
@classmethod
def __class_getitem__( # type: ignore[override]
cls, key: Any
) -> type[Self]:
return cls
def __delitem__(self, key: Any) -> NoReturn:
_immutable_fn(self)
def __setitem__(self, key: Any, value: Any) -> NoReturn:
_immutable_fn(self)
def __setattr__(self, key: Any, value: Any) -> NoReturn:
_immutable_fn(self)
def clear(self) -> NoReturn:
_immutable_fn(self)
def pop(self, key: Any, default: Optional[Any] = None) -> NoReturn:
_immutable_fn(self)
def popitem(self) -> NoReturn:
_immutable_fn(self)
def setdefault(self, key: Any, default: Optional[Any] = None) -> NoReturn:
_immutable_fn(self)
def update(self, *arg: Any, **kw: Any) -> NoReturn:
_immutable_fn(self)
# ImmutableDictBase end
def __repr__(self) -> str:
return f"immutabledict({dict.__repr__(self)})"
@cython.annotation_typing(False) # avoid cython crash from generic return
def union(
self, *dicts: Optional[Mapping[_KT, _VT]]
) -> immutabledict[_KT, _VT]:
return self._union_other(dicts) # type: ignore[no-any-return]
@cython.annotation_typing(False) # avoid cython crash from generic return
def merge_with(
self, *dicts: Optional[Mapping[_KT, _VT]]
) -> immutabledict[_KT, _VT]:
# this is an alias of union
return self._union_other(dicts) # type: ignore[no-any-return]
@cython.cfunc
@cython.inline
def _union_other(self, others: tuple) -> immutabledict:
size = len(others)
if size == 0:
return self
# only_one == immutabledict : we found exactly one immutabledict that
# has contents; no other dict / immutabledict has any contents
#
# only_one is None : we found more than one dict / immutabledict that
# has contents
#
# only_one is False : we've found nothing that is not an empty
# immutabledict
only_one: immutabledict | None | Literal[False]
if self:
self_is_empty = False
only_one = self
else:
only_one = False
self_is_empty = True
for i in range(size):
d = others[i]
if not d:
continue
if only_one is False and isinstance(d, immutabledict):
only_one = d
else:
only_one = None
break
if only_one is False:
return self
elif only_one is not None:
return only_one
result: immutabledict = immutabledict()
if not self_is_empty:
PyDict_Update(result, self)
for i in range(size):
d = others[i]
if not d:
continue
if isinstance(d, dict):
# c version of PyDict_Update supports only dicts
PyDict_Update(result, d)
else:
dict.update(result, d)
return result
def copy(self) -> Self:
return self
def __reduce__(self) -> Any:
return immutabledict, (dict(self),)
# PEP 584
def __ior__(self, __value: Any, /) -> NoReturn:
_immutable_fn(self)
def __or__( # type: ignore[override]
self, __value: Mapping[_KT, _VT], /
) -> immutabledict[_KT, _VT]:
return immutabledict(
dict.__or__(self, __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(
dict.__ror__(self, __value), # type: ignore[call-overload,operator,unused-ignore] # noqa: E501
)
@@ -0,0 +1,298 @@
# util/compat.py
# Copyright (C) 2005-2026 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
# mypy: allow-untyped-defs, allow-untyped-calls
"""Handle Python version/platform incompatibilities."""
from __future__ import annotations
import base64
import dataclasses
import hashlib
from importlib import metadata as importlib_metadata
import inspect
import operator
import platform
import sys
import sysconfig
import typing
from typing import Any
from typing import Callable
from typing import Dict
from typing import Iterable
from typing import List
from typing import Mapping
from typing import Optional
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import Type
py314b1 = sys.version_info >= (3, 14, 0, "beta", 1)
py314 = sys.version_info >= (3, 14)
py313 = sys.version_info >= (3, 13)
py312 = sys.version_info >= (3, 12)
pypy = platform.python_implementation() == "PyPy"
cpython = platform.python_implementation() == "CPython"
freethreading = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
win32 = sys.platform.startswith("win")
osx = sys.platform.startswith("darwin")
arm = "aarch" in platform.machine().lower()
is64bit = sys.maxsize > 2**32
has_refcount_gc = bool(cpython)
dottedgetter = operator.attrgetter
# use sys.version_info to enable mypy version narrowing
if sys.version_info >= (3, 14):
import annotationlib
from string.templatelib import Template as Template
def get_annotations(obj: Any) -> Mapping[str, Any]:
return annotationlib.get_annotations(
obj, format=annotationlib.Format.FORWARDREF
)
else:
def get_annotations(obj: Any) -> Mapping[str, Any]:
return inspect.get_annotations(obj)
class Template:
"""Minimal Template for Python < 3.14 (test usage only)."""
def __init__(self, *parts: Any):
self._parts = parts
@property
def strings(self) -> Tuple[str, ...]:
return tuple(p for p in self._parts if isinstance(p, str))
@property
def interpolations(self) -> Tuple[Any, ...]:
return tuple(p for p in self._parts if not isinstance(p, str))
def __iter__(self) -> Any:
return iter(self._parts)
class FullArgSpec(typing.NamedTuple):
args: List[str]
varargs: Optional[str]
varkw: Optional[str]
defaults: Optional[Tuple[Any, ...]]
kwonlyargs: List[str]
kwonlydefaults: Optional[Dict[str, Any]]
annotations: Mapping[str, Any]
def inspect_getfullargspec(func: Callable[..., Any]) -> FullArgSpec:
"""Fully vendored version of getfullargspec from Python 3.3."""
if inspect.ismethod(func):
func = func.__func__
if not inspect.isfunction(func) and not hasattr(func, "__code__"):
raise TypeError(f"{func!r} is not a Python function")
co = func.__code__
if not inspect.iscode(co):
raise TypeError(f"{co!r} is not a code object")
nargs = co.co_argcount
names = co.co_varnames
nkwargs = co.co_kwonlyargcount
args = list(names[:nargs])
kwonlyargs = list(names[nargs : nargs + nkwargs])
nargs += nkwargs
varargs = None
if co.co_flags & inspect.CO_VARARGS:
varargs = co.co_varnames[nargs]
nargs = nargs + 1
varkw = None
if co.co_flags & inspect.CO_VARKEYWORDS:
varkw = co.co_varnames[nargs]
return FullArgSpec(
args,
varargs,
varkw,
func.__defaults__,
kwonlyargs,
func.__kwdefaults__,
get_annotations(func),
)
# python stubs don't have a public type for this. not worth
# making a protocol
def md5_not_for_security() -> Any:
return hashlib.md5(usedforsecurity=False)
def importlib_metadata_get(group):
ep = importlib_metadata.entry_points()
if typing.TYPE_CHECKING or hasattr(ep, "select"):
return ep.select(group=group)
else:
return ep.get(group, ())
def b(s):
return s.encode("latin-1")
def b64decode(x: str) -> bytes:
return base64.b64decode(x.encode("ascii"))
def b64encode(x: bytes) -> str:
return base64.b64encode(x).decode("ascii")
def decode_backslashreplace(text: bytes, encoding: str) -> str:
return text.decode(encoding, errors="backslashreplace")
def cmp(a, b):
return (a > b) - (a < b)
def _formatannotation(annotation, base_module=None):
"""vendored from python 3.7"""
if isinstance(annotation, str):
return annotation
if getattr(annotation, "__module__", None) == "typing":
return repr(annotation).replace("typing.", "").replace("~", "")
if isinstance(annotation, type):
if annotation.__module__ in ("builtins", base_module):
return repr(annotation.__qualname__)
return annotation.__module__ + "." + annotation.__qualname__
elif isinstance(annotation, typing.TypeVar):
return repr(annotation).replace("~", "")
return repr(annotation).replace("~", "")
def inspect_formatargspec(
args: List[str],
varargs: Optional[str] = None,
varkw: Optional[str] = None,
defaults: Optional[Sequence[Any]] = None,
kwonlyargs: Optional[Sequence[str]] = (),
kwonlydefaults: Optional[Mapping[str, Any]] = {},
annotations: Mapping[str, Any] = {},
formatarg: Callable[[str], str] = str,
formatvarargs: Callable[[str], str] = lambda name: "*" + name,
formatvarkw: Callable[[str], str] = lambda name: "**" + name,
formatvalue: Callable[[Any], str] = lambda value: "=" + repr(value),
formatreturns: Callable[[Any], str] = lambda text: " -> " + str(text),
formatannotation: Callable[[Any], str] = _formatannotation,
) -> str:
"""Copy formatargspec from python 3.7 standard library.
Python 3 has deprecated formatargspec and requested that Signature
be used instead, however this requires a full reimplementation
of formatargspec() in terms of creating Parameter objects and such.
Instead of introducing all the object-creation overhead and having
to reinvent from scratch, just copy their compatibility routine.
Ultimately we would need to rewrite our "decorator" routine completely
which is not really worth it right now, until all Python 2.x support
is dropped.
"""
kwonlydefaults = kwonlydefaults or {}
annotations = annotations or {}
def formatargandannotation(arg):
result = formatarg(arg)
if arg in annotations:
result += ": " + formatannotation(annotations[arg])
return result
specs = []
if defaults:
firstdefault = len(args) - len(defaults)
else:
firstdefault = -1
for i, arg in enumerate(args):
spec = formatargandannotation(arg)
if defaults and i >= firstdefault:
spec = spec + formatvalue(defaults[i - firstdefault])
specs.append(spec)
if varargs is not None:
specs.append(formatvarargs(formatargandannotation(varargs)))
else:
if kwonlyargs:
specs.append("*")
if kwonlyargs:
for kwonlyarg in kwonlyargs:
spec = formatargandannotation(kwonlyarg)
if kwonlydefaults and kwonlyarg in kwonlydefaults:
spec += formatvalue(kwonlydefaults[kwonlyarg])
specs.append(spec)
if varkw is not None:
specs.append(formatvarkw(formatargandannotation(varkw)))
result = "(" + ", ".join(specs) + ")"
if "return" in annotations:
result += formatreturns(formatannotation(annotations["return"]))
return result
def dataclass_fields(cls: Type[Any]) -> Iterable[dataclasses.Field[Any]]:
"""Return a sequence of all dataclasses.Field objects associated
with a class as an already processed dataclass.
The class must **already be a dataclass** for Field objects to be returned.
"""
if dataclasses.is_dataclass(cls):
return dataclasses.fields(cls)
else:
return []
def local_dataclass_fields(cls: Type[Any]) -> Iterable[dataclasses.Field[Any]]:
"""Return a sequence of all dataclasses.Field objects associated with
an already processed dataclass, excluding those that originate from a
superclass.
The class must **already be a dataclass** for Field objects to be returned.
"""
if dataclasses.is_dataclass(cls):
super_fields: Set[dataclasses.Field[Any]] = set()
for sup in cls.__bases__:
super_fields.update(dataclass_fields(sup))
return [f for f in dataclasses.fields(cls) if f not in super_fields]
else:
return []
if freethreading:
import threading
mini_gil = threading.RLock()
"""provide a threading.RLock() under python freethreading only"""
else:
import contextlib
mini_gil = contextlib.nullcontext() # type: ignore[assignment]
@@ -0,0 +1,272 @@
# util/concurrency.py
# Copyright (C) 2005-2026 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
# mypy: allow-untyped-defs, allow-untyped-calls
"""asyncio-related concurrency functions."""
from __future__ import annotations
import asyncio
import sys
from typing import Any
from typing import Awaitable
from typing import Callable
from typing import Coroutine
from typing import NoReturn
from typing import TYPE_CHECKING
from typing import TypeGuard
from typing import TypeVar
from .langhelpers import memoized_property
from .. import exc
_T = TypeVar("_T")
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)
)
_ERROR_MESSAGE = (
"The SQLAlchemy asyncio module requires that the Python 'greenlet' "
"library is installed. In order to ensure this dependency is "
"available, use the 'sqlalchemy[asyncio]' install target: "
"'pip install sqlalchemy[asyncio]'"
)
def _not_implemented(*arg: Any, **kw: Any) -> NoReturn:
raise ImportError(_ERROR_MESSAGE)
class _concurrency_shim_cls:
"""Late import shim for greenlet"""
__slots__ = (
"_has_greenlet",
"greenlet",
"_AsyncIoGreenlet",
"getcurrent",
)
def _initialize(self, *, raise_: bool = True) -> None:
"""Import greenlet and initialize the class"""
if "greenlet" in globals():
return
if not TYPE_CHECKING:
global getcurrent, greenlet, _AsyncIoGreenlet
global _has_gr_context
try:
from greenlet import getcurrent
from greenlet import greenlet
except ImportError as e:
if not TYPE_CHECKING:
# set greenlet in the global scope to prevent re-init
greenlet = None
self._has_greenlet = False
self._initialize_no_greenlet()
if raise_:
raise ImportError(_ERROR_MESSAGE) from e
else:
self._has_greenlet = True
# 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")
# implementation based on snaury gist at
# https://gist.github.com/snaury/202bf4f22c41ca34e56297bae5f33fef
# Issue for context: https://github.com/python-greenlet/greenlet/issues/173 # noqa: E501
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
self.greenlet = greenlet
self.getcurrent = getcurrent
self._AsyncIoGreenlet = _AsyncIoGreenlet
def _initialize_no_greenlet(self):
self.getcurrent = _not_implemented
self.greenlet = _not_implemented # type: ignore[assignment]
self._AsyncIoGreenlet = _not_implemented # type: ignore[assignment]
def __getattr__(self, key: str) -> Any:
if key in self.__slots__:
self._initialize()
return getattr(self, key)
else:
raise AttributeError(key)
_concurrency_shim = _concurrency_shim_cls()
if TYPE_CHECKING:
_T_co = TypeVar("_T_co", covariant=True)
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 = _concurrency_shim.getcurrent()
return getattr(current, "__sqlalchemy_greenlet_provider__", False)
def await_(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_` calls cannot be nested.
:param awaitable: The coroutine to call.
"""
# this is called in the context greenlet while running fn
current = _concurrency_shim.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_() "
"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)
assert current.parent
return current.parent.switch(awaitable) # type: ignore[no-any-return]
await_only = await_ # old name. deprecated on 2.2
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_` 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 = _concurrency_shim._AsyncIoGreenlet(
fn, _concurrency_shim.getcurrent()
)
# runs the function synchronously in gl greenlet. If the execution
# is interrupted by await_, 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_ 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_(self.mutex.acquire())
def __exit__(self, *arg: Any, **kw: Any) -> None:
self.mutex.release()
class _AsyncUtil:
"""Asyncio util for test suite/ util only"""
def __init__(self) -> None:
# runner it lazy so it can be created here
self.runner = asyncio.Runner()
def run(
self,
fn: Callable[..., Coroutine[Any, Any, _T]],
*args: Any,
**kwargs: Any,
) -> _T:
"""Run coroutine on the loop"""
return self.runner.run(fn(*args, **kwargs))
def run_in_greenlet(
self, fn: Callable[..., _T], *args: Any, **kwargs: Any
) -> _T:
"""Run sync function in greenlet. Support nested calls"""
_concurrency_shim._initialize(raise_=False)
if _concurrency_shim._has_greenlet:
if self.runner.get_loop().is_running():
# allow for a wrapped test function to call another
assert in_greenlet()
return fn(*args, **kwargs)
else:
return self.runner.run(greenlet_spawn(fn, *args, **kwargs))
else:
return fn(*args, **kwargs)
def close(self) -> None:
self.runner.close()
@@ -0,0 +1,95 @@
# util/cython.py
# Copyright (C) 2005-2026 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
from __future__ import annotations
from typing import Any
from typing import Callable
from typing import Generic
from typing import overload
from typing import Type
from typing import TypeVar
_T = TypeVar("_T")
_NO_OP = Callable[[_T], _T]
# cython module shims
# --
IS_SHIM = True
# constants
compiled = False
# types
int = int # noqa: A001
char = int
bint = bool
longlong = int
ulonglong = int
Py_ssize_t = int
uint = int
float = float # noqa: A001
double = float
void = Any
NULL = None
class const(Generic[_T]):
@classmethod
def __class_getitem__(cls, item: _T) -> _T:
return item
# functions
def _no_op(fn: _T) -> _T:
return fn
cclass = _no_op # equivalent to "cdef class"
ccall = _no_op # equivalent to "cpdef" function
cfunc = _no_op # equivalent to "cdef" function
inline = _no_op
final = _no_op
pointer = _no_op # not sure how to express a pointer to a type
@overload
def declare(t: Type[const[_T]], value: Any = None, **kw: Any) -> _T: ...
@overload
def declare(t: Type[_T], value: Any = None, **kw: Any) -> _T: ...
def declare(t: Type[_T] | Type[const[_T]], value: Any = None, **kw: Any) -> _T:
return value # type: ignore[no-any-return]
def annotation_typing(_: bool) -> _NO_OP[_T]:
return _no_op
def exceptval(value: Any = None, *, check: bool = False) -> _NO_OP[_T]:
return _no_op
def cast(type_: Type[_T], value: Any, *, typecheck: bool = False) -> _T:
return value # type: ignore[no-any-return]
def returns(_: type) -> _NO_OP[_T]:
return _no_op
def locals(**kwargs: Any) -> _NO_OP[_T]: # noqa: A001
return _no_op
def wraparound(value: bool) -> _NO_OP[_T]:
return _no_op
def boundscheck(value: bool) -> _NO_OP[_T]:
return _no_op
@@ -0,0 +1,401 @@
# util/deprecations.py
# Copyright (C) 2005-2026 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
# mypy: allow-untyped-defs, allow-untyped-calls
"""Helpers related to deprecation of functions, methods, classes, other
functionality."""
from __future__ import annotations
import re
from typing import Any
from typing import Callable
from typing import Dict
from typing import Match
from typing import Optional
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import Type
from typing import TypeVar
from typing import Union
from . import compat
from .langhelpers import _hash_limit_string
from .langhelpers import _warnings_warn
from .langhelpers import decorator
from .langhelpers import inject_docstring_text
from .langhelpers import inject_param_text
from .. import exc
_T = TypeVar("_T", bound=Any)
# https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators
_F = TypeVar("_F", bound="Callable[..., Any]")
def _warn_with_version(
msg: str,
version: str,
type_: Type[exc.SADeprecationWarning],
stacklevel: int,
code: Optional[str] = None,
) -> None:
warn = type_(msg, code=code)
warn.deprecated_since = version
_warnings_warn(warn, stacklevel=stacklevel + 1)
def warn_deprecated(
msg: str, version: str, stacklevel: int = 3, code: Optional[str] = None
) -> None:
_warn_with_version(
msg, version, exc.SADeprecationWarning, stacklevel, code=code
)
def warn_deprecated_limited(
msg: str,
args: Sequence[Any],
version: str,
stacklevel: int = 3,
code: Optional[str] = None,
) -> None:
"""Issue a deprecation warning with a parameterized string,
limiting the number of registrations.
"""
if args:
msg = _hash_limit_string(msg, 10, args)
_warn_with_version(
msg, version, exc.SADeprecationWarning, stacklevel, code=code
)
def deprecated_cls(
version: str, message: str, constructor: Optional[str] = "__init__"
) -> Callable[[Type[_T]], Type[_T]]:
header = ".. deprecated:: %s %s" % (version, (message or ""))
def decorate(cls: Type[_T]) -> Type[_T]:
return _decorate_cls_with_warning(
cls,
constructor,
exc.SADeprecationWarning,
message % dict(func=constructor),
version,
header,
)
return decorate
def deprecated(
version: str,
message: Optional[str] = None,
add_deprecation_to_docstring: bool = True,
warning: Optional[Type[exc.SADeprecationWarning]] = None,
enable_warnings: bool = True,
) -> Callable[[_F], _F]:
"""Decorates a function and issues a deprecation warning on use.
:param version:
Issue version in the warning.
:param message:
If provided, issue message in the warning. A sensible default
is used if not provided.
:param add_deprecation_to_docstring:
Default True. If False, the wrapped function's __doc__ is left
as-is. If True, the 'message' is prepended to the docs if
provided, or sensible default if message is omitted.
"""
if add_deprecation_to_docstring:
header = ".. deprecated:: %s %s" % (
version,
(message or ""),
)
else:
header = None
if message is None:
message = "Call to deprecated function %(func)s"
if warning is None:
warning = exc.SADeprecationWarning
message += " (deprecated since: %s)" % version
def decorate(fn: _F) -> _F:
assert message is not None
assert warning is not None
return _decorate_with_warning(
fn,
warning,
message % dict(func=fn.__name__),
version,
header,
enable_warnings=enable_warnings,
)
return decorate
def moved_20(
message: str, **kw: Any
) -> Callable[[Callable[..., _T]], Callable[..., _T]]:
return deprecated(
"2.0", message=message, warning=exc.MovedIn20Warning, **kw
)
def became_legacy_20(
api_name: str, alternative: Optional[str] = None, **kw: Any
) -> Callable[[_F], _F]:
type_reg = re.match("^:(attr|func|meth):", api_name)
if type_reg:
type_ = {"attr": "attribute", "func": "function", "meth": "method"}[
type_reg.group(1)
]
else:
type_ = "construct"
message = (
"The %s %s is considered legacy as of the "
"1.x series of SQLAlchemy and %s in 2.0."
% (
api_name,
type_,
"becomes a legacy construct",
)
)
if ":attr:" in api_name:
attribute_ok = kw.pop("warn_on_attribute_access", False)
if not attribute_ok:
assert kw.get("enable_warnings") is False, (
"attribute %s will emit a warning on read access. "
"If you *really* want this, "
"add warn_on_attribute_access=True. Otherwise please add "
"enable_warnings=False." % api_name
)
if alternative:
message += " " + alternative
warning_cls = exc.LegacyAPIWarning
return deprecated("2.0", message=message, warning=warning_cls, **kw)
def deprecated_params(**specs: Tuple[str, str]) -> Callable[[_F], _F]:
"""Decorates a function to warn on use of certain parameters.
e.g. ::
@deprecated_params(
weak_identity_map=(
"2.0",
"the :paramref:`.Session.weak_identity_map parameter "
"is deprecated.",
)
)
def some_function(**kwargs): ...
"""
messages: Dict[str, str] = {}
versions: Dict[str, str] = {}
version_warnings: Dict[str, Type[exc.SADeprecationWarning]] = {}
for param, (version, message) in specs.items():
versions[param] = version
messages[param] = _sanitize_restructured_text(message)
version_warnings[param] = exc.SADeprecationWarning
def decorate(fn: _F) -> _F:
spec = compat.inspect_getfullargspec(fn)
check_defaults: Union[Set[str], Tuple[()]]
if spec.defaults is not None:
defaults = dict(
zip(
spec.args[(len(spec.args) - len(spec.defaults)) :],
spec.defaults,
)
)
check_defaults = set(defaults).intersection(messages)
check_kw = set(messages).difference(defaults)
elif spec.kwonlydefaults is not None:
defaults = spec.kwonlydefaults
check_defaults = set(defaults).intersection(messages)
check_kw = set(messages).difference(defaults)
else:
check_defaults = ()
check_kw = set(messages)
check_any_kw = spec.varkw
# latest mypy has opinions here, not sure if they implemented
# Concatenate or something
@decorator
def warned(fn: _F, *args: Any, **kwargs: Any) -> _F:
for m in check_defaults:
if (defaults[m] is None and kwargs[m] is not None) or (
defaults[m] is not None and kwargs[m] != defaults[m]
):
_warn_with_version(
messages[m],
versions[m],
version_warnings[m],
stacklevel=3,
)
if check_any_kw in messages and set(kwargs).difference(
check_defaults
):
assert check_any_kw is not None
_warn_with_version(
messages[check_any_kw],
versions[check_any_kw],
version_warnings[check_any_kw],
stacklevel=3,
)
for m in check_kw:
if m in kwargs:
_warn_with_version(
messages[m],
versions[m],
version_warnings[m],
stacklevel=3,
)
return fn(*args, **kwargs) # type: ignore[no-any-return]
doc = fn.__doc__ if fn.__doc__ is not None else ""
if doc:
doc = inject_param_text(
doc,
{
param: ".. deprecated:: %s %s"
% ("1.4" if version == "2.0" else version, (message or ""))
for param, (version, message) in specs.items()
},
)
decorated = warned(fn)
decorated.__doc__ = doc
return decorated
return decorate
def _sanitize_restructured_text(text: str) -> str:
def repl(m: Match[str]) -> str:
type_, name = m.group(1, 2)
if type_ in ("func", "meth"):
name += "()"
return name
text = re.sub(r":ref:`(.+) <.*>`", lambda m: '"%s"' % m.group(1), text)
return re.sub(r"\:(\w+)\:`~?(?:_\w+)?\.?(.+?)`", repl, text)
def _decorate_cls_with_warning(
cls: Type[_T],
constructor: Optional[str],
wtype: Type[exc.SADeprecationWarning],
message: str,
version: str,
docstring_header: Optional[str] = None,
) -> Type[_T]:
doc = cls.__doc__ is not None and cls.__doc__ or ""
if docstring_header is not None:
if constructor is not None:
docstring_header %= dict(func=constructor)
if issubclass(wtype, exc.Base20DeprecationWarning):
docstring_header += (
" (Background on SQLAlchemy 2.0 at: "
":ref:`migration_20_toplevel`)"
)
doc = inject_docstring_text(doc, docstring_header, 1)
constructor_fn = None
if type(cls) is type:
clsdict = dict(cls.__dict__)
clsdict["__doc__"] = doc
clsdict.pop("__dict__", None)
clsdict.pop("__weakref__", None)
cls = type(cls.__name__, cls.__bases__, clsdict)
if constructor is not None:
constructor_fn = clsdict[constructor]
else:
cls.__doc__ = doc
if constructor is not None:
constructor_fn = getattr(cls, constructor)
if constructor is not None:
assert constructor_fn is not None
assert wtype is not None
setattr(
cls,
constructor,
_decorate_with_warning(
constructor_fn, wtype, message, version, None
),
)
return cls
def _decorate_with_warning(
func: _F,
wtype: Type[exc.SADeprecationWarning],
message: str,
version: str,
docstring_header: Optional[str] = None,
enable_warnings: bool = True,
) -> _F:
"""Wrap a function with a warnings.warn and augmented docstring."""
message = _sanitize_restructured_text(message)
if issubclass(wtype, exc.Base20DeprecationWarning):
doc_only = (
" (Background on SQLAlchemy 2.0 at: "
":ref:`migration_20_toplevel`)"
)
else:
doc_only = ""
@decorator
def warned(fn: _F, *args: Any, **kwargs: Any) -> _F:
skip_warning = not enable_warnings or kwargs.pop(
"_sa_skip_warning", False
)
if not skip_warning:
_warn_with_version(message, version, wtype, stacklevel=3)
return fn(*args, **kwargs) # type: ignore[no-any-return]
doc = func.__doc__ is not None and func.__doc__ or ""
if docstring_header is not None:
docstring_header %= dict(func=func.__name__)
docstring_header += doc_only
doc = inject_docstring_text(doc, docstring_header, 1)
decorated = warned(func)
decorated.__doc__ = doc
decorated._sa_warn = lambda: _warn_with_version( # type: ignore[attr-defined] # noqa: E501
message, version, wtype, stacklevel=3
)
return decorated
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,153 @@
# util/preloaded.py
# Copyright (C) 2005-2026 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
# mypy: allow-untyped-defs, allow-untyped-calls
"""supplies the "preloaded" registry to resolve circular module imports at
runtime.
"""
from __future__ import annotations
import sys
from typing import Any
from typing import Callable
from typing import TYPE_CHECKING
from typing import TypeVar
_FN = TypeVar("_FN", bound=Callable[..., Any])
if TYPE_CHECKING:
from sqlalchemy import dialects as _dialects
from sqlalchemy import orm as _orm
from sqlalchemy.engine import cursor as _engine_cursor
from sqlalchemy.engine import default as _engine_default
from sqlalchemy.engine import reflection as _engine_reflection
from sqlalchemy.engine import result as _engine_result
from sqlalchemy.engine import url as _engine_url
from sqlalchemy.orm import attributes as _orm_attributes
from sqlalchemy.orm import base as _orm_base
from sqlalchemy.orm import clsregistry as _orm_clsregistry
from sqlalchemy.orm import decl_api as _orm_decl_api
from sqlalchemy.orm import decl_base as _orm_decl_base
from sqlalchemy.orm import dependency as _orm_dependency
from sqlalchemy.orm import descriptor_props as _orm_descriptor_props
from sqlalchemy.orm import mapperlib as _orm_mapper
from sqlalchemy.orm import properties as _orm_properties
from sqlalchemy.orm import relationships as _orm_relationships
from sqlalchemy.orm import session as _orm_session
from sqlalchemy.orm import state as _orm_state
from sqlalchemy.orm import strategies as _orm_strategies
from sqlalchemy.orm import strategy_options as _orm_strategy_options
from sqlalchemy.orm import util as _orm_util
from sqlalchemy.sql import ddl as _sql_ddl
from sqlalchemy.sql import default_comparator as _sql_default_comparator
from sqlalchemy.sql import dml as _sql_dml
from sqlalchemy.sql import elements as _sql_elements
from sqlalchemy.sql import functions as _sql_functions
from sqlalchemy.sql import naming as _sql_naming
from sqlalchemy.sql import schema as _sql_schema
from sqlalchemy.sql import selectable as _sql_selectable
from sqlalchemy.sql import sqltypes as _sql_sqltypes
from sqlalchemy.sql import traversals as _sql_traversals
from sqlalchemy.sql import util as _sql_util
# sigh, appease mypy 0.971 which does not accept imports as instance
# variables of a module
dialects = _dialects
engine_cursor = _engine_cursor
engine_default = _engine_default
engine_reflection = _engine_reflection
engine_result = _engine_result
engine_url = _engine_url
orm_clsregistry = _orm_clsregistry
orm_base = _orm_base
orm = _orm
orm_attributes = _orm_attributes
orm_decl_api = _orm_decl_api
orm_decl_base = _orm_decl_base
orm_descriptor_props = _orm_descriptor_props
orm_dependency = _orm_dependency
orm_mapper = _orm_mapper
orm_properties = _orm_properties
orm_relationships = _orm_relationships
orm_session = _orm_session
orm_strategies = _orm_strategies
orm_strategy_options = _orm_strategy_options
orm_state = _orm_state
orm_util = _orm_util
sql_ddl = _sql_ddl
sql_default_comparator = _sql_default_comparator
sql_dml = _sql_dml
sql_elements = _sql_elements
sql_functions = _sql_functions
sql_naming = _sql_naming
sql_selectable = _sql_selectable
sql_traversals = _sql_traversals
sql_schema = _sql_schema
sql_sqltypes = _sql_sqltypes
sql_util = _sql_util
class _ModuleRegistry:
"""Registry of modules to load in a package init file.
To avoid potential thread safety issues for imports that are deferred
in a function, like https://bugs.python.org/issue38884, these modules
are added to the system module cache by importing them after the packages
has finished initialization.
A global instance is provided under the name :attr:`.preloaded`. Use
the function :func:`.preload_module` to register modules to load and
:meth:`.import_prefix` to load all the modules that start with the
given path.
While the modules are loaded in the global module cache, it's advisable
to access them using :attr:`.preloaded` to ensure that it was actually
registered. Each registered module is added to the instance ``__dict__``
in the form `<package>_<module>`, omitting ``sqlalchemy`` from the package
name. Example: ``sqlalchemy.sql.util`` becomes ``preloaded.sql_util``.
"""
def __init__(self, prefix="sqlalchemy."):
self.module_registry = set()
self.prefix = prefix
def preload_module(self, *deps: str) -> Callable[[_FN], _FN]:
"""Adds the specified modules to the list to load.
This method can be used both as a normal function and as a decorator.
No change is performed to the decorated object.
"""
self.module_registry.update(deps)
return lambda fn: fn
def import_prefix(self, path: str) -> None:
"""Resolve all the modules in the registry that start with the
specified path.
"""
for module in self.module_registry:
if self.prefix:
key = module.split(self.prefix)[-1].replace(".", "_")
else:
key = module
if (
not path or module.startswith(path)
) and key not in self.__dict__:
__import__(module, globals(), locals())
self.__dict__[key] = globals()[key] = sys.modules[module]
_reg = _ModuleRegistry()
preload_module = _reg.preload_module
import_prefix = _reg.import_prefix
# this appears to do absolutely nothing for any version of mypy
# if TYPE_CHECKING:
# def __getattr__(key: str) -> ModuleType:
# ...
@@ -0,0 +1,304 @@
# util/queue.py
# Copyright (C) 2005-2026 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
# mypy: allow-untyped-defs, allow-untyped-calls
"""An adaptation of Py2.3/2.4's Queue module which supports reentrant
behavior, using RLock instead of Lock for its mutex object. The
Queue object is used exclusively by the sqlalchemy.pool.QueuePool
class.
This is to support the connection pool's usage of weakref callbacks to return
connections to the underlying Queue, which can in extremely
rare cases be invoked within the ``get()`` method of the Queue itself,
producing a ``put()`` inside the ``get()`` and therefore a reentrant
condition.
"""
from __future__ import annotations
import asyncio
from collections import deque
import threading
from time import time as _time
from typing import Any
from typing import Deque
from typing import Generic
from typing import Optional
from typing import TypeVar
from .concurrency import await_
from .langhelpers import memoized_property
_T = TypeVar("_T", bound=Any)
__all__ = ["Empty", "Full", "Queue"]
class Empty(Exception):
"Exception raised by Queue.get(block=0)/get_nowait()."
pass
class Full(Exception):
"Exception raised by Queue.put(block=0)/put_nowait()."
pass
class QueueCommon(Generic[_T]):
maxsize: int
use_lifo: bool
def __init__(self, maxsize: int = 0, use_lifo: bool = False): ...
def empty(self) -> bool:
raise NotImplementedError()
def full(self) -> bool:
raise NotImplementedError()
def qsize(self) -> int:
raise NotImplementedError()
def put_nowait(self, item: _T) -> None:
raise NotImplementedError()
def put(
self, item: _T, block: bool = True, timeout: Optional[float] = None
) -> None:
raise NotImplementedError()
def get_nowait(self) -> _T:
raise NotImplementedError()
def get(self, block: bool = True, timeout: Optional[float] = None) -> _T:
raise NotImplementedError()
class Queue(QueueCommon[_T]):
queue: Deque[_T]
def __init__(self, maxsize: int = 0, use_lifo: bool = False):
"""Initialize a queue object with a given maximum size.
If `maxsize` is <= 0, the queue size is infinite.
If `use_lifo` is True, this Queue acts like a Stack (LIFO).
"""
self._init(maxsize)
# mutex must be held whenever the queue is mutating. All methods
# that acquire mutex must release it before returning. mutex
# is shared between the two conditions, so acquiring and
# releasing the conditions also acquires and releases mutex.
self.mutex = threading.RLock()
# Notify not_empty whenever an item is added to the queue; a
# thread waiting to get is notified then.
self.not_empty = threading.Condition(self.mutex)
# Notify not_full whenever an item is removed from the queue;
# a thread waiting to put is notified then.
self.not_full = threading.Condition(self.mutex)
# If this queue uses LIFO or FIFO
self.use_lifo = use_lifo
def qsize(self) -> int:
"""Return the approximate size of the queue (not reliable!)."""
with self.mutex:
return self._qsize()
def empty(self) -> bool:
"""Return True if the queue is empty, False otherwise (not
reliable!)."""
with self.mutex:
return self._empty()
def full(self) -> bool:
"""Return True if the queue is full, False otherwise (not
reliable!)."""
with self.mutex:
return self._full()
def put(
self, item: _T, block: bool = True, timeout: Optional[float] = None
) -> None:
"""Put an item into the queue.
If optional args `block` is True and `timeout` is None (the
default), block if necessary until a free slot is
available. If `timeout` is a positive number, it blocks at
most `timeout` seconds and raises the ``Full`` exception if no
free slot was available within that time. Otherwise (`block`
is false), put an item on the queue if a free slot is
immediately available, else raise the ``Full`` exception
(`timeout` is ignored in that case).
"""
with self.not_full:
if not block:
if self._full():
raise Full
elif timeout is None:
while self._full():
self.not_full.wait()
else:
if timeout < 0:
raise ValueError("'timeout' must be a positive number")
endtime = _time() + timeout
while self._full():
remaining = endtime - _time()
if remaining <= 0.0:
raise Full
self.not_full.wait(remaining)
self._put(item)
self.not_empty.notify()
def put_nowait(self, item: _T) -> None:
"""Put an item into the queue without blocking.
Only enqueue the item if a free slot is immediately available.
Otherwise raise the ``Full`` exception.
"""
return self.put(item, False)
def get(self, block: bool = True, timeout: Optional[float] = None) -> _T:
"""Remove and return an item from the queue.
If optional args `block` is True and `timeout` is None (the
default), block if necessary until an item is available. If
`timeout` is a positive number, it blocks at most `timeout`
seconds and raises the ``Empty`` exception if no item was
available within that time. Otherwise (`block` is false),
return an item if one is immediately available, else raise the
``Empty`` exception (`timeout` is ignored in that case).
"""
with self.not_empty:
if not block:
if self._empty():
raise Empty
elif timeout is None:
while self._empty():
self.not_empty.wait()
else:
if timeout < 0:
raise ValueError("'timeout' must be a positive number")
endtime = _time() + timeout
while self._empty():
remaining = endtime - _time()
if remaining <= 0.0:
raise Empty
self.not_empty.wait(remaining)
item = self._get()
self.not_full.notify()
return item
def get_nowait(self) -> _T:
"""Remove and return an item from the queue without blocking.
Only get an item if one is immediately available. Otherwise
raise the ``Empty`` exception.
"""
return self.get(False)
def _init(self, maxsize: int) -> None:
self.maxsize = maxsize
self.queue = deque()
def _qsize(self) -> int:
return len(self.queue)
def _empty(self) -> bool:
return not self.queue
def _full(self) -> bool:
return self.maxsize > 0 and len(self.queue) == self.maxsize
def _put(self, item: _T) -> None:
self.queue.append(item)
def _get(self) -> _T:
if self.use_lifo:
# LIFO
return self.queue.pop()
else:
# FIFO
return self.queue.popleft()
class AsyncAdaptedQueue(QueueCommon[_T]):
def __init__(self, maxsize: int = 0, use_lifo: bool = False):
self.use_lifo = use_lifo
self.maxsize = maxsize
def empty(self) -> bool:
return self._queue.empty()
def full(self):
return self._queue.full()
def qsize(self):
return self._queue.qsize()
@memoized_property
def _queue(self) -> asyncio.Queue[_T]:
# Delay creation of the queue until it is first used, to avoid
# binding it to a possibly wrong event loop.
# By delaying the creation of the pool we accommodate the common
# usage pattern of instantiating the engine at module level, where a
# different event loop is in present compared to when the application
# is actually run.
queue: asyncio.Queue[_T]
if self.use_lifo:
queue = asyncio.LifoQueue(maxsize=self.maxsize)
else:
queue = asyncio.Queue(maxsize=self.maxsize)
return queue
def put_nowait(self, item: _T) -> None:
try:
self._queue.put_nowait(item)
except asyncio.QueueFull as err:
raise Full() from err
def put(
self, item: _T, block: bool = True, timeout: Optional[float] = None
) -> None:
if not block:
return self.put_nowait(item)
try:
if timeout is not None:
await_(asyncio.wait_for(self._queue.put(item), timeout))
else:
await_(self._queue.put(item))
except (asyncio.QueueFull, asyncio.TimeoutError) as err:
raise Full() from err
def get_nowait(self) -> _T:
try:
return self._queue.get_nowait()
except asyncio.QueueEmpty as err:
raise Empty() from err
def get(self, block: bool = True, timeout: Optional[float] = None) -> _T:
if not block:
return self.get_nowait()
try:
if timeout is not None:
return await_(asyncio.wait_for(self._queue.get(), timeout))
else:
return await_(self._queue.get())
except (asyncio.QueueEmpty, asyncio.TimeoutError) as err:
raise Empty() from err
@@ -0,0 +1,202 @@
# util/tool_support.py
# Copyright (C) 2005-2026 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
# mypy: allow-untyped-defs, allow-untyped-calls
"""support routines for the helpers in tools/.
These aren't imported by the enclosing util package as the are not
needed for normal library use.
"""
from __future__ import annotations
from argparse import ArgumentParser
from argparse import Namespace
import contextlib
import difflib
import os
from pathlib import Path
import shlex
import shutil
import subprocess
import sys
from typing import Any
from typing import Dict
from typing import Iterator
from typing import Optional
from typing import Union
from . import compat
class code_writer_cmd:
parser: ArgumentParser
args: Namespace
suppress_output: bool
diffs_detected: bool
source_root: Path
pyproject_toml_path: Path
def __init__(self, tool_script: str):
self.source_root = Path(tool_script).parent.parent
self.pyproject_toml_path = self.source_root / Path("pyproject.toml")
assert self.pyproject_toml_path.exists()
self.parser = ArgumentParser()
self.parser.add_argument(
"--stdout",
action="store_true",
help="Write to stdout instead of saving to file",
)
self.parser.add_argument(
"-c",
"--check",
help="Don't write the files back, just return the "
"status. Return code 0 means nothing would change. "
"Return code 1 means some files would be reformatted",
action="store_true",
)
def run_zimports(self, tempfile: str) -> None:
self._run_console_script(
str(tempfile),
{
"entrypoint": "zimports",
"options": f"--toml-config {self.pyproject_toml_path}",
},
)
def run_black(self, tempfile: str) -> None:
self._run_console_script(
str(tempfile),
{
"entrypoint": "black",
"options": f"--config {self.pyproject_toml_path}",
},
)
def _run_console_script(self, path: str, options: Dict[str, Any]) -> None:
"""Run a Python console application from within the process.
Used for black, zimports
"""
is_posix = os.name == "posix"
entrypoint_name = options["entrypoint"]
for entry in compat.importlib_metadata_get("console_scripts"):
if entry.name == entrypoint_name:
impl = entry
break
else:
raise Exception(
f"Could not find entrypoint console_scripts.{entrypoint_name}"
)
cmdline_options_str = options.get("options", "")
cmdline_options_list = shlex.split(
cmdline_options_str, posix=is_posix
) + [path]
kw: Dict[str, Any] = {}
if self.suppress_output:
kw["stdout"] = kw["stderr"] = subprocess.DEVNULL
subprocess.run(
[
sys.executable,
"-c",
"import %s; %s.%s()" % (impl.module, impl.module, impl.attr),
]
+ cmdline_options_list,
cwd=str(self.source_root),
**kw,
)
def write_status(self, *text: str) -> None:
if not self.suppress_output:
sys.stderr.write(" ".join(text))
def write_output_file_from_text(
self, text: str, destination_path: Union[str, Path]
) -> None:
if self.args.check:
self._run_diff(destination_path, source=text)
elif self.args.stdout:
print(text)
else:
self.write_status(f"Writing {destination_path}...")
Path(destination_path).write_text(
text, encoding="utf-8", newline="\n"
)
self.write_status("done\n")
def write_output_file_from_tempfile(
self, tempfile: str, destination_path: str
) -> None:
if self.args.check:
self._run_diff(destination_path, source_file=tempfile)
os.unlink(tempfile)
elif self.args.stdout:
with open(tempfile) as tf:
print(tf.read())
os.unlink(tempfile)
else:
self.write_status(f"Writing {destination_path}...")
shutil.move(tempfile, destination_path)
self.write_status("done\n")
def _run_diff(
self,
destination_path: Union[str, Path],
*,
source: Optional[str] = None,
source_file: Optional[str] = None,
) -> None:
if source_file:
with open(source_file, encoding="utf-8") as tf:
source_lines = list(tf)
elif source is not None:
source_lines = source.splitlines(keepends=True)
else:
assert False, "source or source_file is required"
with open(destination_path, encoding="utf-8") as dp:
d = difflib.unified_diff(
list(dp),
source_lines,
fromfile=Path(destination_path).as_posix(),
tofile="<proposed changes>",
n=3,
lineterm="\n",
)
d_as_list = list(d)
if d_as_list:
self.diffs_detected = True
print("".join(d_as_list))
@contextlib.contextmanager
def add_arguments(self) -> Iterator[ArgumentParser]:
yield self.parser
@contextlib.contextmanager
def run_program(self) -> Iterator[None]:
self.args = self.parser.parse_args()
if self.args.check:
self.diffs_detected = False
self.suppress_output = True
elif self.args.stdout:
self.suppress_output = True
else:
self.suppress_output = False
yield
if self.args.check and self.diffs_detected:
sys.exit(1)
else:
sys.exit(0)
@@ -0,0 +1,120 @@
# util/topological.py
# Copyright (C) 2005-2026 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
"""Topological sorting algorithms."""
from __future__ import annotations
from typing import Any
from typing import Collection
from typing import DefaultDict
from typing import Iterable
from typing import Iterator
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import TypeVar
from .. import util
from ..exc import CircularDependencyError
_T = TypeVar("_T", bound=Any)
__all__ = ["sort", "sort_as_subsets", "find_cycles"]
def sort_as_subsets(
tuples: Collection[Tuple[_T, _T]], allitems: Collection[_T]
) -> Iterator[Sequence[_T]]:
edges: DefaultDict[_T, Set[_T]] = util.defaultdict(set)
for parent, child in tuples:
edges[child].add(parent)
todo = list(allitems)
todo_set = set(allitems)
while todo_set:
output = []
for node in todo:
if todo_set.isdisjoint(edges[node]):
output.append(node)
if not output:
raise CircularDependencyError(
"Circular dependency detected.",
find_cycles(tuples, allitems),
_gen_edges(edges),
)
todo_set.difference_update(output)
todo = [t for t in todo if t in todo_set]
yield output
def sort(
tuples: Collection[Tuple[_T, _T]],
allitems: Collection[_T],
deterministic_order: bool = True,
) -> Iterator[_T]:
"""sort the given list of items by dependency.
'tuples' is a list of tuples representing a partial ordering.
deterministic_order is no longer used, the order is now always
deterministic given the order of "allitems". the flag is there
for backwards compatibility with Alembic.
"""
for set_ in sort_as_subsets(tuples, allitems):
yield from set_
def find_cycles(
tuples: Iterable[Tuple[_T, _T]], allitems: Iterable[_T]
) -> Set[_T]:
# adapted from:
# https://neopythonic.blogspot.com/2009/01/detecting-cycles-in-directed-graph.html
edges: DefaultDict[_T, Set[_T]] = util.defaultdict(set)
for parent, child in tuples:
edges[parent].add(child)
nodes_to_test = set(edges)
output = set()
# we'd like to find all nodes that are
# involved in cycles, so we do the full
# pass through the whole thing for each
# node in the original list.
# we can go just through parent edge nodes.
# if a node is only a child and never a parent,
# by definition it can't be part of a cycle. same
# if it's not in the edges at all.
for node in nodes_to_test:
stack = [node]
todo = nodes_to_test.difference(stack)
while stack:
top = stack[-1]
for node in edges[top]:
if node in stack:
cyc = stack[stack.index(node) :]
todo.difference_update(cyc)
output.update(cyc)
if node in todo:
stack.append(node)
todo.remove(node)
break
else:
stack.pop()
return output
def _gen_edges(edges: DefaultDict[_T, Set[_T]]) -> Set[Tuple[_T, _T]]:
return {(right, left) for left in edges for right in edges[left]}
@@ -0,0 +1,709 @@
# util/typing.py
# Copyright (C) 2022-2026 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
# mypy: allow-untyped-defs, allow-untyped-calls
from __future__ import annotations
import builtins
from collections import deque
import collections.abc as collections_abc
import re
import sys
from types import NoneType
import typing
from typing import Any
from typing import Callable
from typing import Dict
from typing import ForwardRef
from typing import Generic
from typing import get_args
from typing import get_origin
from typing import Iterable
from typing import Literal
from typing import Mapping
from typing import NewType
from typing import NoReturn
from typing import Optional
from typing import overload
from typing import Protocol
from typing import Set
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeGuard
from typing import Union
import typing_extensions
if True: # zimports removes the tailing comments
from typing_extensions import (
dataclass_transform as dataclass_transform, # 3.11,
)
from typing_extensions import NotRequired as NotRequired # 3.11
from typing_extensions import TypeVarTuple as TypeVarTuple # 3.11
from typing_extensions import Self as Self # 3.11
from typing_extensions import TypeAliasType as TypeAliasType # 3.12
from typing_extensions import Unpack as Unpack # 3.11
from typing_extensions import Never as Never # 3.11
from typing_extensions import LiteralString as LiteralString # 3.11
from typing_extensions import TypeVar as TypeVar # 3.13 for default
_T = TypeVar("_T", bound=Any)
_KT = TypeVar("_KT")
_VT = TypeVar("_VT")
_VT_co = TypeVar("_VT_co", covariant=True)
TupleAny = Tuple[Any, ...]
def is_fwd_none(typ: Any) -> bool:
return isinstance(typ, ForwardRef) and typ.__forward_arg__ == "None"
_AnnotationScanType = Union[
Type[Any], str, ForwardRef, NewType, TypeAliasType, "GenericProtocol[Any]"
]
_MatchedOnType = Union[
"GenericProtocol[Any]", TypeAliasType, NewType, Type[Any]
]
class ArgsTypeProtocol(Protocol):
"""protocol for types that have ``__args__``
there's no public interface for this AFAIK
"""
__args__: Tuple[_AnnotationScanType, ...]
class GenericProtocol(Protocol[_T]):
"""protocol for generic types.
this since Python.typing _GenericAlias is private
"""
__args__: Tuple[_AnnotationScanType, ...]
__origin__: Type[_T]
# Python's builtin _GenericAlias has this method, however builtins like
# list, dict, etc. do not, even though they have ``__origin__`` and
# ``__args__``
#
# def copy_with(self, params: Tuple[_AnnotationScanType, ...]) -> Type[_T]:
# ...
# copied from TypeShed, required in order to implement
# MutableMapping.update()
class SupportsKeysAndGetItem(Protocol[_KT, _VT_co]):
def keys(self) -> Iterable[_KT]: ...
def __getitem__(self, __k: _KT) -> _VT_co: ...
# work around https://github.com/microsoft/pyright/issues/3025
_LiteralStar = Literal["*"]
def de_stringify_annotation(
cls: Type[Any],
annotation: _AnnotationScanType,
originating_module: str,
locals_: Mapping[str, Any],
*,
str_cleanup_fn: Optional[Callable[[str, str], str]] = None,
include_generic: bool = False,
_already_seen: Optional[Set[Any]] = None,
) -> Type[Any]:
"""Resolve annotations that may be string based into real objects.
This is particularly important if a module defines "from __future__ import
annotations", as everything inside of __annotations__ is a string. We want
to at least have generic containers like ``Mapped``, ``Union``, ``List``,
etc.
"""
# looked at typing.get_type_hints(), looked at pydantic. We need much
# less here, and we here try to not use any private typing internals
# or construct ForwardRef objects which is documented as something
# that should be avoided.
original_annotation = annotation
if is_fwd_ref(annotation):
annotation = annotation.__forward_arg__
if isinstance(annotation, str):
if str_cleanup_fn:
annotation = str_cleanup_fn(annotation, originating_module)
annotation = eval_expression(
annotation, originating_module, locals_=locals_, in_class=cls
)
if (
include_generic
and is_generic(annotation)
and not is_literal(annotation)
):
if _already_seen is None:
_already_seen = set()
if annotation in _already_seen:
# only occurs recursively. outermost return type
# will always be Type.
# the element here will be either ForwardRef or
# Optional[ForwardRef]
return original_annotation # type: ignore[return-value]
else:
_already_seen.add(annotation)
elements = tuple(
de_stringify_annotation(
cls,
elem,
originating_module,
locals_,
str_cleanup_fn=str_cleanup_fn,
include_generic=include_generic,
_already_seen=_already_seen,
)
for elem in annotation.__args__
)
return _copy_generic_annotation_with(annotation, elements)
return annotation # type: ignore[return-value]
def fixup_container_fwd_refs(
type_: _AnnotationScanType,
) -> _AnnotationScanType:
"""Correct dict['x', 'y'] into dict[ForwardRef('x'), ForwardRef('y')]
and similar for list, set
"""
if (
is_generic(type_)
and get_origin(type_)
in (
dict,
set,
list,
collections_abc.MutableSet,
collections_abc.MutableMapping,
collections_abc.MutableSequence,
collections_abc.Mapping,
collections_abc.Sequence,
)
# fight, kick and scream to struggle to tell the difference between
# dict[] and typing.Dict[] which DO NOT compare the same and DO NOT
# behave the same yet there is NO WAY to distinguish between which type
# it is using public attributes
and not re.match(
"typing.(?:Dict|List|Set|.*Mapping|.*Sequence|.*Set)", repr(type_)
)
):
# compat with py3.10 and earlier
return get_origin(type_).__class_getitem__( # type: ignore[no-any-return, union-attr] # noqa: E501
tuple(
[
ForwardRef(elem) if isinstance(elem, str) else elem
for elem in get_args(type_)
]
)
)
return type_
def _copy_generic_annotation_with(
annotation: GenericProtocol[_T], elements: Tuple[_AnnotationScanType, ...]
) -> Type[_T]:
if hasattr(annotation, "copy_with"):
# List, Dict, etc. real generics
return annotation.copy_with(elements) # type: ignore[no-any-return]
else:
# Python builtins list, dict, etc.
return annotation.__origin__[elements] # type: ignore[no-any-return]
def eval_expression(
expression: str,
module_name: str,
*,
locals_: Optional[Mapping[str, Any]] = None,
in_class: Optional[Type[Any]] = None,
) -> Any:
try:
base_globals: Dict[str, Any] = sys.modules[module_name].__dict__
except KeyError as ke:
raise NameError(
f"Module {module_name} isn't present in sys.modules; can't "
f"evaluate expression {expression}"
) from ke
try:
if in_class is not None:
cls_namespace = dict(in_class.__dict__)
cls_namespace.setdefault(in_class.__name__, in_class)
# see #10899. We want the locals/globals to take precedence
# over the class namespace in this context, even though this
# is not the usual way variables would resolve.
cls_namespace.update(base_globals)
annotation = eval(expression, cls_namespace, locals_)
else:
annotation = eval(expression, base_globals, locals_)
except Exception as err:
raise NameError(
f"Could not de-stringify annotation {expression!r}"
) from err
else:
return annotation
def eval_name_only(
name: str,
module_name: str,
*,
locals_: Optional[Mapping[str, Any]] = None,
) -> Any:
if "." in name:
return eval_expression(name, module_name, locals_=locals_)
try:
base_globals: Dict[str, Any] = sys.modules[module_name].__dict__
except KeyError as ke:
raise NameError(
f"Module {module_name} isn't present in sys.modules; can't "
f"resolve name {name}"
) from ke
# name only, just look in globals. eval() works perfectly fine here,
# however we are seeking to have this be faster, as this occurs for
# every Mapper[] keyword, etc. depending on configuration
try:
return base_globals[name]
except KeyError as ke:
# check in builtins as well to handle `list`, `set` or `dict`, etc.
try:
return builtins.__dict__[name]
except KeyError:
pass
raise NameError(
f"Could not locate name {name} in module {module_name}"
) from ke
def resolve_name_to_real_class_name(name: str, module_name: str) -> str:
try:
obj = eval_name_only(name, module_name)
except NameError:
return name
else:
return getattr(obj, "__name__", name)
def is_pep593(type_: Optional[Any]) -> bool:
return type_ is not None and get_origin(type_) in _type_tuples.Annotated
def is_non_string_iterable(obj: Any) -> TypeGuard[Iterable[Any]]:
return isinstance(obj, collections_abc.Iterable) and not isinstance(
obj, (str, bytes)
)
def is_literal(type_: Any) -> bool:
return get_origin(type_) in _type_tuples.Literal
def is_newtype(type_: Optional[_AnnotationScanType]) -> TypeGuard[NewType]:
return isinstance(type_, _type_tuples.NewType)
def is_generic(type_: _AnnotationScanType) -> TypeGuard[GenericProtocol[Any]]:
return hasattr(type_, "__args__") and hasattr(type_, "__origin__")
def is_pep695(type_: _AnnotationScanType) -> TypeGuard[TypeAliasType]:
# NOTE: a generic TAT does not instance check as TypeAliasType outside of
# python 3.10. For sqlalchemy use cases it's fine to consider it a TAT
# though.
# NOTE: things seems to work also without this additional check
if is_generic(type_):
if is_pep593(type_):
return False
return is_pep695(type_.__origin__)
return isinstance(type_, _type_instances.TypeAliasType)
def pep695_values(type_: _AnnotationScanType) -> Set[Any]:
"""Extracts the value from a TypeAliasType, recursively exploring unions
and inner TypeAliasType to flatten them into a single set.
Forward references are not evaluated, so no recursive exploration happens
into them.
"""
_seen = set()
def recursive_value(inner_type):
if inner_type in _seen:
# recursion are not supported (at least it's flagged as
# an error by pyright). Just avoid infinite loop
return inner_type
_seen.add(inner_type)
if not is_pep695(inner_type):
return inner_type
value = inner_type.__value__
if not is_union(value):
return value
return [recursive_value(t) for t in value.__args__]
res = recursive_value(type_)
if isinstance(res, list):
types = set()
stack = deque(res)
while stack:
t = stack.popleft()
if isinstance(t, list):
stack.extend(t)
else:
types.add(None if t is NoneType or is_fwd_none(t) else t)
return types
else:
return {res}
@overload
def is_fwd_ref(
type_: _AnnotationScanType,
check_generic: bool = ...,
check_for_plain_string: Literal[False] = ...,
) -> TypeGuard[ForwardRef]: ...
@overload
def is_fwd_ref(
type_: _AnnotationScanType,
check_generic: bool = ...,
check_for_plain_string: bool = ...,
) -> TypeGuard[Union[str, ForwardRef]]: ...
def is_fwd_ref(
type_: _AnnotationScanType,
check_generic: bool = False,
check_for_plain_string: bool = False,
) -> TypeGuard[Union[str, ForwardRef]]:
if check_for_plain_string and isinstance(type_, str):
return True
elif isinstance(type_, _type_instances.ForwardRef):
return True
elif check_generic and is_generic(type_):
return any(
is_fwd_ref(
arg, True, check_for_plain_string=check_for_plain_string
)
for arg in type_.__args__
)
else:
return False
@overload
def de_optionalize_union_types(type_: str) -> str: ...
@overload
def de_optionalize_union_types(type_: Type[Any]) -> Type[Any]: ...
@overload
def de_optionalize_union_types(type_: _MatchedOnType) -> _MatchedOnType: ...
@overload
def de_optionalize_union_types(
type_: _AnnotationScanType,
) -> _AnnotationScanType: ...
def de_optionalize_union_types(
type_: _AnnotationScanType,
) -> _AnnotationScanType:
"""Given a type, filter out ``Union`` types that include ``NoneType``
to not include the ``NoneType``.
"""
if is_fwd_ref(type_):
return _de_optionalize_fwd_ref_union_types(type_, False)
elif is_union(type_) and includes_none(type_):
typ = {
t
for t in type_.__args__
if t is not NoneType and not is_fwd_none(t)
}
return make_union_type(*typ)
else:
return type_
@overload
def _de_optionalize_fwd_ref_union_types(
type_: ForwardRef, return_has_none: Literal[True]
) -> bool: ...
@overload
def _de_optionalize_fwd_ref_union_types(
type_: ForwardRef, return_has_none: Literal[False]
) -> _AnnotationScanType: ...
def _de_optionalize_fwd_ref_union_types(
type_: ForwardRef, return_has_none: bool
) -> Union[_AnnotationScanType, bool]:
"""return the non-optional type for Optional[], Union[None, ...], x|None,
etc. without de-stringifying forward refs.
unfortunately this seems to require lots of hardcoded heuristics
"""
annotation = type_.__forward_arg__
mm = re.match(r"^(.+?)\[(.+)\]$", annotation)
if mm:
g1 = mm.group(1).split(".")[-1]
if g1 == "Optional":
return True if return_has_none else ForwardRef(mm.group(2))
elif g1 == "Union":
if "[" in mm.group(2):
# cases like "Union[Dict[str, int], int, None]"
elements: list[str] = []
current: list[str] = []
ignore_comma = 0
for char in mm.group(2):
if char == "[":
ignore_comma += 1
elif char == "]":
ignore_comma -= 1
elif ignore_comma == 0 and char == ",":
elements.append("".join(current).strip())
current.clear()
continue
current.append(char)
else:
elements = re.split(r",\s*", mm.group(2))
parts = [ForwardRef(elem) for elem in elements if elem != "None"]
if return_has_none:
return len(elements) != len(parts)
else:
return make_union_type(*parts) if parts else Never # type: ignore[return-value] # noqa: E501
else:
return False if return_has_none else type_
pipe_tokens = re.split(r"\s*\|\s*", annotation)
has_none = "None" in pipe_tokens
if return_has_none:
return has_none
if has_none:
anno_str = "|".join(p for p in pipe_tokens if p != "None")
return ForwardRef(anno_str) if anno_str else Never # type: ignore[return-value] # noqa: E501
return type_
def make_union_type(*types: _AnnotationScanType) -> Type[Any]:
"""Make a Union type."""
return Union[types] # type: ignore[return-value]
def includes_none(type_: Any) -> bool:
"""Returns if the type annotation ``type_`` allows ``None``.
This function supports:
* forward refs
* unions
* pep593 - Annotated
* pep695 - TypeAliasType (does not support looking into
fw reference of other pep695)
* NewType
* plain types like ``int``, ``None``, etc
"""
if is_fwd_ref(type_):
return _de_optionalize_fwd_ref_union_types(type_, True)
if is_union(type_):
return any(includes_none(t) for t in get_args(type_))
if is_pep593(type_):
return includes_none(get_args(type_)[0])
if is_pep695(type_):
return any(includes_none(t) for t in pep695_values(type_))
if is_newtype(type_):
return includes_none(type_.__supertype__)
try:
return type_ in (NoneType, None) or is_fwd_none(type_)
except TypeError:
# if type_ is Column, mapped_column(), etc. the use of "in"
# resolves to ``__eq__()`` which then gives us an expression object
# that can't resolve to boolean. just catch it all via exception
return False
def is_a_type(type_: Any) -> bool:
return (
isinstance(type_, type)
or get_origin(type_) is not None
or getattr(type_, "__module__", None)
in ("typing", "typing_extensions")
or type(type_).__mro__[0].__module__ in ("typing", "typing_extensions")
)
def is_union(type_: Any) -> TypeGuard[ArgsTypeProtocol]:
return is_origin_of(type_, "Union", "UnionType")
def is_origin_of_cls(
type_: Any, class_obj: Union[Tuple[Type[Any], ...], Type[Any]]
) -> bool:
"""return True if the given type has an __origin__ that shares a base
with the given class"""
origin = get_origin(type_)
if origin is None:
return False
return isinstance(origin, type) and issubclass(origin, class_obj)
def is_origin_of(
type_: Any, *names: str, module: Optional[str] = None
) -> bool:
"""return True if the given type has an __origin__ with the given name
and optional module."""
origin = get_origin(type_)
if origin is None:
return False
return origin.__name__ in names and (
module is None or origin.__module__.startswith(module)
)
class DescriptorProto(Protocol):
def __get__(self, instance: object, owner: Any) -> Any: ...
def __set__(self, instance: Any, value: Any) -> None: ...
def __delete__(self, instance: Any) -> None: ...
_DESC = TypeVar("_DESC", bound=DescriptorProto)
class DescriptorReference(Generic[_DESC]):
"""a descriptor that refers to a descriptor.
used for cases where we need to have an instance variable referring to an
object that is itself a descriptor, which typically confuses typing tools
as they don't know when they should use ``__get__`` or not when referring
to the descriptor assignment as an instance variable. See
sqlalchemy.orm.interfaces.PropComparator.prop
"""
if TYPE_CHECKING:
def __get__(self, instance: object, owner: Any) -> _DESC: ...
def __set__(self, instance: Any, value: _DESC) -> None: ...
def __delete__(self, instance: Any) -> None: ...
_DESC_co = TypeVar("_DESC_co", bound=DescriptorProto, covariant=True)
class RODescriptorReference(Generic[_DESC_co]):
"""a descriptor that refers to a descriptor.
same as :class:`.DescriptorReference` but is read-only, so that subclasses
can define a subtype as the generically contained element
"""
if TYPE_CHECKING:
def __get__(self, instance: object, owner: Any) -> _DESC_co: ...
def __set__(self, instance: Any, value: Any) -> NoReturn: ...
def __delete__(self, instance: Any) -> NoReturn: ...
_FN = TypeVar("_FN", bound=Optional[Callable[..., Any]])
class CallableReference(Generic[_FN]):
"""a descriptor that refers to a callable.
works around mypy's limitation of not allowing callables assigned
as instance variables
"""
if TYPE_CHECKING:
def __get__(self, instance: object, owner: Any) -> _FN: ...
def __set__(self, instance: Any, value: _FN) -> None: ...
def __delete__(self, instance: Any) -> None: ...
class _TypingInstances:
def __getattr__(self, key: str) -> tuple[type, ...]:
types = tuple(
{
t
for t in [
getattr(typing, key, None),
getattr(typing_extensions, key, None),
]
if t is not None
}
)
if not types:
raise AttributeError(key)
self.__dict__[key] = types
return types
_type_tuples = _TypingInstances()
if TYPE_CHECKING:
_type_instances = typing_extensions
else:
_type_instances = _type_tuples
LITERAL_TYPES = _type_tuples.Literal