From d216a380040ab9a79c83b05d97fa1d6312065b28 Mon Sep 17 00:00:00 2001 From: omid zarinmahd Date: Sat, 26 Sep 2026 23:20:38 +0330 Subject: [PATCH] Fix: subjects. The staged change rewrites postgresql:// and postgres:// to postgresql+psycopg2 before create_engine, covers that in tests, and bumps the version to 6.1.1. --- tests/test_orm.py | 52 ++++++++++++++++++++++++++++++++ yhttp/ext/sqlalchemy/__init__.py | 2 +- yhttp/ext/sqlalchemy/orm.py | 9 +++++- 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 tests/test_orm.py diff --git a/tests/test_orm.py b/tests/test_orm.py new file mode 100644 index 0000000..80757b3 --- /dev/null +++ b/tests/test_orm.py @@ -0,0 +1,52 @@ +import pytest + +from yhttp.ext.sqlalchemy.orm import ORM + + +def _engine_url(monkeypatch, url): + seen = {} + + def fake_create_engine(target, **kwargs): + seen['url'] = target + seen['kwargs'] = kwargs + + class Engine: + def dispose(self): + return None + + return Engine() + + monkeypatch.setattr( + 'yhttp.ext.sqlalchemy.orm.create_engine', + fake_create_engine, + ) + orm = ORM(object()) + orm.connect(url) + try: + return seen + finally: + orm.disconnect() + + +@pytest.mark.parametrize('url', ( + 'postgresql://user:pass@localhost:5432/dbname?sslmode=require', + 'postgres://user:pass@localhost:5432/dbname?sslmode=require', +)) +def test_driverless_postgresql_uses_psycopg2(monkeypatch, url): + seen = _engine_url(monkeypatch, url) + parsed = seen['url'] + + assert parsed.drivername == 'postgresql+psycopg2' + + +@pytest.mark.parametrize('url', ( + 'postgresql+psycopg2://user:pass@localhost/dbname', + 'postgresql+psycopg://user:pass@localhost/dbname', + 'postgresql+asyncpg://user:pass@localhost/dbname', +)) +def test_explicit_postgresql_driver_unchanged(monkeypatch, url): + seen = _engine_url(monkeypatch, url) + + assert seen['url'].drivername == url.split(':', 1)[0] + assert seen['url'].username == 'user' + assert seen['url'].database == 'dbname' diff --git a/yhttp/ext/sqlalchemy/__init__.py b/yhttp/ext/sqlalchemy/__init__.py index b053c69..549703a 100644 --- a/yhttp/ext/sqlalchemy/__init__.py +++ b/yhttp/ext/sqlalchemy/__init__.py @@ -1,4 +1,4 @@ from .install import install -__version__ = '6.1.0' +__version__ = '6.1.1' diff --git a/yhttp/ext/sqlalchemy/orm.py b/yhttp/ext/sqlalchemy/orm.py index 447b886..dad3603 100644 --- a/yhttp/ext/sqlalchemy/orm.py +++ b/yhttp/ext/sqlalchemy/orm.py @@ -1,4 +1,5 @@ from sqlalchemy import create_engine +from sqlalchemy.engine import make_url from sqlalchemy.orm import sessionmaker, close_all_sessions, Session, \ scoped_session @@ -22,8 +23,14 @@ def connect(self, url=None): assert self.engine is None assert u is not None + parsed = make_url(u) + if parsed.drivername in ('postgresql', 'postgres'): + # SQLAlchemy 2.1 loads psycopg 3 for a driverless URL. + # https://docs.sqlalchemy.org/en/21/changelog/migration_21.html#default-postgresql-driver-changed-to-psycopg-psycopg-3 # noqa: E501 + parsed = parsed.set(drivername='postgresql+psycopg2') + self.engine = create_engine( - u, + parsed, isolation_level='REPEATABLE READ', plugins=self.saplugins or [], )