From cdf64e648b3ee86d4520fd95fd4a3ab224f2b5ba0337cd2748a7390af784a0e3 Mon Sep 17 00:00:00 2001 From: Dirk Mueller Date: Thu, 21 Oct 2021 13:26:26 +0000 Subject: [PATCH] Accepting request 925856 from home:bnavigator:branches:devel:languages:python - Update to version 0.5.3 * Support dialect+driver for default database drivers like postgresql+asyncpg (#396) * Documentation of low-level transaction (#390) - Release 0.5.2 * Reset counter for failed connections (#385) * Avoid dangling task-local connections after Database.disconnect() (#211) - Release 0.5.1 * Make database connect and disconnect calls idempotent (#379) * Fix in_ and notin_ queries in SQLAlchemy 1.4 (#378) - Release 0.5.0 (August 26th, 2021) * Support SQLAlchemy 1.4 (#299) * Fix concurrent transactions (#328) - Drop sqlalchemy-14.patch fixed upstream OBS-URL: https://build.opensuse.org/request/show/925856 OBS-URL: https://build.opensuse.org/package/show/devel:languages:python/python-databases?expand=0&rev=10 --- databases-0.4.3.tar.gz | 3 - databases-0.5.3.tar.gz | 3 + python-databases.changes | 19 ++ python-databases.spec | 19 +- sqlalchemy-14.patch | 409 --------------------------------------- 5 files changed, 31 insertions(+), 422 deletions(-) delete mode 100644 databases-0.4.3.tar.gz create mode 100644 databases-0.5.3.tar.gz delete mode 100644 sqlalchemy-14.patch diff --git a/databases-0.4.3.tar.gz b/databases-0.4.3.tar.gz deleted file mode 100644 index b8a49c0..0000000 --- a/databases-0.4.3.tar.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e55e67120ae4c6eefc369c7728639f92d49b67ab94d507f49a62770dcff68fbd -size 26517 diff --git a/databases-0.5.3.tar.gz b/databases-0.5.3.tar.gz new file mode 100644 index 0000000..b0cd23a --- /dev/null +++ b/databases-0.5.3.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:298d0312c3f2b1f95feaf491bd00f5d1227834aefa8100139e1b2494bb1fcc37 +size 28342 diff --git a/python-databases.changes b/python-databases.changes index 67c0193..fd660ba 100644 --- a/python-databases.changes +++ b/python-databases.changes @@ -1,3 +1,22 @@ +------------------------------------------------------------------- +Sun Oct 17 14:40:53 UTC 2021 - Ben Greiner + +- Update to version 0.5.3 + * Support dialect+driver for default database drivers like + postgresql+asyncpg (#396) + * Documentation of low-level transaction (#390) +- Release 0.5.2 + * Reset counter for failed connections (#385) + * Avoid dangling task-local connections after + Database.disconnect() (#211) +- Release 0.5.1 + * Make database connect and disconnect calls idempotent (#379) + * Fix in_ and notin_ queries in SQLAlchemy 1.4 (#378) +- Release 0.5.0 (August 26th, 2021) + * Support SQLAlchemy 1.4 (#299) + * Fix concurrent transactions (#328) +- Drop sqlalchemy-14.patch fixed upstream + ------------------------------------------------------------------- Wed May 26 12:10:35 UTC 2021 - Matej Cepl diff --git a/python-databases.spec b/python-databases.spec index 82c6dce..fea6b33 100644 --- a/python-databases.spec +++ b/python-databases.spec @@ -19,20 +19,19 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define skip_python2 1 Name: python-databases -Version: 0.4.3 +Version: 0.5.3 Release: 0 Summary: Async database support for Python License: BSD-3-Clause URL: https://github.com/encode/databases Source: https://github.com/encode/databases/archive/%{version}.tar.gz#/databases-%{version}.tar.gz -# PATCH-FIX-UPSTREAM sqlalchemy-14.patch gh#encode/databases#299 mcepl@suse.com -# Upgrade used API of SQLAlchemy to 1.4 -Patch0: sqlalchemy-14.patch BuildRequires: %{python_module setuptools} BuildRequires: fdupes BuildRequires: python-rpm-macros -Requires: python-sqlalchemy >= 1.3 -Suggests: python-aiocontextvars +Requires: python-sqlalchemy >= 1.4 +%if 0%{?python_version_nodots} < 37 +Requires: python-aiocontextvars +%endif Suggests: python-aiomysql Suggests: python-aiopg Suggests: python-aiosqlite @@ -40,12 +39,11 @@ Suggests: python-asyncpg BuildArch: noarch # SECTION test requirements BuildRequires: %{python_module aiosqlite} +BuildRequires: %{python_module aiocontextvars if %python-base < 3.7} BuildRequires: %{python_module asyncpg} BuildRequires: %{python_module pytest} BuildRequires: %{python_module requests} -BuildRequires: %{python_module sqlalchemy >= 1.3} -BuildRequires: (python3-aiocontextvars if python3-base < 3.7) -BuildRequires: (python36-aiocontextvars if python36-base) +BuildRequires: %{python_module sqlalchemy >= 1.4} # /SECTION %python_subpackages @@ -77,6 +75,7 @@ export PYTHONPATH=${PWD} %files %{python_files} %doc README.md %license LICENSE.md -%{python_sitelib}/* +%{python_sitelib}/databases +%{python_sitelib}/databases-%{version}*-info %changelog diff --git a/sqlalchemy-14.patch b/sqlalchemy-14.patch deleted file mode 100644 index d9ea487..0000000 --- a/sqlalchemy-14.patch +++ /dev/null @@ -1,409 +0,0 @@ -From 9d6e0c024833bd41421f0798a94ef2bbf27a31d5 Mon Sep 17 00:00:00 2001 -From: PrettyWood -Date: Mon, 15 Mar 2021 22:33:21 +0100 -Subject: [PATCH 1/2] build(deps): switch to sqlalchemy 1.4 - ---- - databases/backends/aiopg.py | 35 ++++++++++++++++++++------ - databases/backends/mysql.py | 35 ++++++++++++++++++++------ - databases/backends/postgres.py | 23 ++++++++++++++++- - databases/backends/sqlite.py | 35 ++++++++++++++++++++------ - databases/core.py | 2 - - requirements.txt | 2 - - setup.py | 2 - - tests/test_database_url.py | 4 ++ - tests/test_databases.py | 55 ++++++++++++++++++++++++++++++++++++++--- - 9 files changed, 160 insertions(+), 33 deletions(-) - ---- 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 = {} ---- 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 = {} ---- 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 ---- 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", " ") ---- 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 ---- a/requirements.txt -+++ b/requirements.txt -@@ -22,4 +22,4 @@ mypy - pytest - pytest-cov - starlette --requests -+requests -\ No newline at end of file ---- 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"], ---- 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") ---- 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_dupli - 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_transacti - @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 - 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]