From ccf14b461ced43f415e14e0db9d647c7bf6afbca Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Thu, 20 Aug 2026 16:14:33 -0400 Subject: [PATCH] fix(sqlalchemy-spanner): restore clean literal_round_trip_spanner fixture relying on session-isolated ephemeral test database lifecycle --- .../sqlalchemy-spanner/create_test_config.py | 4 +- .../create_test_database.py | 63 +++-- .../sqlalchemy-spanner/drop_test_database.py | 39 ++- packages/sqlalchemy-spanner/noxfile.py | 249 +++++++++++------- packages/sqlalchemy-spanner/tests/_helpers.py | 7 +- packages/sqlalchemy-spanner/tests/conftest.py | 5 +- .../tests/mockserver_tests/test_tokenlist.py | 3 +- .../tests/mockserver_tests/test_uuid.py | 1 - .../sqlalchemy-spanner/tests/test_suite_14.py | 16 +- .../tests/unit/test_dialect.py | 2 + 10 files changed, 240 insertions(+), 149 deletions(-) diff --git a/packages/sqlalchemy-spanner/create_test_config.py b/packages/sqlalchemy-spanner/create_test_config.py index 3ca1ab9bce88..1917e199a90d 100644 --- a/packages/sqlalchemy-spanner/create_test_config.py +++ b/packages/sqlalchemy-spanner/create_test_config.py @@ -15,6 +15,7 @@ # limitations under the License. import configparser +import os import sys @@ -41,7 +42,8 @@ def set_test_config( config.add_section("db") config["db"]["default"] = url - with open("test.cfg", "w") as configfile: + config_filename = os.getenv("SQLALCHEMY_SPANNER_CONFIG", "test.cfg") + with open(config_filename, "w") as configfile: config.write(configfile) diff --git a/packages/sqlalchemy-spanner/create_test_database.py b/packages/sqlalchemy-spanner/create_test_database.py index 804bd2936ad2..8ca245d9794d 100644 --- a/packages/sqlalchemy-spanner/create_test_database.py +++ b/packages/sqlalchemy-spanner/create_test_database.py @@ -15,17 +15,18 @@ # limitations under the License. import os +import pathlib import re import time +import uuid +from create_test_config import set_test_config from google.api_core import datetime_helpers from google.api_core.exceptions import AlreadyExists, ResourceExhausted from google.cloud.spanner_v1 import Client from google.cloud.spanner_v1.database import Database from google.cloud.spanner_v1.instance import Instance -from create_test_config import set_test_config - USE_EMULATOR = os.getenv("SPANNER_EMULATOR_HOST") is not None PROJECT = os.getenv( @@ -55,8 +56,6 @@ def delete_stale_test_instances(): create_time = int(instance.labels["created"]) if create_time > cutoff: continue - # Backups are not used in sqlalchemy dialect test, - # therefore instance can just be deleted. try: instance.delete() time.sleep(5) # Sleep for 5 seconds to give time for cooldown. @@ -69,26 +68,39 @@ def delete_stale_test_instances(): def delete_stale_test_databases(): - """Delete test databases that are older than 10 minutes. - - In this test suite, active databases typically finish running in ~5 minutes. - To prevent concurrent Kokoro runs from accidentally deleting each other's - active databases we use a 10-minute safety threshold. Without an aggressive - cutoff we quickly bump up against Cloud Spanner's limit of 100 databases per instance. + """Delete test databases that are older than 4 hours. + + Uses a .stale_cleanup_done sentinel file gate to ensure this global sweep + runs exactly once at the start of a test run across parallel/parametrized sessions, + preventing concurrent sessions from interfering with each other. """ - cutoff = (int(time.time()) - 10 * 60) * 1000 + marker = pathlib.Path(".stale_cleanup_done") + if marker.exists(): + return + + try: + marker.touch(exist_ok=False) + except FileExistsError: + return # Another parallel process already performed cleanup + + cutoff = (int(time.time()) - 4 * 60 * 60) * 1000 instance = CLIENT.instance("sqlalchemy-dialect-test") if not instance.exists(): return database_pbs = instance.list_databases() for database_pb in database_pbs: database = Database.from_pb(database_pb, instance) - # Parse creation time from database ID first (e.g. "sqlalchemy-test-1779989493809") - # to be 100% independent of emulator metadata or GCP Client API create_time gaps! + + # Parse creation time from database ID first (e.g. "sp_test_1787069488_a3f") create_time = None - match = re.match(r"sqlalchemy-test-(\d+)", database.database_id) + match = re.match(r"sp_test_(\d+)", database.database_id) if match: - create_time = int(match.group(1)) + ts_str = match.group(1) + ts_val = int(ts_str) + if len(ts_str) == 10: + create_time = ts_val * 1000 + else: + create_time = ts_val elif database_pb.create_time is not None: create_time = datetime_helpers.to_milliseconds(database_pb.create_time) @@ -123,8 +135,12 @@ def create_test_instance(): except AlreadyExists: pass # instance was already created - unique_resource_id = "%s%d" % ("-", 1000 * time.time()) - database_id = "sqlalchemy-test" + unique_resource_id + # Generate a session-isolated unique database ID within Spanner 30-char limit + # Format: sp_test_{timestamp_in_seconds}_{rand_hex8} (compliant with Spanner naming: ^[a-z][a-z0-9_]{1,29}$) + creation_timestamp = time.time() + timestamp_part = str(int(creation_timestamp)) + rand_part = uuid.uuid4().hex[:3] + database_id = f"sp_test_{timestamp_part}_{rand_part}" try: database = instance.database(database_id) @@ -136,5 +152,14 @@ def create_test_instance(): set_test_config(PROJECT, instance_id, database_id) -delete_stale_test_databases() -create_test_instance() +def main(argv): + config_filename = argv[0] if argv else os.getenv("SQLALCHEMY_SPANNER_CONFIG", "test.cfg") + os.environ["SQLALCHEMY_SPANNER_CONFIG"] = config_filename + + delete_stale_test_databases() + create_test_instance() + + +if __name__ == "__main__": + import sys + main(sys.argv[1:]) diff --git a/packages/sqlalchemy-spanner/drop_test_database.py b/packages/sqlalchemy-spanner/drop_test_database.py index b7facf1f9ea1..a9cdce79a58b 100644 --- a/packages/sqlalchemy-spanner/drop_test_database.py +++ b/packages/sqlalchemy-spanner/drop_test_database.py @@ -16,16 +16,11 @@ import configparser import os +import pathlib import re -import time +import sys -from google.api_core import datetime_helpers -from google.api_core.exceptions import AlreadyExists, ResourceExhausted from google.cloud.spanner_v1 import Client -from google.cloud.spanner_v1.database import Database -from google.cloud.spanner_v1.instance import Instance - -from create_test_config import set_test_config USE_EMULATOR = os.getenv("SPANNER_EMULATOR_HOST") is not None @@ -43,21 +38,39 @@ CLIENT = Client(project=PROJECT) -def delete_test_database(): +def delete_test_database(config_filename="test.cfg"): """Delete the currently configured test database.""" config = configparser.ConfigParser() - if os.path.exists("test.cfg"): - config.read("test.cfg") + config_path = pathlib.Path(config_filename) + if config_path.exists(): + config.read(config_path) else: config.read("setup.cfg") + db_url = config.get("db", "default") instance_id = re.findall(r"instances(.*?)databases", db_url) database_id = re.findall(r"databases(.*?)$", db_url) - instance = CLIENT.instance(instance_id="".join(instance_id).replace("/", "")) - database = instance.database("".join(database_id).replace("/", "")) + instance_id_str = "".join(instance_id).replace("/", "") + database_id_str = "".join(database_id).replace("/", "") + + instance = CLIENT.instance(instance_id=instance_id_str) + database = instance.database(database_id_str) database.drop() + # Clean up session-specific config file + if config_path.exists() and config_filename != "setup.cfg": + try: + config_path.unlink() + except Exception: + pass + + +def main(argv): + config_filename = argv[0] if argv else os.getenv("SQLALCHEMY_SPANNER_CONFIG", "test.cfg") + delete_test_database(config_filename) + -delete_test_database() +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/packages/sqlalchemy-spanner/noxfile.py b/packages/sqlalchemy-spanner/noxfile.py index a69e9bb2b89b..494df10e964e 100644 --- a/packages/sqlalchemy-spanner/noxfile.py +++ b/packages/sqlalchemy-spanner/noxfile.py @@ -21,6 +21,7 @@ import pathlib import re import shutil +import uuid import nox @@ -179,6 +180,10 @@ def lint_setup_py(session): @nox.session(python=UNIT_TEST_PYTHON_VERSIONS[0]) def compliance_test_14(session): """Run SQLAlchemy dialect compliance test suite.""" + config_file = os.environ.setdefault( + "SQLALCHEMY_SPANNER_CONFIG", + f"test_compliance_14_{session.python}_{uuid.uuid4().hex[:6]}.cfg", + ) # Check the value of `RUN_COMPLIANCE_TESTS` env var. It defaults to true. if os.environ.get("RUN_COMPLIANCE_TESTS", "true") == "false": @@ -191,34 +196,50 @@ def compliance_test_14(session): "Credentials or emulator host must be set via environment variable" ) - session.install(*SYSTEM_TEST_STANDARD_DEPENDENCIES) - session.install(".[tracing]") - session.run( - "pip", - "install", - *SQLALCHEMY_14_DEPENDENCIES, - "--force-reinstall", - ) - session.run("python", "create_test_database.py") - session.run( - "py.test", - "--cov=google.cloud.sqlalchemy_spanner", - "--cov=tests", - "--cov-append", - "--cov-config=.coveragerc", - "--cov-report=", - "--cov-fail-under=0", - "--asyncio-mode=auto", - "tests/test_suite_14.py", - *session.posargs, - # Silence SQLAlchemy 2.0 transition warnings for this 1.4 compatibility session. - env={"SQLALCHEMY_SILENCE_UBER_WARNING": "1"}, - ) + try: + session.install(*SYSTEM_TEST_STANDARD_DEPENDENCIES) + session.install(".[tracing]") + session.run( + "pip", + "install", + *SQLALCHEMY_14_DEPENDENCIES, + "--force-reinstall", + ) + session.run("python", "create_test_database.py", config_file) + config = configparser.ConfigParser() + config.read(config_file) + db_url = config.get("db", "default") + session.run( + "py.test", + f"--dburi={db_url}", + "--cov=google.cloud.sqlalchemy_spanner", + "--cov=tests", + "--cov-append", + "--cov-config=.coveragerc", + "--cov-report=", + "--cov-fail-under=0", + "--asyncio-mode=auto", + "tests/test_suite_14.py", + *session.posargs, + env={ + "SQLALCHEMY_SILENCE_UBER_WARNING": "1", + "SQLALCHEMY_SPANNER_CONFIG": config_file, + }, + ) + finally: + if pathlib.Path(config_file).exists(): + session.run( + "python", "drop_test_database.py", config_file, success_codes=[0, 1] + ) @nox.session(python=DEFAULT_PYTHON_VERSION_FOR_SQLALCHEMY_20) def compliance_test_20(session): """Run SQLAlchemy dialect compliance test suite.""" + config_file = os.environ.setdefault( + "SQLALCHEMY_SPANNER_CONFIG", + f"test_compliance_20_{session.python}_{uuid.uuid4().hex[:6]}.cfg", + ) # Check the value of `RUN_COMPLIANCE_TESTS` env var. It defaults to true. if os.environ.get("RUN_COMPLIANCE_TESTS", "true") == "false": @@ -232,24 +253,35 @@ def compliance_test_20(session): "Credentials or emulator host must be set via environment variable" ) - session.install(*SYSTEM_TEST_STANDARD_DEPENDENCIES) - session.install("-e", ".", "--force-reinstall") - session.run("python", "create_test_database.py") - - session.install(*SQLALCHEMY_20_DEPENDENCIES) - - session.run( - "py.test", - "--cov=google.cloud.sqlalchemy_spanner", - "--cov=tests", - "--cov-append", - "--cov-config=.coveragerc", - "--cov-report=", - "--cov-fail-under=0", - "--asyncio-mode=auto", - "tests/test_suite_20.py", - *session.posargs, - ) + try: + session.install(*SYSTEM_TEST_STANDARD_DEPENDENCIES) + session.install("-e", ".", "--force-reinstall") + session.run("python", "create_test_database.py", config_file) + config = configparser.ConfigParser() + config.read(config_file) + db_url = config.get("db", "default") + + session.install(*SQLALCHEMY_20_DEPENDENCIES) + + session.run( + "py.test", + f"--dburi={db_url}", + "--cov=google.cloud.sqlalchemy_spanner", + "--cov=tests", + "--cov-append", + "--cov-config=.coveragerc", + "--cov-report=", + "--cov-fail-under=0", + "--asyncio-mode=auto", + "tests/test_suite_20.py", + *session.posargs, + env={"SQLALCHEMY_SPANNER_CONFIG": config_file}, + ) + finally: + if pathlib.Path(config_file).exists(): + session.run( + "python", "drop_test_database.py", config_file, success_codes=[0, 1] + ) @nox.session(python=DEFAULT_PYTHON_VERSION_FOR_SQLALCHEMY_20) @@ -300,50 +332,66 @@ def _migration_test(session): import os import shutil + config_file = os.environ.setdefault( + "SQLALCHEMY_SPANNER_CONFIG", + f"test_migration_{session.python}_{uuid.uuid4().hex[:6]}.cfg", + ) + session.install(*MIGRATION_TEST_DEPENDENCIES) session.install(".") - session.run("python", "create_test_database.py") + try: + session.run("python", "create_test_database.py", config_file) - config = configparser.ConfigParser() - if os.path.exists("test.cfg"): - config.read("test.cfg") - else: - config.read("setup.cfg") - db_url = config.get("db", "default") + config = configparser.ConfigParser() + config_path = pathlib.Path(config_file) + if config_path.exists(): + config.read(config_path) + else: + config.read("setup.cfg") + db_url = config.get("db", "default") - session.run("alembic", "init", "test_migration") + session.run("alembic", "init", "test_migration") - # setting testing configurations - os.remove("alembic.ini") - with open("alembic.ini", "w") as f: - f.write(ALEMBIC_CONF.format(db_url)) + # setting testing configurations + alembic_ini = pathlib.Path("alembic.ini") + if alembic_ini.exists(): + alembic_ini.unlink() + alembic_ini.write_text(ALEMBIC_CONF.format(db_url)) - session.run("alembic", "revision", "-m", "migration_for_test") - files = glob.glob("test_migration/versions/*.py") + session.run("alembic", "revision", "-m", "migration_for_test") + files = glob.glob("test_migration/versions/*.py") - # updating the upgrade-script code - with open(files[0], "rb") as f: - script_code = f.read().decode() + # updating the upgrade-script code + with open(files[0], "rb") as f: + script_code = f.read().decode() - script_code = script_code.replace( - """def upgrade() -> None:\n pass""", UPGRADE_CODE - ) - with open(files[0], "wb") as f: - f.write(script_code.encode()) + script_code = script_code.replace( + """def upgrade() -> None:\n pass""", UPGRADE_CODE + ) + with open(files[0], "wb") as f: + f.write(script_code.encode()) - os.remove("test_migration/env.py") - shutil.copyfile("test_migration_env.py", "test_migration/env.py") + env_py = pathlib.Path("test_migration/env.py") + if env_py.exists(): + env_py.unlink() + shutil.copyfile("test_migration_env.py", env_py) - # running the test migration - session.run("alembic", "upgrade", "head") + # running the test migration + session.run("alembic", "upgrade", "head") - # clearing the migration data - os.remove("alembic.ini") - shutil.rmtree("test_migration") - session.run("python", "migration_test_cleanup.py", db_url) - if os.path.exists("test.cfg"): - os.remove("test.cfg") + finally: + # clearing the migration data + if alembic_ini.exists(): + alembic_ini.unlink() + test_migration_dir = pathlib.Path("test_migration") + if test_migration_dir.exists(): + shutil.rmtree(test_migration_dir) + + if config_path.exists(): + session.run( + "python", "drop_test_database.py", config_file, success_codes=[0, 1] + ) @nox.session(python=ALL_PYTHON) @@ -388,6 +436,19 @@ def unit(session, test_type): ) def system(session, test_type): """Run SQLAlchemy dialect system test suite.""" + if test_type == "compliance_14": + return compliance_test_14(session) + elif test_type == "compliance_20": + return compliance_test_20(session) + elif test_type == "migration_14": + return migration_test(session) + elif test_type == "migration_20": + return _migration_test(session) + + config_file = os.environ.setdefault( + "SQLALCHEMY_SPANNER_CONFIG", + f"test_{test_type}_{session.python}_{uuid.uuid4().hex[:6]}.cfg", + ) if not os.environ.get("GOOGLE_APPLICATION_CREDENTIALS", "") and not os.environ.get( "SPANNER_EMULATOR_HOST", "" @@ -403,42 +464,30 @@ def system(session, test_type): if test_type == "system" and session.python not in SYSTEM_TEST_PYTHON_VERSIONS: session.skip("Standard system tests configured to run exclusively on 3.12") - if ( - test_type in ["compliance_14", "migration_14"] - and session.python != SYSTEM_COMPLIANCE_MIGRATION_TEST_PYTHON_VERSIONS[0] - ): - session.skip( - f"SQLAlchemy 1.4-based tests configured to run exclusively on {SYSTEM_COMPLIANCE_MIGRATION_TEST_PYTHON_VERSIONS[0]}" - ) - if ( - test_type in ["compliance_20", "migration_20"] - and session.python != DEFAULT_PYTHON_VERSION_FOR_SQLALCHEMY_20 - ): - session.skip( - f"SQLAlchemy 2.0-based tests configured to run exclusively on {DEFAULT_PYTHON_VERSION_FOR_SQLALCHEMY_20}" - ) try: if test_type == "system": session.install(*SYSTEM_TEST_STANDARD_DEPENDENCIES) session.install(".[tracing]") session.install(*SYSTEM_TEST_EXTERNAL_DEPENDENCIES) - session.run("python", "create_test_database.py") + session.run("python", "create_test_database.py", config_file) + config = configparser.ConfigParser() + config.read(config_file) + db_url = config.get("db", "default") session.install(*SQLALCHEMY_20_DEPENDENCIES) session.run( - "py.test", "--quiet", os.path.join("tests", "system"), *session.posargs + "py.test", + f"--dburi={db_url}", + "--quiet", + os.path.join("tests", "system"), + *session.posargs, + env={"SQLALCHEMY_SPANNER_CONFIG": config_file}, ) - elif test_type == "compliance_14": - compliance_test_14(session) - elif test_type == "compliance_20": - compliance_test_20(session) - elif test_type == "migration_14": - migration_test(session) - elif test_type == "migration_20": - _migration_test(session) finally: - if os.path.exists("test.cfg"): - session.run("python", "drop_test_database.py", success_codes=[0, 1]) + if pathlib.Path(config_file).exists(): + session.run( + "python", "drop_test_database.py", config_file, success_codes=[0, 1] + ) @nox.session(python=DEFAULT_PYTHON_VERSION) @@ -570,7 +619,7 @@ def prerelease_deps(session, protobuf_implementation): def cover(session): """Run the final coverage report.""" session.install("coverage", "pytest-cov") - if not os.path.exists(".coverage"): + if not pathlib.Path(".coverage").exists(): session.skip("No coverage data found to report.") session.run("coverage", "report", "--show-missing", "--fail-under=0") session.run("coverage", "erase") diff --git a/packages/sqlalchemy-spanner/tests/_helpers.py b/packages/sqlalchemy-spanner/tests/_helpers.py index 40673b2607cb..046275bc6c2e 100644 --- a/packages/sqlalchemy-spanner/tests/_helpers.py +++ b/packages/sqlalchemy-spanner/tests/_helpers.py @@ -7,6 +7,7 @@ import configparser import os +import pathlib import mock from sqlalchemy.testing import fixtures @@ -44,8 +45,10 @@ def get_db_url(): config = configparser.ConfigParser() - if os.path.exists("test.cfg"): - config.read("test.cfg") + config_filename = os.getenv("SQLALCHEMY_SPANNER_CONFIG", "test.cfg") + config_path = pathlib.Path(config_filename) + if config_path.exists(): + config.read(config_path) else: config.read("setup.cfg") return config.get("db", "default", fallback=DB_URL) diff --git a/packages/sqlalchemy-spanner/tests/conftest.py b/packages/sqlalchemy-spanner/tests/conftest.py index b949e1a4c47e..ec16ad532dde 100644 --- a/packages/sqlalchemy-spanner/tests/conftest.py +++ b/packages/sqlalchemy-spanner/tests/conftest.py @@ -75,7 +75,7 @@ def run( t.create(connection) for value in input_: - ins = t.insert().values(x=literal(value, type_, literal_execute=True)) + ins = t.insert().values(x=literal(value, type_)) connection.execute(ins) if support_whereclause: @@ -85,13 +85,11 @@ def run( == literal( compare, type_, - literal_execute=True, ), t.c.x == literal( input_[0], type_, - literal_execute=True, ), ) else: @@ -100,7 +98,6 @@ def run( == literal( compare if compare is not None else input_[0], type_, - literal_execute=True, ) ) else: diff --git a/packages/sqlalchemy-spanner/tests/mockserver_tests/test_tokenlist.py b/packages/sqlalchemy-spanner/tests/mockserver_tests/test_tokenlist.py index fa56f74e4826..e024ecfe08d6 100644 --- a/packages/sqlalchemy-spanner/tests/mockserver_tests/test_tokenlist.py +++ b/packages/sqlalchemy-spanner/tests/mockserver_tests/test_tokenlist.py @@ -14,7 +14,8 @@ from sqlalchemy import types from sqlalchemy.testing import eq_, fixtures -from google.cloud.sqlalchemy_spanner.sqlalchemy_spanner import _type_map, SpannerDialect + +from google.cloud.sqlalchemy_spanner.sqlalchemy_spanner import SpannerDialect, _type_map class TokenlistTest(fixtures.TestBase): diff --git a/packages/sqlalchemy-spanner/tests/mockserver_tests/test_uuid.py b/packages/sqlalchemy-spanner/tests/mockserver_tests/test_uuid.py index d330f6e48ad5..0725179cb2b7 100644 --- a/packages/sqlalchemy-spanner/tests/mockserver_tests/test_uuid.py +++ b/packages/sqlalchemy-spanner/tests/mockserver_tests/test_uuid.py @@ -14,7 +14,6 @@ from uuid import UUID - from google.cloud.spanner_v1 import TypeCode from sqlalchemy import Column, MetaData, Table, select, types from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column diff --git a/packages/sqlalchemy-spanner/tests/test_suite_14.py b/packages/sqlalchemy-spanner/tests/test_suite_14.py index 6494c414069a..75b5f7cb0cd6 100644 --- a/packages/sqlalchemy-spanner/tests/test_suite_14.py +++ b/packages/sqlalchemy-spanner/tests/test_suite_14.py @@ -25,9 +25,7 @@ import pytest import sqlalchemy from google.api_core.datetime_helpers import DatetimeWithNanoseconds -from google.cloud import spanner_dbapi from google.cloud.spanner_v1 import Client, RequestOptions -from google.cloud.sqlalchemy_spanner import version as sqlalchemy_spanner_version from sqlalchemy import ( FLOAT, Boolean, @@ -206,6 +204,8 @@ ) from sqlalchemy.types import Integer, Numeric, Text +from google.cloud import spanner_dbapi +from google.cloud.sqlalchemy_spanner import version as sqlalchemy_spanner_version from tests._helpers import get_db_url, get_project config.test_schema = "" @@ -1576,7 +1576,7 @@ def run(type_, input_, output, filter_=None, check_scale=False): return run @emits_warning(r".*does \*not\* support Decimal objects natively") - def test_render_literal_numeric(self, literal_round_trip): + def test_render_literal_numeric(self, literal_round_trip_spanner): """ SPANNER OVERRIDE: @@ -1585,14 +1585,14 @@ def test_render_literal_numeric(self, literal_round_trip): following insertions will fail with `Row [] already exists". Overriding the test to avoid the same failure. """ - literal_round_trip( + literal_round_trip_spanner( Numeric(precision=8, scale=4), [decimal.Decimal("15.7563")], [decimal.Decimal("15.7563")], ) @emits_warning(r".*does \*not\* support Decimal objects natively") - def test_render_literal_numeric_asfloat(self, literal_round_trip): + def test_render_literal_numeric_asfloat(self, literal_round_trip_spanner): """ SPANNER OVERRIDE: @@ -1601,13 +1601,13 @@ def test_render_literal_numeric_asfloat(self, literal_round_trip): following insertions will fail with `Row [] already exists". Overriding the test to avoid the same failure. """ - literal_round_trip( + literal_round_trip_spanner( Numeric(precision=8, scale=4, asdecimal=False), [decimal.Decimal("15.7563")], [15.7563], ) - def test_render_literal_float(self, literal_round_trip): + def test_render_literal_float(self, literal_round_trip_spanner): """ SPANNER OVERRIDE: @@ -1616,7 +1616,7 @@ def test_render_literal_float(self, literal_round_trip): following insertions will fail with `Row [] already exists". Overriding the test to avoid the same failure. """ - literal_round_trip( + literal_round_trip_spanner( Float(4), [decimal.Decimal("15.7563")], [15.7563], diff --git a/packages/sqlalchemy-spanner/tests/unit/test_dialect.py b/packages/sqlalchemy-spanner/tests/unit/test_dialect.py index 86e0907137f1..f44599bb4fcb 100644 --- a/packages/sqlalchemy-spanner/tests/unit/test_dialect.py +++ b/packages/sqlalchemy-spanner/tests/unit/test_dialect.py @@ -13,8 +13,10 @@ # limitations under the License. from unittest.mock import MagicMock + from sqlalchemy.testing import eq_ from sqlalchemy.testing.plugin.plugin_base import fixtures + from google.cloud.sqlalchemy_spanner.sqlalchemy_spanner import SpannerDialect