from .base import NO_KEY
from .. import exc as sa_exc
from .. import util
+from ..sql.base import NO_ARG
from ..util.compat import inspect_getfullargspec
from ..util.typing import Protocol
fn._sa_instrumented = True
fn.__doc__ = getattr(dict, fn.__name__).__doc__
- Unspecified = util.symbol("Unspecified")
-
def __setitem__(fn):
def __setitem__(self, key, value, _sa_initiator=None):
if key in self:
return clear
def pop(fn):
- def pop(self, key, default=Unspecified):
+ def pop(self, key, default=NO_ARG):
__before_pop(self)
_to_del = key in self
- if default is Unspecified:
+ if default is NO_ARG:
item = fn(self, key)
else:
item = fn(self, key, default)
return setdefault
def update(fn):
- def update(self, __other=Unspecified, **kw):
- if __other is not Unspecified:
+ def update(self, __other=NO_ARG, **kw):
+ if __other is not NO_ARG:
if hasattr(__other, "keys"):
for key in list(__other):
if key not in self or self[key] is not __other[key]:
l = locals().copy()
l.pop("_tidy")
- l.pop("Unspecified")
return l
fn._sa_instrumented = True
fn.__doc__ = getattr(set, fn.__name__).__doc__
- Unspecified = util.symbol("Unspecified")
-
def add(fn):
def add(self, value, _sa_initiator=None):
if value not in self:
l = locals().copy()
l.pop("_tidy")
- l.pop("Unspecified")
return l
Repeated calls of symbol('name') will all return the same instance.
- In SQLAlchemy 2.0, symbol() is used for the implementation of
- ``_FastIntFlag``, but otherwise should be mostly replaced by
- ``enum.Enum`` and variants.
-
-
"""
name: str
if doc:
sym.__doc__ = doc
+ # NOTE: we should ultimately get rid of this global thing,
+ # however, currently it is to support pickling. The best
+ # change would be when we are on py3.11 at a minimum, we
+ # switch to stdlib enum.IntFlag.
cls.symbols[name] = sym
+ else:
+ if canonical and canonical != sym:
+ raise TypeError(
+ f"Can't replace canonical symbol for {name} "
+ f"with new int value {canonical}"
+ )
return sym
def __reduce__(self):
setattr(cls, k, sym)
items.append(sym)
+ cls.__members__ = _collections.immutabledict(
+ {sym.name: sym for sym in items}
+ )
+
def __iter__(self) -> Iterator[symbol]:
- return iter(self._items)
+ raise NotImplementedError(
+ "iter not implemented to ensure compatibility with "
+ "Python 3.11 IntFlag. Please use __members__. See "
+ "https://github.com/python/cpython/issues/99304"
+ )
class _FastIntFlag(metaclass=_IntFlagMeta):
def test_fast_int_flag(self):
class Enum(FastIntFlag):
- sym1 = 1
- sym2 = 2
+ fi_sym1 = 1
+ fi_sym2 = 2
+
+ fi_sym3 = 3
+
+ assert Enum.fi_sym1 is not Enum.fi_sym3
+ assert Enum.fi_sym1 != Enum.fi_sym3
+
+ assert Enum.fi_sym1.name == "fi_sym1"
+
+ # modified for #8783
+ eq_(
+ list(Enum.__members__.values()),
+ [Enum.fi_sym1, Enum.fi_sym2, Enum.fi_sym3],
+ )
+
+ def test_fast_int_flag_still_global(self):
+ """FastIntFlag still causes elements to be global symbols.
+
+ This is to support pickling. There are likely other ways to
+ achieve this, however this is what we have for now.
+
+ """
+
+ class Enum1(FastIntFlag):
+ fi_sym1 = 1
+ fi_sym2 = 2
+
+ class Enum2(FastIntFlag):
+ fi_sym1 = 1
+ fi_sym2 = 2
+
+ # they are global
+ assert Enum1.fi_sym1 is Enum2.fi_sym1
+
+ def test_fast_int_flag_dont_allow_conflicts(self):
+ """FastIntFlag still causes elements to be global symbols.
+
+ While we do this and haven't yet changed it, make sure conflicting
+ int values for the same name don't come in.
+
+ """
- sym3 = 3
+ class Enum1(FastIntFlag):
+ fi_sym1 = 1
+ fi_sym2 = 2
- assert Enum.sym1 is not Enum.sym3
- assert Enum.sym1 != Enum.sym3
+ with expect_raises_message(
+ TypeError,
+ "Can't replace canonical symbol for fi_sym1 with new int value 2",
+ ):
+
+ class Enum2(FastIntFlag):
+ fi_sym1 = 2
+ fi_sym2 = 3
+
+ @testing.combinations("native", "ours", argnames="native")
+ def test_compare_to_native_py_intflag(self, native):
+ """monitor IntFlag behavior in upstream Python for #8783"""
+
+ if native == "native":
+ from enum import IntFlag
+ else:
+ from sqlalchemy.util import FastIntFlag as IntFlag
+
+ class Enum(IntFlag):
+ fi_sym1 = 1
+ fi_sym2 = 2
+ fi_sym4 = 4
+
+ fi_sym1plus2 = 3
- assert Enum.sym1.name == "sym1"
+ # not an alias because there's no 16
+ fi_sym17 = 17
- eq_(list(Enum), [Enum.sym1, Enum.sym2, Enum.sym3])
+ sym1, sym2, sym4, sym1plus2, sym17 = Enum.__members__.values()
+ eq_(
+ [sym1, sym2, sym4, sym1plus2, sym17],
+ [
+ Enum.fi_sym1,
+ Enum.fi_sym2,
+ Enum.fi_sym4,
+ Enum.fi_sym1plus2,
+ Enum.fi_sym17,
+ ],
+ )
def test_pickle(self):
sym1 = util.symbol("foo")
assert not (sym1 | sym2) & (sym3 | sym4)
assert (sym1 | sym2) & (sym2 | sym4)
+ def test_fast_int_flag_no_more_iter(self):
+ """test #8783"""
+
+ class MyEnum(FastIntFlag):
+ sym1 = 1
+ sym2 = 2
+ sym3 = 4
+ sym4 = 8
+
+ with expect_raises_message(
+ NotImplementedError, "iter not implemented to ensure compatibility"
+ ):
+ list(MyEnum)
+
def test_parser(self):
class MyEnum(FastIntFlag):
sym1 = 1
sym3 = 4
sym4 = 8
- sym1, sym2, sym3, sym4 = tuple(MyEnum)
+ sym1, sym2, sym3, sym4 = tuple(MyEnum.__members__.values())
lookup_one = {sym1: [], sym2: [True], sym3: [False], sym4: [None]}
lookup_two = {sym1: [], sym2: [True], sym3: [False]}
lookup_three = {sym1: [], sym2: ["symbol2"], sym3: []}