Skip to content

Commit

Permalink
Merge pull request #753 from kurtmckee/rm-sqlalchemy-1.3-support
Browse files Browse the repository at this point in the history
Drop support for sqlalchemy 1.3
  • Loading branch information
kurtmckee authored Sep 6, 2024
2 parents ec84969 + 19a3295 commit a66ee64
Show file tree
Hide file tree
Showing 47 changed files with 77 additions and 267 deletions.
4 changes: 0 additions & 4 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ jobs:
- '3.12'
tox_env: ['sqlalchemy14', 'sqlalchemy2']
include:
# Test against Python 3.9 and sqlalchemy 1.3
- os: 'ubuntu-20.04'
python-version: '3.9'
tox_env: 'sqlalchemy13'
# Test against Python 3.9 and sqlalchemy 1.4
- os: 'ubuntu-20.04'
python-version: '3.9'
Expand Down
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Unreleased changes
^^^^^^^^^^^^^^^^^^

- Drop support for Python 3.7 and 3.8.
- Drop support for sqlalchemy 1.3.
- Add support for Python 3.12.
- Add a Read the Docs configuration file.
- Make documentation builds reproducible.
Expand Down
20 changes: 5 additions & 15 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import sqlalchemy.exc
from sqlalchemy import create_engine
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import declarative_base, sessionmaker, synonym_for
from sqlalchemy.orm.session import close_all_sessions

from sqlalchemy_utils import (
Expand All @@ -16,11 +16,6 @@
i18n,
InstrumentedList
)
from sqlalchemy_utils.compat import (
_declarative_base,
_select_args,
_synonym_for
)
from sqlalchemy_utils.functions.orm import _get_class_registry
from sqlalchemy_utils.types.pg_composite import remove_composite_listeners

Expand Down Expand Up @@ -154,7 +149,7 @@ def connection(engine):

@pytest.fixture
def Base():
return _declarative_base()
return declarative_base()


@pytest.fixture
Expand Down Expand Up @@ -191,7 +186,7 @@ def articles_count(self):
def articles_count(cls):
Article = _get_class_registry(Base)['Article']
return (
sa.select(*_select_args(sa.func.count(Article.id)))
sa.select(sa.func.count(Article.id))
.where(Article.category_id == cls.id)
.correlate(Article.__table__)
.label('article_count')
Expand All @@ -201,7 +196,7 @@ def articles_count(cls):
def name_alias(self):
return self.name

@_synonym_for('name')
@synonym_for('name')
@property
def name_synonym(self):
return self.name
Expand Down Expand Up @@ -238,12 +233,7 @@ def session(request, engine, connection, Base, init_models):
with connection.begin():
Base.metadata.create_all(connection)
Session = sessionmaker(bind=connection)
try:
# Enable sqlalchemy 2.0 behavior.
session = Session(future=True)
except TypeError:
# sqlalchemy 1.3
session = Session()
session = Session(future=True)
i18n.get_locale = get_locale

def teardown():
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def get_version():
include_package_data=True,
platforms='any',
install_requires=[
'SQLAlchemy>=1.3',
'SQLAlchemy>=1.4',
],
extras_require=extras_require,
python_requires='>=3.9',
Expand Down
5 changes: 2 additions & 3 deletions sqlalchemy_utils/aggregates.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,6 @@ class Rating(Base):
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.sql.functions import _FunctionGenerator

from .compat import _select_args, get_scalar_subquery
from .functions.orm import get_column_key
from .relationships import (
chained_join,
Expand Down Expand Up @@ -455,7 +454,7 @@ def aggregate_query(self):
self.relationships[0].mapper.class_
)

return get_scalar_subquery(query)
return query.scalar_subquery()

def update_query(self, objects):
table = self.class_.__table__
Expand Down Expand Up @@ -490,7 +489,7 @@ def update_query(self, objects):
return query.where(
local.in_(
sa.select(
*_select_args(remote)
remote
).select_from(
chained_join(*reversed(self.relationships))
).where(
Expand Down
69 changes: 0 additions & 69 deletions sqlalchemy_utils/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,72 +10,3 @@ def get_sqlalchemy_version(version=metadata("sqlalchemy")["Version"]):
return tuple(int(v) for v in match.groups() if v is not None)
except AttributeError:
return ()


_sqlalchemy_version = get_sqlalchemy_version()


# In sqlalchemy 2.0, some functions moved to sqlalchemy.orm.
# In sqlalchemy 1.3, they are only available in .ext.declarative.
# In sqlalchemy 1.4, they are available in both places.
#
# WARNING
# -------
#
# These imports are for internal, private compatibility.
# They are not supported and may change or move at any time.
# Do not import these in your own code.
#

if _sqlalchemy_version >= (1, 4):
from sqlalchemy.orm import declarative_base as _declarative_base
from sqlalchemy.orm import synonym_for as _synonym_for
else:
from sqlalchemy.ext.declarative import \
declarative_base as _declarative_base
from sqlalchemy.ext.declarative import synonym_for as _synonym_for


# scalar subqueries
if _sqlalchemy_version >= (1, 4):
def get_scalar_subquery(query):
return query.scalar_subquery()
else:
def get_scalar_subquery(query):
return query.as_scalar()


# In sqlalchemy 2.0, select() columns are positional.
# In sqlalchemy 1.3, select() columns must be wrapped in a list.
#
# _select_args() is designed so its return value can be unpacked:
#
# select(*_select_args(1, 2))
#
# When sqlalchemy 1.3 support is dropped, remove the call to _select_args()
# and keep the arguments the same:
#
# select(1, 2)
#
# WARNING
# -------
#
# _select_args() is a private, internal function.
# It is not supported and may change or move at any time.
# Do not import this in your own code.
#
if _sqlalchemy_version >= (1, 4):
def _select_args(*args):
return args
else:
def _select_args(*args):
return [args]


__all__ = (
"_declarative_base",
"get_scalar_subquery",
"get_sqlalchemy_version",
"_select_args",
"_synonym_for",
)
6 changes: 1 addition & 5 deletions sqlalchemy_utils/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,7 @@ def get(self, state, dict_, passive=attributes.PASSIVE_OFF):

id = self.get_state_id(state)

try:
target = session.get(target_class, id)
except AttributeError:
# sqlalchemy 1.3
target = session.query(target_class).get(id)
target = session.get(target_class, id)

# Return found (or not found) target.
return target
Expand Down
2 changes: 0 additions & 2 deletions sqlalchemy_utils/listeners.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,6 @@ def auto_delete_orphans(attr):
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy import *
from sqlalchemy.orm import *
# Necessary in sqlalchemy 1.3:
# from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import event
Expand Down
3 changes: 1 addition & 2 deletions sqlalchemy_utils/relationships/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import sqlalchemy.orm
from sqlalchemy.sql.util import ClauseAdapter

from ..compat import _select_args
from .chained_join import chained_join # noqa


Expand Down Expand Up @@ -96,7 +95,7 @@ def select_correlated_expression(
):
relationships = list(reversed(path_to_relationships(path, root_model)))

query = sa.select(*_select_args(expr))
query = sa.select(expr)

join_expr, aliases = chained_inverse_join(relationships, leaf_model)

Expand Down
6 changes: 1 addition & 5 deletions sqlalchemy_utils/types/encrypted/encrypted_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,7 @@ class StringEncryptedType(TypeDecorator, ScalarCoercible):
import sqlalchemy as sa
from sqlalchemy import create_engine
try:
from sqlalchemy.orm import declarative_base
except ImportError:
# sqlalchemy 1.3
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy_utils import StringEncryptedType
Expand Down
2 changes: 0 additions & 2 deletions sqlalchemy_utils/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,6 @@ def create_view(
)
premium_members = select(users).where(users.c.premium_user == True)
# sqlalchemy 1.3:
# premium_members = select([users]).where(users.c.premium_user == True)
create_view('premium_users', premium_members, metadata)
metadata.create_all(engine) # View is created at this point
Expand Down
9 changes: 2 additions & 7 deletions tests/functions/test_cast_if.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,11 @@
import sqlalchemy.orm

from sqlalchemy_utils import cast_if
from sqlalchemy_utils.compat import (
_declarative_base,
_select_args,
get_scalar_subquery
)


@pytest.fixture(scope='class')
def base():
return _declarative_base()
return sqlalchemy.orm.declarative_base()


@pytest.fixture(scope='class')
Expand Down Expand Up @@ -44,7 +39,7 @@ def test_synonym(self, article_cls):
assert cast_if(expr, sa.String) is expr

def test_scalar_selectable(self, article_cls):
expr = get_scalar_subquery(sa.select(*_select_args(article_cls.id)))
expr = sa.select(article_cls.id).scalar_subquery()
assert cast_if(expr, sa.Integer) is expr

def test_scalar(self):
Expand Down
3 changes: 1 addition & 2 deletions tests/functions/test_get_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import sqlalchemy.orm

from sqlalchemy_utils import get_type
from sqlalchemy_utils.compat import _select_args, get_scalar_subquery


@pytest.fixture
Expand Down Expand Up @@ -47,5 +46,5 @@ def test_relationship_property(self, Article, User):
assert get_type(Article.author) == User

def test_scalar_select(self, Article):
query = get_scalar_subquery(sa.select(*_select_args(Article.id)))
query = sa.select(Article.id).scalar_subquery()
assert isinstance(get_type(query), sa.Integer)
5 changes: 2 additions & 3 deletions tests/functions/test_json_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import sqlalchemy as sa

from sqlalchemy_utils import json_sql
from sqlalchemy_utils.compat import _select_args


@pytest.mark.usefixtures('postgresql_dsn')
Expand All @@ -22,12 +21,12 @@ class TestJSONSQL:
([1, 2], [1, 2]),
([], []),
(
[sa.select(*_select_args(sa.text('1'))).label('alias')],
[sa.select(sa.text('1')).label('alias')],
[1]
)
)
)
def test_compiled_scalars(self, connection, value, result):
assert result == (
connection.execute(sa.select(*_select_args(json_sql(value)))).fetchone()[0]
connection.execute(sa.select(json_sql(value))).fetchone()[0]
)
5 changes: 2 additions & 3 deletions tests/functions/test_jsonb_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import sqlalchemy as sa

from sqlalchemy_utils import jsonb_sql
from sqlalchemy_utils.compat import _select_args


@pytest.mark.usefixtures('postgresql_dsn')
Expand All @@ -22,12 +21,12 @@ class TestJSONBSQL:
([1, 2], [1, 2]),
([], []),
(
[sa.select(*_select_args(sa.text('1'))).label('alias')],
[sa.select(sa.text('1')).label('alias')],
[1]
)
)
)
def test_compiled_scalars(self, connection, value, result):
assert result == (
connection.execute(sa.select(*_select_args(jsonb_sql(value)))).fetchone()[0]
connection.execute(sa.select(jsonb_sql(value))).fetchone()[0]
)
3 changes: 1 addition & 2 deletions tests/functions/test_make_order_by_deterministic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import sqlalchemy as sa
import sqlalchemy.orm

from sqlalchemy_utils.compat import _select_args
from sqlalchemy_utils.functions.sort_query import make_order_by_deterministic

from .. import assert_contains
Expand Down Expand Up @@ -31,7 +30,7 @@ class User(Base):
)

User.article_count = sa.orm.column_property(
sa.select(*_select_args(sa.func.count()))
sa.select(sa.func.count())
.select_from(Article)
.where(Article.author_id == User.id)
.label('article_count')
Expand Down
3 changes: 1 addition & 2 deletions tests/functions/test_render.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import pytest
import sqlalchemy as sa

from sqlalchemy_utils.compat import _select_args
from sqlalchemy_utils.functions import (
mock_engine,
render_expression,
Expand Down Expand Up @@ -40,7 +39,7 @@ def test_render_statement(self, session, User):
assert 'WHERE user.id = 3' in text

def test_render_statement_without_mapper(self, session):
statement = sa.select(*_select_args(sa.text('1')))
statement = sa.select(sa.text('1'))
text = render_statement(statement, bind=session.bind)

assert 'SELECT 1' in text
Expand Down
Loading

0 comments on commit a66ee64

Please sign in to comment.