Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ jobs:
- '3.12'
- '3.13'
- '3.14'
tox_env: ['sqlalchemy14', 'sqlalchemy2']
runs-on: ${{ matrix.os }}
services:
postgres:
Expand Down Expand Up @@ -68,7 +67,7 @@ jobs:
- name: Run tests
env:
SQLALCHEMY_UTILS_TEST_POSTGRESQL_PASSWORD: postgres
run: tox -e ${{ matrix.tox_env }}
run: tox -e py${{ matrix.python-version }}

docs:
runs-on: 'ubuntu-latest'
Expand Down
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Here you can see the full list of changes between each SQLAlchemy-Utils release.
Unreleased
^^^^^^^^^^

- Drop support for sqlalchemy 1.4.
- Drop support for Python 3.9.
- Support Python 3.14.
- Fix ``ChoiceType`` returning the raw scalar instead of a ``Choice`` for falsy codes such as ``0`` or the empty string. (#813)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"SQLAlchemy>=1.4",
"SQLAlchemy>=2.0",
]

[project.urls]
Expand Down
10 changes: 2 additions & 8 deletions sqlalchemy_utils/functions/database.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import itertools
import os
from collections.abc import Mapping, Sequence
from copy import copy

import sqlalchemy as sa
from sqlalchemy.engine.url import make_url
Expand Down Expand Up @@ -416,13 +415,8 @@ def _set_url_database(url: sa.engine.url.URL, database):
:param database: New database to set.

"""
if hasattr(url, '_replace'):
# Cannot use URL.set() as database may need to be set to None.
ret = url._replace(database=database)
else: # SQLAlchemy <1.4
url = copy(url)
url.database = database
ret = url
# Cannot use URL.set() as database may need to be set to None.
ret = url._replace(database=database)
assert ret.database == database, ret
return ret

Expand Down
5 changes: 1 addition & 4 deletions sqlalchemy_utils/functions/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,7 @@ def render_literal_value(self, value, type_):
def dump(*args, **kw):
return None

try:
engine = sa.create_mock_engine(bind_url, executor=dump)
except AttributeError: # SQLAlchemy <1.4
engine = sa.create_engine(bind_url, strategy='mock', executor=dump)
engine = sa.create_mock_engine(bind_url, executor=dump)
return engine


Expand Down
31 changes: 9 additions & 22 deletions sqlalchemy_utils/functions/orm.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@
from sqlalchemy.orm.attributes import InstrumentedAttribute
from sqlalchemy.orm.exc import UnmappedInstanceError

try:
from sqlalchemy.orm.context import _ColumnEntity, _MapperEntity
except ImportError: # SQLAlchemy <1.4
from sqlalchemy.orm.query import _ColumnEntity, _MapperEntity
from sqlalchemy.orm.context import _ColumnEntity, _MapperEntity

from sqlalchemy.orm.session import object_session
from sqlalchemy.orm.util import AliasedInsp
Expand Down Expand Up @@ -281,12 +278,9 @@ def get_mapper(mixed):
if isinstance(mixed, sa.orm.attributes.InstrumentedAttribute):
mixed = mixed.class_
if isinstance(mixed, sa.Table):
if hasattr(mapperlib, '_all_registries'):
all_mappers = set()
for mapper_registry in mapperlib._all_registries():
all_mappers.update(mapper_registry.mappers)
else: # SQLAlchemy <1.4
all_mappers = mapperlib._mapper_registry
all_mappers = set()
for mapper_registry in mapperlib._all_registries():
all_mappers.update(mapper_registry.mappers)
mappers = [mapper for mapper in all_mappers if mixed in mapper.tables]
if len(mappers) > 1:
raise ValueError("Multiple mappers found for table '%s'." % mixed.name)
Expand Down Expand Up @@ -449,11 +443,10 @@ def get_columns(mixed):
SA Table object, SA Mapper, SA declarative class, SA declarative class
instance or an alias of any of these objects
"""
if isinstance(mixed, sa.SelectBase):
return mixed.subquery().c
if isinstance(mixed, sa.sql.selectable.Selectable):
try:
return mixed.selected_columns
except AttributeError: # SQLAlchemy <1.4
return mixed.c
return mixed.c
if isinstance(mixed, sa.orm.util.AliasedClass):
return sa.inspect(mixed).mapper.columns
if isinstance(mixed, sa.orm.Mapper):
Expand Down Expand Up @@ -520,10 +513,7 @@ def quote(mixed, ident):


def _get_query_compile_state(query):
if hasattr(query, '_compile_state'):
return query._compile_state()
else: # SQLAlchemy <1.4
return query
return query._compile_state()


def get_polymorphic_mappers(mixed):
Expand Down Expand Up @@ -878,7 +868,4 @@ def naturally_equivalent(obj, obj2):


def _get_class_registry(class_):
try:
return class_.registry._class_registry
except AttributeError: # SQLAlchemy <1.4
return class_._decl_class_registry
return class_.registry._class_registry
5 changes: 1 addition & 4 deletions sqlalchemy_utils/functions/sort_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,7 @@ def make_order_by_deterministic(query):
"""
order_by_func = sa.asc

try:
order_by_clauses = query._order_by_clauses
except AttributeError: # SQLAlchemy <1.4
order_by_clauses = query._order_by
order_by_clauses = query._order_by_clauses
if not order_by_clauses:
column = None
else:
Expand Down
15 changes: 2 additions & 13 deletions sqlalchemy_utils/types/uuid.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@
from sqlalchemy import types, util
from sqlalchemy.dialects import mssql, postgresql

from ..compat import get_sqlalchemy_version
from .scalar_coercible import ScalarCoercible

sqlalchemy_version = get_sqlalchemy_version()


class UUIDType(ScalarCoercible, types.TypeDecorator):
"""
Expand Down Expand Up @@ -71,16 +68,8 @@ def _coerce(value):

return value

# sqlalchemy >= 1.4.30 quotes UUID's automatically.
# It is only necessary to quote UUID's in sqlalchemy < 1.4.30.
if sqlalchemy_version < (1, 4, 30):

def process_literal_param(self, value, dialect):
return f"'{value}'" if value else value
else:

def process_literal_param(self, value, dialect):
return value
def process_literal_param(self, value, dialect):
return value

def process_bind_param(self, value, dialect):
if value is None:
Expand Down
4 changes: 0 additions & 4 deletions tests/functions/test_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@
import sqlalchemy as sa

from sqlalchemy_utils import create_database, database_exists, drop_database
from sqlalchemy_utils.compat import get_sqlalchemy_version

pymysql = None
try:
import pymysql # noqa
except ImportError:
pass

sqlalchemy_version = get_sqlalchemy_version()


class DatabaseTest:
def test_create_and_drop(self, dsn):
Expand Down Expand Up @@ -101,7 +98,6 @@ def dsn(self, postgresql_db_user, postgresql_db_password):
)


@pytest.mark.skipif('sqlalchemy_version < (2, 0, 0)')
class TestDatabasePostgresPsycoPG3(DatabaseTest):

@pytest.fixture
Expand Down
5 changes: 1 addition & 4 deletions tests/types/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,7 @@ def test_compilation(self, Document, session):
def test_unhashable_type(self, AAA, BBB, session):
"""Verify there are no TypeErrors with certain JSON queries.

This test will fail under these conditions:

1. sqlalchemy versions 1.4.19 through 1.4.23 are installed.
2. `JSONType.hashable` is not set to False.
This test will fail if `JSONType.hashable` is not set to False.

For more info, see:

Expand Down
18 changes: 1 addition & 17 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tox]
envlist =
py{3.10, 3.11, 3.12, 3.13, 3.14}-sqlalchemy{14, 2}
py{3.10, 3.11, 3.12, 3.13, 3.14}
ruff
docs
labels =
Expand All @@ -12,22 +12,6 @@ commands =
deps =
.[test_all]
pytest-cov
sqlalchemy14: SQLAlchemy>=1.4,<1.5
sqlalchemy2: SQLAlchemy>=2
; It's sometimes necessary to test against specific sqlalchemy versions
; to verify bug or feature behavior before or after a specific version.
;
; You can choose the sqlalchemy version using this command syntax:
; $ tox -e py39-sqlalchemy1_4_xx
;
; sqlalchemy 1.4.19 through 1.4.23 have JSON-related TypeErrors. See #543.
sqlalchemy1_4_19: SQLAlchemy==1.4.19
; sqlalchemy <1.4.28 threw a DeprecationWarning when copying a URL. See #573.
sqlalchemy1_4_27: SQLAlchemy==1.4.27
; sqlalchemy 1.4.30 introduced UUID literal quoting. See #580.
sqlalchemy1_4_29: SQLAlchemy==1.4.29
setenv =
SQLALCHEMY_WARN_20 = true
passenv =
SQLALCHEMY_UTILS_TEST_*
recreate = True
Expand Down