Repository: jupyterhub/jupyterhub

Issue:
SqlAlchemy warning - can we avoid it?
I saw the following from z2jh's test suite with the latest Python dependencies (jupyterhub 4.0.0b1) and considered this warning:

```
/usr/local/lib/python3.11/site-packages/jupyterhub/orm.py:423: SAWarning: The argument signature for the "ConnectionEvents.engine_connect" event listener has changed as of version 2.0, and conversion for the old argument signature will be removed in a future release.  The new signature is "def engine_connect(conn)" (This warning originated from the Session 'autoflush' process, which was invoked automatically in response to a user-initiated operation.)
  return db.query(cls).filter(cls.name == name).first()
```

Is this something that can be avoided? I seems to relate to sqlalchemy and perhaps specifically to this which mentions `engine_connect` at all

https://github.com/jupyterhub/jupyterhub/blob/f57d196e3338f4aa8ab3c53bad888541cbf2fb48/jupyterhub/orm.py#L895-L907

### JupyterHub startup logs until warning shows

```
[I 2023-03-01 12:36:13.363 JupyterHub app:2854] Running JupyterHub version 4.0.0b1
[I 2023-03-01 12:36:13.363 JupyterHub app:2884] Using Authenticator: jupyterhub.auth.DummyAuthenticator-4.0.0b1
[I 2023-03-01 12:36:13.363 JupyterHub app:2884] Using Spawner: kubespawner.spawner.KubeSpawner-4.3.0
[I 2023-03-01 12:36:13.363 JupyterHub app:2884] Using Proxy: jupyterhub.proxy.ConfigurableHTTPProxy-4.0.0b1
[D 2023-03-01 12:36:13.383 JupyterHub app:1832] Connecting to db: postgresql+psycopg2://postgres:[redacted]@postgresql:5432/jupyterhub
[I 2023-03-01 12:36:13.824 JupyterHub dbutil:130] Upgrading postgresql+psycopg2://postgres:[redacted]@postgresql:5432/jupyterhub
[I 2023-03-01 12:36:14.687 alembic.runtime.migration migration:205] Context impl PostgresqlImpl.
[I 2023-03-01 12:36:14.688 alembic.runtime.migration migration:208] Will assume transactional DDL.
[I 2023-03-01 12:36:14.696 alembic.runtime.migration migration:619] Running upgrade 4dc2d5a8c53c -> 833da8570507, rbac changes for jupyterhub 2.0
[I 2023-03-01 12:36:14.715 alembic.runtime.migration migration:619] Running upgrade 833da8570507 -> 651f5419b74d, api_token_scopes
[I 2023-03-01 12:36:14.754 alembic.runtime.migration migration:619] Running upgrade 651f5419b74d -> 0eee8c825d24, added properties column
[D 2023-03-01 12:36:14.912 JupyterHub orm:1002] database schema version found: 0eee8c825d24
[D 2023-03-01 12:36:15.000 JupyterHub app:2103] Loading roles into database
[I 2023-03-01 12:36:15.022 JupyterHub roles:172] Role jupyterhub-idle-culler added to database
[I 2023-03-01 12:36:15.025 JupyterHub roles:172] Role test-role-2 added to database
[I 2023-03-01 12:36:15.027 JupyterHub roles:172] Role test-scoped-access added to database
[I 2023-03-01 12:36:15.029 JupyterHub app:1983] Not using allowed_users. Any authenticated user will be allowed.
/usr/local/lib/python3.11/site-packages/jupyterhub/orm.py:423: SAWarning: The argument signature for the "ConnectionEvents.engine_connect" event listener has changed as of version 2.0, and conversion for the old argument signature will be removed in a future release.  The new signature is "def engine_connect(conn)" (This warning originated from the Session 'autoflush' process, which was invoked automatically in response to a user-initiated operation.)
  return db.query(cls).filter(cls.name == name).first()
```


Discussion:
I looked into it working on the sqla 2 compatibility, and I don't believe so. We already implement the new signature, but we can't stay compatible with 1.4 without encountering the warning.

Essentially, the warning is triggered incorrectly. The right action is either to ignore or suppress the warning because no code needs to change at any point.
Hm, I guess we could actually suppress the warning itself in JupyterHub, since we know it's wrong and where it's triggered.
The warning is emitted from here: https://github.com/sqlalchemy/sqlalchemy/blob/9d9e47fe9398b6bcda917f63441d2b5ad4b345c4/lib/sqlalchemy/event/legacy.py#L60-L115
Using utility functions here: https://github.com/sqlalchemy/sqlalchemy/blob/9d9e47fe9398b6bcda917f63441d2b5ad4b345c4/lib/sqlalchemy/util/deprecations.py#L42-L60
Relying on utility functions here: https://github.com/sqlalchemy/sqlalchemy/blob/9d9e47fe9398b6bcda917f63441d2b5ad4b345c4/lib/sqlalchemy/util/langhelpers.py#L1845-L1894
Relying on `import warnings` -> `warnings.warn` function: https://github.com/sqlalchemy/sqlalchemy/blob/9d9e47fe9398b6bcda917f63441d2b5ad4b345c4/lib/sqlalchemy/util/langhelpers.py#L1891-L1894

I've not worked with supressing warnings, but I see from [python official docs](https://docs.python.org/3/library/warnings.html#temporarily-suppressing-warnings) it seems possible using [`warnings.filterwarnings`](https://docs.python.org/3/library/warnings.html#warnings.filterwarnings).

If `SAWarning`, `argument signature`, and `engine_connect` shows up, I think we should be safe to ignore it.

Searching in the jupyterhub code base I see the following situations where warnings are supressed, only the first is in a non-test setting.

https://github.com/jupyterhub/jupyterhub/blob/f57d196e3338f4aa8ab3c53bad888541cbf2fb48/jupyterhub/singleuser/mixins.py#L623-L629

https://github.com/jupyterhub/jupyterhub/blob/f57d196e3338f4aa8ab3c53bad888541cbf2fb48/jupyterhub/tests/test_roles.py#L345-L350

Resolving patch:
diff --git a/jupyterhub/orm.py b/jupyterhub/orm.py
--- a/jupyterhub/orm.py
+++ b/jupyterhub/orm.py
@@ -8,6 +8,7 @@
 
 import alembic.command
 import alembic.config
+import sqlalchemy
 from alembic.script import ScriptDirectory
 from sqlalchemy import (
     Boolean,
@@ -902,10 +903,12 @@ def register_ping_connection(engine):
     https://docs.sqlalchemy.org/en/rel_1_1/core/pooling.html#disconnect-handling-pessimistic
     """
 
-    @event.listens_for(engine, "engine_connect")
-    def ping_connection(connection, branch=None):
-        # TODO: remove unused branch arg when we require sqlalchemy 2.0
-
+    # listeners are normally registered as a decorator,
+    # but we need two different signatures to avoid SAWarning:
+    #    The argument signature for the "ConnectionEvents.engine_connect" event listener has changed
+    # while we support sqla 1.4 and 2.0.
+    # @event.listens_for(engine, "engine_connect")
+    def ping_connection(connection):
         # turn off "close with result".  This flag is only used with
         # "connectionless" execution, otherwise will be False in any case
         save_should_close_with_result = connection.should_close_with_result
@@ -939,6 +942,17 @@ def ping_connection(connection, branch=None):
             # restore "close with result"
             connection.should_close_with_result = save_should_close_with_result
 
+    # sqla v1/v2 compatible invocation of @event.listens_for:
+    def ping_connection_v1(connection, branch=None):
+        """sqlalchemy < 2.0 compatibility"""
+        return ping_connection(connection)
+
+    if int(sqlalchemy.__version__.split(".", 1)[0]) >= 2:
+        listener = ping_connection
+    else:
+        listener = ping_connection_v1
+    event.listens_for(engine, "engine_connect")(listener)
+
 
 def check_db_revision(engine):
     """Check the JupyterHub database revision


Test patch: