diff --git a/databases-pr-299-40c41c2b.patch b/databases-pr-299-40c41c2b.patch deleted file mode 100644 index 0f29373..0000000 --- a/databases-pr-299-40c41c2b.patch +++ /dev/null @@ -1,436 +0,0 @@ -diff --git a/CHANGELOG.md b/CHANGELOG.md -index a88ea38..876d49b 100644 ---- a/CHANGELOG.md -+++ b/CHANGELOG.md -@@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - - ### Fixed - --* Pin SQLAlchemy to 1.13.* (#314) -+* Pin SQLAlchemy to <1.4 (#314) - - ## 0.4.2 (March 14th, 2021) - -diff --git a/README.md b/README.md -index dc98a63..1960ef3 100644 ---- a/README.md -+++ b/README.md -@@ -19,8 +19,6 @@ Databases is suitable for integrating against any async Web framework, such as [ - - **Documentation**: [https://www.encode.io/databases/](https://www.encode.io/databases/) - --**Community**: [https://discuss.encode.io/c/databases](https://discuss.encode.io/c/databases) -- - **Requirements**: Python 3.6+ - - --- -diff --git a/databases/backends/aiopg.py b/databases/backends/aiopg.py -index 2fecb1b..e8ccc99 100644 ---- a/databases/backends/aiopg.py -+++ b/databases/backends/aiopg.py -@@ -7,11 +7,11 @@ import uuid - import aiopg - from aiopg.sa.engine import APGCompiler_psycopg2 - from sqlalchemy.dialects.postgresql.psycopg2 import PGDialect_psycopg2 -+from sqlalchemy.engine.cursor import CursorResultMetaData - from sqlalchemy.engine.interfaces import Dialect, ExecutionContext --from sqlalchemy.engine.result import ResultMetaData, RowProxy -+from sqlalchemy.engine.result import Row - from sqlalchemy.sql import ClauseElement - from sqlalchemy.sql.ddl import DDLElement --from sqlalchemy.types import TypeEngine - - from databases.core import DatabaseURL - from databases.interfaces import ConnectionBackend, DatabaseBackend, TransactionBackend -@@ -119,9 +119,15 @@ class AiopgConnection(ConnectionBackend): - try: - await cursor.execute(query, args) - rows = await cursor.fetchall() -- metadata = ResultMetaData(context, cursor.description) -+ metadata = CursorResultMetaData(context, cursor.description) - return [ -- RowProxy(metadata, row, metadata._processors, metadata._keymap) -+ Row( -+ metadata, -+ metadata._processors, -+ metadata._keymap, -+ Row._default_key_style, -+ row, -+ ) - for row in rows - ] - finally: -@@ -136,8 +142,14 @@ class AiopgConnection(ConnectionBackend): - row = await cursor.fetchone() - if row is None: - return None -- metadata = ResultMetaData(context, cursor.description) -- return RowProxy(metadata, row, metadata._processors, metadata._keymap) -+ metadata = CursorResultMetaData(context, cursor.description) -+ return Row( -+ metadata, -+ metadata._processors, -+ metadata._keymap, -+ Row._default_key_style, -+ row, -+ ) - finally: - cursor.close() - -@@ -169,9 +181,15 @@ class AiopgConnection(ConnectionBackend): - cursor = await self._connection.cursor() - try: - await cursor.execute(query, args) -- metadata = ResultMetaData(context, cursor.description) -+ metadata = CursorResultMetaData(context, cursor.description) - async for row in cursor: -- yield RowProxy(metadata, row, metadata._processors, metadata._keymap) -+ yield Row( -+ metadata, -+ metadata._processors, -+ metadata._keymap, -+ Row._default_key_style, -+ row, -+ ) - finally: - cursor.close() - -@@ -196,6 +214,7 @@ class AiopgConnection(ConnectionBackend): - compiled._result_columns, - compiled._ordered_columns, - compiled._textual_ordered_columns, -+ compiled._loose_column_name_matching, - ) - else: - args = {} -diff --git a/databases/backends/mysql.py b/databases/backends/mysql.py -index b6476ad..b615488 100644 ---- a/databases/backends/mysql.py -+++ b/databases/backends/mysql.py -@@ -5,11 +5,11 @@ import uuid - - import aiomysql - from sqlalchemy.dialects.mysql import pymysql -+from sqlalchemy.engine.cursor import CursorResultMetaData - from sqlalchemy.engine.interfaces import Dialect, ExecutionContext --from sqlalchemy.engine.result import ResultMetaData, RowProxy -+from sqlalchemy.engine.result import Row - from sqlalchemy.sql import ClauseElement - from sqlalchemy.sql.ddl import DDLElement --from sqlalchemy.types import TypeEngine - - from databases.core import LOG_EXTRA, DatabaseURL - from databases.interfaces import ConnectionBackend, DatabaseBackend, TransactionBackend -@@ -107,9 +107,15 @@ class MySQLConnection(ConnectionBackend): - try: - await cursor.execute(query, args) - rows = await cursor.fetchall() -- metadata = ResultMetaData(context, cursor.description) -+ metadata = CursorResultMetaData(context, cursor.description) - return [ -- RowProxy(metadata, row, metadata._processors, metadata._keymap) -+ Row( -+ metadata, -+ metadata._processors, -+ metadata._keymap, -+ Row._default_key_style, -+ row, -+ ) - for row in rows - ] - finally: -@@ -124,8 +130,14 @@ class MySQLConnection(ConnectionBackend): - row = await cursor.fetchone() - if row is None: - return None -- metadata = ResultMetaData(context, cursor.description) -- return RowProxy(metadata, row, metadata._processors, metadata._keymap) -+ metadata = CursorResultMetaData(context, cursor.description) -+ return Row( -+ metadata, -+ metadata._processors, -+ metadata._keymap, -+ Row._default_key_style, -+ row, -+ ) - finally: - await cursor.close() - -@@ -159,9 +171,15 @@ class MySQLConnection(ConnectionBackend): - cursor = await self._connection.cursor() - try: - await cursor.execute(query, args) -- metadata = ResultMetaData(context, cursor.description) -+ metadata = CursorResultMetaData(context, cursor.description) - async for row in cursor: -- yield RowProxy(metadata, row, metadata._processors, metadata._keymap) -+ yield Row( -+ metadata, -+ metadata._processors, -+ metadata._keymap, -+ Row._default_key_style, -+ row, -+ ) - finally: - await cursor.close() - -@@ -186,6 +204,7 @@ class MySQLConnection(ConnectionBackend): - compiled._result_columns, - compiled._ordered_columns, - compiled._textual_ordered_columns, -+ compiled._loose_column_name_matching, - ) - else: - args = {} -diff --git a/databases/backends/postgres.py b/databases/backends/postgres.py -index 8c1d75b..73f998a 100644 ---- a/databases/backends/postgres.py -+++ b/databases/backends/postgres.py -@@ -104,8 +104,29 @@ class Record(Mapping): - self._dialect = dialect - self._column_map, self._column_map_int, self._column_map_full = column_maps - -+ @property -+ def _mapping(self) -> asyncpg.Record: -+ return self._row -+ -+ def keys(self) -> typing.KeysView: -+ import warnings -+ -+ warnings.warn( -+ "The `Row.keys()` method is deprecated to mimic SQLAlchemy behaviour, " -+ "use `Row._mapping.keys()` instead.", -+ DeprecationWarning, -+ ) -+ return self._mapping.keys() -+ - def values(self) -> typing.ValuesView: -- return self._row.values() -+ import warnings -+ -+ warnings.warn( -+ "The `Row.values()` method is deprecated to mimic SQLAlchemy behaviour, " -+ "use `Row._mapping.values()` instead.", -+ DeprecationWarning, -+ ) -+ return self._mapping.values() - - def __getitem__(self, key: typing.Any) -> typing.Any: - if len(self._column_map) == 0: # raw query -diff --git a/databases/backends/sqlite.py b/databases/backends/sqlite.py -index 28ceb6f..a7e30cc 100644 ---- a/databases/backends/sqlite.py -+++ b/databases/backends/sqlite.py -@@ -4,11 +4,11 @@ import uuid - - import aiosqlite - from sqlalchemy.dialects.sqlite import pysqlite -+from sqlalchemy.engine.cursor import CursorResultMetaData - from sqlalchemy.engine.interfaces import Dialect, ExecutionContext --from sqlalchemy.engine.result import ResultMetaData, RowProxy -+from sqlalchemy.engine.result import Row - from sqlalchemy.sql import ClauseElement - from sqlalchemy.sql.ddl import DDLElement --from sqlalchemy.types import TypeEngine - - from databases.core import LOG_EXTRA, DatabaseURL - from databases.interfaces import ConnectionBackend, DatabaseBackend, TransactionBackend -@@ -92,9 +92,15 @@ class SQLiteConnection(ConnectionBackend): - - async with self._connection.execute(query, args) as cursor: - rows = await cursor.fetchall() -- metadata = ResultMetaData(context, cursor.description) -+ metadata = CursorResultMetaData(context, cursor.description) - return [ -- RowProxy(metadata, row, metadata._processors, metadata._keymap) -+ Row( -+ metadata, -+ metadata._processors, -+ metadata._keymap, -+ Row._default_key_style, -+ row, -+ ) - for row in rows - ] - -@@ -106,8 +112,14 @@ class SQLiteConnection(ConnectionBackend): - row = await cursor.fetchone() - if row is None: - return None -- metadata = ResultMetaData(context, cursor.description) -- return RowProxy(metadata, row, metadata._processors, metadata._keymap) -+ metadata = CursorResultMetaData(context, cursor.description) -+ return Row( -+ metadata, -+ metadata._processors, -+ metadata._keymap, -+ Row._default_key_style, -+ row, -+ ) - - async def execute(self, query: ClauseElement) -> typing.Any: - assert self._connection is not None, "Connection is not acquired" -@@ -129,9 +141,15 @@ class SQLiteConnection(ConnectionBackend): - assert self._connection is not None, "Connection is not acquired" - query, args, context = self._compile(query) - async with self._connection.execute(query, args) as cursor: -- metadata = ResultMetaData(context, cursor.description) -+ metadata = CursorResultMetaData(context, cursor.description) - async for row in cursor: -- yield RowProxy(metadata, row, metadata._processors, metadata._keymap) -+ yield Row( -+ metadata, -+ metadata._processors, -+ metadata._keymap, -+ Row._default_key_style, -+ row, -+ ) - - def transaction(self) -> TransactionBackend: - return SQLiteTransaction(self) -@@ -158,6 +176,7 @@ class SQLiteConnection(ConnectionBackend): - compiled._result_columns, - compiled._ordered_columns, - compiled._textual_ordered_columns, -+ compiled._loose_column_name_matching, - ) - - query_message = compiled.string.replace(" \n", " ").replace("\n", " ") -diff --git a/databases/core.py b/databases/core.py -index 2bab673..288592c 100644 ---- a/databases/core.py -+++ b/databases/core.py -@@ -5,7 +5,7 @@ import logging - import sys - import typing - from types import TracebackType --from urllib.parse import SplitResult, parse_qsl, urlsplit, unquote -+from urllib.parse import SplitResult, parse_qsl, unquote, urlsplit - - from sqlalchemy import text - from sqlalchemy.sql import ClauseElement -diff --git a/requirements.txt b/requirements.txt -index 0f22a02..559c929 100644 ---- a/requirements.txt -+++ b/requirements.txt -@@ -22,4 +22,4 @@ mypy - pytest - pytest-cov - starlette --requests -+requests -\ No newline at end of file -diff --git a/setup.py b/setup.py -index 4cdd0ff..b4133b1 100644 ---- a/setup.py -+++ b/setup.py -@@ -48,7 +48,7 @@ setup( - packages=get_packages("databases"), - package_data={"databases": ["py.typed"]}, - data_files=[("", ["LICENSE.md"])], -- install_requires=['sqlalchemy<1.4', 'aiocontextvars;python_version<"3.7"'], -+ install_requires=['sqlalchemy>=1.4,<1.5', 'aiocontextvars;python_version<"3.7"'], - extras_require={ - "postgresql": ["asyncpg"], - "mysql": ["aiomysql"], -diff --git a/tests/test_database_url.py b/tests/test_database_url.py -index 53e01be..38c178a 100644 ---- a/tests/test_database_url.py -+++ b/tests/test_database_url.py -@@ -1,7 +1,9 @@ --from databases import DatabaseURL - from urllib.parse import quote -+ - import pytest - -+from databases import DatabaseURL -+ - - def test_database_url_repr(): - u = DatabaseURL("postgresql://localhost/name") -diff --git a/tests/test_databases.py b/tests/test_databases.py -index c731768..bad9aba 100644 ---- a/tests/test_databases.py -+++ b/tests/test_databases.py -@@ -3,6 +3,7 @@ import datetime - import decimal - import functools - import os -+import re - - import pytest - import sqlalchemy -@@ -336,8 +337,8 @@ async def test_result_values_allow_duplicate_names(database_url): - query = "SELECT 1 AS id, 2 AS id" - row = await database.fetch_one(query=query) - -- assert list(row.keys()) == ["id", "id"] -- assert list(row.values()) == [1, 2] -+ assert list(row._mapping.keys()) == ["id", "id"] -+ assert list(row._mapping.values()) == [1, 2] - - - @pytest.mark.parametrize("database_url", DATABASE_URLS) -@@ -981,7 +982,7 @@ async def test_iterate_outside_transaction_with_temp_table(database_url): - @async_adapter - async def test_column_names(database_url, select_query): - """ -- Test that column names are exposed correctly through `.keys()` on each row. -+ Test that column names are exposed correctly through `._mapping.keys()` on each row. - """ - async with Database(database_url) as database: - async with database.transaction(force_rollback=True): -@@ -993,6 +994,52 @@ async def test_column_names(database_url, select_query): - results = await database.fetch_all(query=select_query) - assert len(results) == 1 - -- assert sorted(results[0].keys()) == ["completed", "id", "text"] -+ assert sorted(results[0]._mapping.keys()) == ["completed", "id", "text"] - assert results[0]["text"] == "example1" - assert results[0]["completed"] == True -+ -+ -+@pytest.mark.parametrize("database_url", DATABASE_URLS) -+@async_adapter -+async def test_posgres_interface(database_url): -+ """ -+ Since SQLAlchemy 1.4, `Row.values()` is removed and `Row.keys()` is deprecated. -+ Custom postgres interface mimics more or less this behaviour by deprecating those -+ two methods -+ """ -+ database_url = DatabaseURL(database_url) -+ -+ if database_url.scheme != "postgresql": -+ pytest.skip("Test is only for postgresql") -+ -+ async with Database(database_url) as database: -+ async with database.transaction(force_rollback=True): -+ query = notes.insert() -+ values = {"text": "example1", "completed": True} -+ await database.execute(query, values) -+ -+ query = notes.select() -+ result = await database.fetch_one(query=query) -+ -+ with pytest.warns( -+ DeprecationWarning, -+ match=re.escape( -+ "The `Row.keys()` method is deprecated to mimic SQLAlchemy behaviour, " -+ "use `Row._mapping.keys()` instead." -+ ), -+ ): -+ assert ( -+ list(result.keys()) -+ == [k for k in result] -+ == ["id", "text", "completed"] -+ ) -+ -+ with pytest.warns( -+ DeprecationWarning, -+ match=re.escape( -+ "The `Row.values()` method is deprecated to mimic SQLAlchemy behaviour, " -+ "use `Row._mapping.values()` instead." -+ ), -+ ): -+ # avoid checking `id` at index 0 since it may change depending on the launched tests -+ assert list(result.values())[1:] == ["example1", True] diff --git a/python-databases.spec b/python-databases.spec index 94fc5ef..b8be79c 100644 --- a/python-databases.spec +++ b/python-databases.spec @@ -12,12 +12,6 @@ License: BSD URL: https://www.encode.io/%{srcname}/ Source0: https://github.com/encode/%{srcname}/archive/%{version}/%{srcname}-%{version}.tar.gz -# Patch with work-in-progress SQLAlchemy 1.4 support from upstream PR#299 -# (https://github.com/encode/databases/pull/299); this corresponds to the -# PrettyWood:fix/sqlalchemy-1.4 branch at commit -# 40c41c2b7b3fedae484ad94d81b27ce88a09c5ed. -Patch0: %{srcname}-pr-299-40c41c2b.patch - BuildArch: noarch BuildRequires: python3-devel @@ -74,9 +68,6 @@ Summary: %{summary} %prep %setup -q -n %{srcname}-%{version} -%if 0%{?fedora} > 34 -%patch0 -p1 -%endif # The following is equivalent to the former patch file # databases-0.4.3-wayward-license-file.patch, but the sed expression works # regardless of whether databases-pr-299-40c41c2b.patch has affected nearby @@ -127,6 +118,7 @@ export TEST_DATABASE_URLS="sqlite:///testsuite" %changelog * Fri Oct 01 2021 Benjamin A. Beasley - 0.4.3-4 - Drop the -doc subpackage entirely +- Drop workarounds for SQLAlchemy 1.4 in F35+ * Fri May 28 2021 Benjamin A. Beasley - 0.4.3-3 - Build documentation without the mkdocs-material theme