sqlalchemy / sqlalchemy

The Database Toolkit for Python
https://www.sqlalchemy.org
MIT License
9.05k stars 1.36k forks source link

`sqlalchemy.utils.langhelpers.TypingOnly` too rigidly prevents special dunders from appearing #11334

Closed edgarrmondragon closed 1 month ago

edgarrmondragon commented 1 month ago

Describe the bug

An exception is raised on Python 3.13:

AssertionError: Class <class 'sqlalchemy.sql.elements.SQLCoreOperations'> directly inherits TypingOnly but has additional attributes {'__static_attributes__'}.

Let me know if this is too premature, specially since greenlet (a current dependency, but I think it'll be dropped for 2.1?) does not currently build on Python 3.13. Otherwise I can submit a quick PR to fix this.

Optional link from https://docs.sqlalchemy.org which documents the behavior that is expected

No response

SQLAlchemy Version in Use

main

DBAPI (i.e. the database driver)

pysqlite

Database Vendor and Major Version

SQLite

Python Version

3.13

Operating system

Linux

To Reproduce

from sqlalchemy.orm import mapped_column

Error

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    from sqlalchemy.orm import mapped_column
  File "/Mylib/.venv/lib/python3.13/site-packages/sqlalchemy/__init__.py", line 13, in <module>
    from .engine import AdaptedConnection as AdaptedConnection
  File "/Mylib/.venv/lib/python3.13/site-packages/sqlalchemy/engine/__init__.py", line 18, in <module>
    from . import events as events
  File "/Mylib/.venv/lib/python3.13/site-packages/sqlalchemy/engine/events.py", line 19, in <module>
    from .base import Connection
  File "/Mylib/.venv/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 30, in <module>
    from .interfaces import BindTyping
  File "/Mylib/.venv/lib/python3.13/site-packages/sqlalchemy/engine/interfaces.py", line 38, in <module>
    from ..sql.compiler import Compiled as Compiled
  File "/Mylib/.venv/lib/python3.13/site-packages/sqlalchemy/sql/__init__.py", line 14, in <module>
    from .compiler import COLLECT_CARTESIAN_PRODUCTS as COLLECT_CARTESIAN_PRODUCTS
  File "/Mylib/.venv/lib/python3.13/site-packages/sqlalchemy/sql/compiler.py", line 62, in <module>
    from . import crud
  File "/Mylib/.venv/lib/python3.13/site-packages/sqlalchemy/sql/crud.py", line 34, in <module>
    from . import dml
  File "/Mylib/.venv/lib/python3.13/site-packages/sqlalchemy/sql/dml.py", line 34, in <module>
    from . import util as sql_util
  File "/Mylib/.venv/lib/python3.13/site-packages/sqlalchemy/sql/util.py", line 46, in <module>
    from .ddl import sort_tables as sort_tables  # noqa: F401
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Mylib/.venv/lib/python3.13/site-packages/sqlalchemy/sql/ddl.py", line 30, in <module>
    from .elements import ClauseElement
  File "/Mylib/.venv/lib/python3.13/site-packages/sqlalchemy/sql/elements.py", line 806, in <module>
    class SQLCoreOperations(Generic[_T_co], ColumnOperators, TypingOnly):
    ...<372 lines>...
            def __rfloordiv__(self, other: Any) -> ColumnElement[Any]: ...
  File "/Users/me/.pyenv/versions/3.13.0a6/lib/python3.13/typing.py", line 1085, in _generic_init_subclass
    super(Generic, cls).__init_subclass__(*args, **kwargs)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/Mylib/.venv/lib/python3.13/site-packages/sqlalchemy/util/langhelpers.py", line 1981, in __init_subclass__
    raise AssertionError(
    ...<2 lines>...
    )
AssertionError: Class <class 'sqlalchemy.sql.elements.SQLCoreOperations'> directly inherits TypingOnly but has additional attributes {'__static_attributes__'}.

Additional context

zzzeek commented 1 month ago

I think TypingOnly should be changed as follows:

diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py
index fe3bd1684..87b3e486c 100644
--- a/lib/sqlalchemy/util/langhelpers.py
+++ b/lib/sqlalchemy/util/langhelpers.py
@@ -1967,13 +1967,7 @@ class TypingOnly:
     def __init_subclass__(cls) -> None:
         if TypingOnly in cls.__bases__:
             remaining = set(cls.__dict__).difference(
-                {
-                    "__module__",
-                    "__doc__",
-                    "__slots__",
-                    "__orig_bases__",
-                    "__annotations__",
-                }
+                {name for name in cls.__dict__ if re.match("^__.+__$", name)}
             )
             if remaining:
                 raise AssertionError(

I see you have a PR, let's do it like that. I dont think we should be hardcoding all the dunders here.

edgarrmondragon commented 1 month ago

Gotcha, that's better!

sqla-tester commented 1 month ago

Edgar Ramírez-Mondragón has proposed a fix for this issue in the main branch:

Ignore all dunders when checking attributes in sqlalchemy.util.langhelpers.TypingOnly https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/5287

sqla-tester commented 1 month ago

Edgar Ramírez-Mondragón has proposed a fix for this issue in the main branch:

Ignore all dunders when checking attributes in sqlalchemy.util.langhelpers.TypingOnly https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/5287

sqla-tester commented 1 month ago

Edgar Ramírez-Mondragón has proposed a fix for this issue in the rel_2_0 branch:

Ignore all dunders when checking attributes in sqlalchemy.util.langhelpers.TypingOnly https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/5273