--- /dev/null
+.. change::
+ :tags: bug, orm
+ :tickets: 13104
+
+ Fixed issue when using ORM mappings with Python 3.14's :pep:`649` feature
+ that no longer requires "future annotations", where the ORM's introspection
+ of the ``__init__`` method of mapped classes would fail if non-present
+ identifiers in annotations were present. The vendored ``getfullargspec()``
+ method has been amended to use ``Format.FORWARDREF`` under Python 3.14 to
+ prevent resolution of names that aren't present.
+
lambda: util.py314, "Python 3.14 or above not supported"
)
+ @property
+ def pep649(self):
+ """pep649 deferred evaluation of annotations without future mode"""
+ return self.python314
+
@property
def cpython(self):
return exclusions.only_if(
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 .langhelpers import format_argspec_plus as format_argspec_plus
from .langhelpers import generic_fn_descriptor as generic_fn_descriptor
from .langhelpers import generic_repr as generic_repr
-from .langhelpers import get_annotations as get_annotations
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
return iter(self._parts)
+if py314:
+
+ import annotationlib
+
+ 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 FullArgSpec(typing.NamedTuple):
args: List[str]
varargs: Optional[str]
defaults: Optional[Tuple[Any, ...]]
kwonlyargs: List[str]
kwonlydefaults: Optional[Dict[str, Any]]
- annotations: Dict[str, Any]
+ annotations: Mapping[str, Any]
def inspect_getfullargspec(func: Callable[..., Any]) -> FullArgSpec:
func.__defaults__,
kwonlyargs,
func.__kwdefaults__,
- func.__annotations__,
+ get_annotations(func),
)
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
_MA = TypeVar("_MA", bound="HasMemoized.memoized_attribute[Any]")
_M = TypeVar("_M", bound=ModuleType)
-if compat.py314:
-
- import annotationlib
-
- 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)
-
def restore_annotations(
cls: type, new_annotations: dict[str, Any]
import typing_extensions
from sqlalchemy import Column
+from sqlalchemy import util
from sqlalchemy.testing import eq_
from sqlalchemy.testing import fixtures
from sqlalchemy.testing import is_
types.add(lt)
is_(lt in ti, True)
eq_(len(ti), len(types), k)
+
+ @requires.pep649
+ def test_pep649_getfullargspec(self):
+ """test for #13104"""
+
+ def foo(x: Frobnizzle): # type: ignore # noqa: F821
+ pass
+
+ anno = util.get_annotations(foo)
+ eq_(
+ util.inspect_getfullargspec(foo),
+ util.compat.FullArgSpec(
+ args=["x"],
+ varargs=None,
+ varkw=None,
+ defaults=None,
+ kwonlyargs=[],
+ kwonlydefaults=None,
+ annotations=anno,
+ ),
+ )
else:
inh_type.fail()
+ @requires.pep649
+ def test_pep649_init(self, decl_base):
+
+ class A(decl_base):
+ __tablename__ = "a"
+
+ id: Mapped[int] = mapped_column(
+ BigInteger, Identity(always=True), primary_key=True
+ )
+
+ bs: Mapped[list[B]] = relationship(back_populates="a")
+
+ def __init__(self, bs: list[B], *args, **kwargs):
+ super().__init__(*args, bs=bs, **kwargs)
+
+ class B(decl_base):
+ __tablename__ = "b"
+
+ id: Mapped[int] = mapped_column(
+ BigInteger, Identity(always=True), primary_key=True
+ )
+
+ a_id: Mapped[int] = mapped_column(ForeignKey("a.id"))
+ a: Mapped[A] = relationship(back_populates="bs")
+
class WriteOnlyRelationshipTest(fixtures.TestBase):
def _assertions(self, A, B, lazy):
else:
inh_type.fail()
+ @requires.pep649
+ def test_pep649_init(self, decl_base):
+
+ class A(decl_base):
+ __tablename__ = "a"
+
+ id: Mapped[int] = mapped_column(
+ BigInteger, Identity(always=True), primary_key=True
+ )
+
+ bs: Mapped[list[B]] = relationship(back_populates="a")
+
+ def __init__(self, bs: list[B], *args, **kwargs):
+ super().__init__(*args, bs=bs, **kwargs)
+
+ class B(decl_base):
+ __tablename__ = "b"
+
+ id: Mapped[int] = mapped_column(
+ BigInteger, Identity(always=True), primary_key=True
+ )
+
+ a_id: Mapped[int] = mapped_column(ForeignKey("a.id"))
+ a: Mapped[A] = relationship(back_populates="bs")
+
class WriteOnlyRelationshipTest(fixtures.TestBase):
def _assertions(self, A, B, lazy):