From c3438d6593d9ba7adf7d9760eb8973424ce63d52 Mon Sep 17 00:00:00 2001 From: jagankuamrm Date: Thu, 31 Jul 2025 13:18:18 +0000 Subject: [PATCH 01/11] Update the google-cloud-spanner dependencies to 3.x --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 0bcdb5f..2aa2b71 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='spanner-orm', - version='0.2.0', + version='0.3.0', description='Basic ORM for Spanner', maintainer='Python Spanner ORM developers', maintainer_email='python-spanner-orm@google.com', @@ -25,7 +25,7 @@ include_package_data=True, python_requires='~=3.8', install_requires=[ - 'google-cloud-spanner >= 2, <4', + 'google-cloud-spanner >= 3, <4', 'immutabledict', ], tests_require=['absl-py', 'google-api-core', 'portpicker'], From ce77e089ff7a57c8f12dd6d909a705ab7ce060ca Mon Sep 17 00:00:00 2001 From: jagankuamrm Date: Thu, 31 Jul 2025 13:20:26 +0000 Subject: [PATCH 02/11] Fix the deprecation warnings --- spanner_orm/__init__.py | 5 ----- spanner_orm/tests/migrations_emulator_test.py | 20 +++++++++---------- ...create_custom_length_field_f959b767457d.py | 2 +- .../create_foreign_key_test_model.py | 8 ++++---- ..._null_filtered_index_model_760ec5fae5da.py | 6 +++--- .../create_small_test_model.py | 6 +++--- .../create_unittest_model.py | 14 ++++++------- 7 files changed, 28 insertions(+), 33 deletions(-) diff --git a/spanner_orm/__init__.py b/spanner_orm/__init__.py index 5b27fc6..4da2f26 100644 --- a/spanner_orm/__init__.py +++ b/spanner_orm/__init__.py @@ -113,8 +113,3 @@ model_creation_ddl = update_module.model_creation_ddl MigrationExecutor = migration_executor.MigrationExecutor - -try: - __import__('pkg_resources').declare_namespace('spanner_orm') -except ImportError: - __path__ = __import__('pkgutil').extend_path(__path__, 'spanner_orm') diff --git a/spanner_orm/tests/migrations_emulator_test.py b/spanner_orm/tests/migrations_emulator_test.py index 4f936bb..96978bd 100644 --- a/spanner_orm/tests/migrations_emulator_test.py +++ b/spanner_orm/tests/migrations_emulator_test.py @@ -124,7 +124,7 @@ def test_drop_interleaved_table(self): class _Parent(spanner_orm.Model): __table__ = 'Parent' parent_key = spanner_orm.Field( - spanner_orm.String, primary_key=True) + spanner_orm.String(), primary_key=True) def upgrade(): return spanner_orm.CreateTable(_Parent) @@ -133,15 +133,15 @@ def upgrade(): class _Parent(spanner_orm.Model): __table__ = 'Parent' parent_key = spanner_orm.Field( - spanner_orm.String, primary_key=True) + spanner_orm.String(), primary_key=True) class _Child(spanner_orm.Model): __table__ = 'Child' __interleaved__ = _Parent parent_key = spanner_orm.Field( - spanner_orm.String, primary_key=True) + spanner_orm.String(), primary_key=True) child_key = spanner_orm.Field( - spanner_orm.String, primary_key=True) + spanner_orm.String(), primary_key=True) def upgrade(): return spanner_orm.CreateTable(_Child) @@ -170,8 +170,8 @@ def upgrade(): class _TableToDrop(spanner_orm.Model): __table__ = 'TableToDrop' key = spanner_orm.Field( - spanner_orm.String, primary_key=True) - value = spanner_orm.Field(spanner_orm.String) + spanner_orm.String(), primary_key=True) + value = spanner_orm.Field(spanner_orm.String()) def upgrade(): return spanner_orm.CreateTable(_TableToDrop) @@ -194,7 +194,7 @@ def upgrade(): class _TableToDrop(spanner_orm.Model): __table__ = 'TableToDrop' parent_key = spanner_orm.Field( - spanner_orm.String, primary_key=True) + spanner_orm.String(), primary_key=True) def upgrade(): return spanner_orm.CreateTable(_TableToDrop) @@ -203,15 +203,15 @@ def upgrade(): class _TableToDrop(spanner_orm.Model): __table__ = 'TableToDrop' parent_key = spanner_orm.Field( - spanner_orm.String, primary_key=True) + spanner_orm.String(), primary_key=True) class _Child(spanner_orm.Model): __table__ = 'Child' __interleaved__ = _TableToDrop parent_key = spanner_orm.Field( - spanner_orm.String, primary_key=True) + spanner_orm.String(), primary_key=True) child_key = spanner_orm.Field( - spanner_orm.String, primary_key=True) + spanner_orm.String(), primary_key=True) def upgrade(): return spanner_orm.CreateTable(_Child) diff --git a/spanner_orm/tests/migrations_for_emulator_test/create_custom_length_field_f959b767457d.py b/spanner_orm/tests/migrations_for_emulator_test/create_custom_length_field_f959b767457d.py index 47d698b..915d1f3 100644 --- a/spanner_orm/tests/migrations_for_emulator_test/create_custom_length_field_f959b767457d.py +++ b/spanner_orm/tests/migrations_for_emulator_test/create_custom_length_field_f959b767457d.py @@ -16,7 +16,7 @@ class OriginalTeeTable(spanner_orm.model.Model): """ __table__ = 'Tee' - id = spanner_orm.Field(spanner_orm.String, primary_key=True) + id = spanner_orm.Field(spanner_orm.String(), primary_key=True) custom_string_length = spanner_orm.Field(spanner_orm.String(20)) custom_array_string_length = spanner_orm.Field( spanner_orm.Array(spanner_orm.String(4))) diff --git a/spanner_orm/tests/migrations_for_emulator_test/create_foreign_key_test_model.py b/spanner_orm/tests/migrations_for_emulator_test/create_foreign_key_test_model.py index bdd3935..02c947d 100644 --- a/spanner_orm/tests/migrations_for_emulator_test/create_foreign_key_test_model.py +++ b/spanner_orm/tests/migrations_for_emulator_test/create_foreign_key_test_model.py @@ -29,10 +29,10 @@ class OriginalForeignKeyTestModelTable(spanner_orm.model.Model): """ORM Model with the original schema for the ForeignKeyTestModel table.""" __table__ = 'ForeignKeyTestModel' - referencing_key_1 = field.Field(field.String, primary_key=True) - referencing_key_2 = field.Field(field.String, primary_key=True) - referencing_key_3 = field.Field(field.Integer, primary_key=True) - self_referencing_key = field.Field(field.String, nullable=True) + referencing_key_1 = field.Field(field.String(), primary_key=True) + referencing_key_2 = field.Field(field.String(), primary_key=True) + referencing_key_3 = field.Field(field.Integer(), primary_key=True) + self_referencing_key = field.Field(field.String(), nullable=True) foreign_key_1 = foreign_key_relationship.ForeignKeyRelationship( 'SmallTestModel', {'referencing_key_1': 'key'}) foreign_key_2 = foreign_key_relationship.ForeignKeyRelationship( diff --git a/spanner_orm/tests/migrations_for_emulator_test/create_null_filtered_index_model_760ec5fae5da.py b/spanner_orm/tests/migrations_for_emulator_test/create_null_filtered_index_model_760ec5fae5da.py index 494a000..4dafccb 100644 --- a/spanner_orm/tests/migrations_for_emulator_test/create_null_filtered_index_model_760ec5fae5da.py +++ b/spanner_orm/tests/migrations_for_emulator_test/create_null_filtered_index_model_760ec5fae5da.py @@ -25,9 +25,9 @@ class _NullFilteredIndexModel(spanner_orm.Model): __table__ = 'NullFilteredIndexModel' - key = spanner_orm.Field(spanner_orm.String, primary_key=True) - value_1 = spanner_orm.Field(spanner_orm.String, nullable=True) - value_2 = spanner_orm.Field(spanner_orm.Integer) + key = spanner_orm.Field(spanner_orm.String(), primary_key=True) + value_1 = spanner_orm.Field(spanner_orm.String(), nullable=True) + value_2 = spanner_orm.Field(spanner_orm.Integer()) def upgrade() -> spanner_orm.MigrationUpdate: diff --git a/spanner_orm/tests/migrations_for_emulator_test/create_small_test_model.py b/spanner_orm/tests/migrations_for_emulator_test/create_small_test_model.py index c4b019b..5878337 100644 --- a/spanner_orm/tests/migrations_for_emulator_test/create_small_test_model.py +++ b/spanner_orm/tests/migrations_for_emulator_test/create_small_test_model.py @@ -28,9 +28,9 @@ class OriginalSmallTestModelTable(spanner_orm.model.Model): """ORM Model with the original schema for the SmallTestModel table.""" __table__ = 'SmallTestModel' - key = field.Field(field.String, primary_key=True) - value_1 = field.Field(field.String) - value_2 = field.Field(field.String, nullable=True) + key = field.Field(field.String(), primary_key=True) + value_1 = field.Field(field.String()) + value_2 = field.Field(field.String(), nullable=True) def upgrade() -> spanner_orm.CreateTable: diff --git a/spanner_orm/tests/migrations_for_emulator_test/create_unittest_model.py b/spanner_orm/tests/migrations_for_emulator_test/create_unittest_model.py index 315d972..8fcf25e 100644 --- a/spanner_orm/tests/migrations_for_emulator_test/create_unittest_model.py +++ b/spanner_orm/tests/migrations_for_emulator_test/create_unittest_model.py @@ -28,18 +28,18 @@ class OriginalUnittestModelTable(spanner_orm.model.Model): """ORM Model with the original schema for the UnittestModel table.""" __table__ = 'table' - int_ = field.Field(field.Integer, primary_key=True) - int_2 = field.Field(field.Integer, nullable=True) - float_ = field.Field(field.Float, primary_key=True) - float_2 = field.Field(field.Float, nullable=True) - string = field.Field(field.String, primary_key=True) - string_2 = field.Field(field.String, nullable=True) + int_ = field.Field(field.Integer(), primary_key=True) + int_2 = field.Field(field.Integer(), nullable=True) + float_ = field.Field(field.Float(), primary_key=True) + float_2 = field.Field(field.Float(), nullable=True) + string = field.Field(field.String(), primary_key=True) + string_2 = field.Field(field.String(), nullable=True) string_3 = field.Field(field.String(20), nullable=True) bytes_ = field.Field(field.BytesBase64, primary_key=True) bytes_2 = field.Field(field.BytesBase64, nullable=True) bytes_3 = field.Field(field.BytesBase64(20), nullable=True) timestamp = field.Field(field.Timestamp) - string_array = field.Field(field.StringArray, nullable=True) + string_array = field.Field(field.Array(field.String()), nullable=True) string_array_2 = field.Field(field.Array(field.String(20)), nullable=True) From 4a1e84ea71192f0c2baedaeade5865e2423a5f63 Mon Sep 17 00:00:00 2001 From: jagankuamrm Date: Tue, 5 Aug 2025 18:37:08 +0000 Subject: [PATCH 03/11] spnner version upgrade --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b198738..1adfa23 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -45,7 +45,7 @@ jobs: pip install \ absl-py \ google-api-core \ - 'google-cloud-spanner >= 2, <4' \ + 'google-cloud-spanner >= 3, <4' \ immutabledict \ portpicker \ pytest From 1819bf0470d1bb0318dd408d64844c6a33bda875 Mon Sep 17 00:00:00 2001 From: jagankuamrm Date: Wed, 6 Aug 2025 14:36:41 +0000 Subject: [PATCH 04/11] empty commit --- spanner_orm/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spanner_orm/api.py b/spanner_orm/api.py index d049617..df7bd8a 100644 --- a/spanner_orm/api.py +++ b/spanner_orm/api.py @@ -72,7 +72,7 @@ def _run_read_only(self, method, *args, **kwargs): with self._connection.snapshot(multi_use=True) as snapshot: return method(snapshot, *args, **kwargs) - + class SpannerWriteApi(SpannerRetryableApi): """Handles sending write requests to Spanner.""" From 7b5810a608bd65939e862d53095ce67a8a18435a Mon Sep 17 00:00:00 2001 From: jagankumarm Date: Wed, 6 Aug 2025 20:22:04 +0530 Subject: [PATCH 05/11] Update api.py --- spanner_orm/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spanner_orm/api.py b/spanner_orm/api.py index df7bd8a..d049617 100644 --- a/spanner_orm/api.py +++ b/spanner_orm/api.py @@ -72,7 +72,7 @@ def _run_read_only(self, method, *args, **kwargs): with self._connection.snapshot(multi_use=True) as snapshot: return method(snapshot, *args, **kwargs) - + class SpannerWriteApi(SpannerRetryableApi): """Handles sending write requests to Spanner.""" From cd9b4d721fed08570eaae3b0bd611ea9cd47b7aa Mon Sep 17 00:00:00 2001 From: jagankuamrm Date: Thu, 7 Aug 2025 05:22:38 +0000 Subject: [PATCH 06/11] Resolve test race condition --- spanner_orm/testlib/spanner_emulator/emulator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spanner_orm/testlib/spanner_emulator/emulator.py b/spanner_orm/testlib/spanner_emulator/emulator.py index 2cafa8a..55f7a67 100644 --- a/spanner_orm/testlib/spanner_emulator/emulator.py +++ b/spanner_orm/testlib/spanner_emulator/emulator.py @@ -15,6 +15,7 @@ import os import subprocess +import time from typing import Mapping, Optional import portpicker @@ -65,6 +66,7 @@ def __init__(self, self._host_port = None self._start() + time.sleep(1) self._wait_for_ready() def get_client( From 6bae92c68d70033ecc9a35b535a037714369832f Mon Sep 17 00:00:00 2001 From: jagankuamrm Date: Thu, 14 Aug 2025 07:16:02 +0000 Subject: [PATCH 07/11] Fix for google-cloud-spanner v3 compatibility --- spanner_orm/api.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/spanner_orm/api.py b/spanner_orm/api.py index d049617..3799825 100644 --- a/spanner_orm/api.py +++ b/spanner_orm/api.py @@ -99,7 +99,14 @@ def run_write(self, method: Callable[..., CallableReturn], *args: Any, Returns: The return value from `method` will be returned from this method """ - return self._ensure_session(self._connection.run_in_transaction, method, + def wrapper_method(transaction, *w_args, **w_kwargs): + # This ensures the transaction object is the first argument passed to + # the original method. + return method(transaction, *w_args, **w_kwargs) + + # Pass the wrapper_method to run_in_transaction, which will handle the + # transaction lifecycle and provide the 'transaction' object. + return self._ensure_session(self._connection.run_in_transaction, wrapper_method, *args, **kwargs) From 480da7959b53cfc9ec904523eb3a52197404e003 Mon Sep 17 00:00:00 2001 From: jagankuamrm Date: Thu, 14 Aug 2025 07:43:35 +0000 Subject: [PATCH 08/11] wrapper change in run_write --- spanner_orm/api.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/spanner_orm/api.py b/spanner_orm/api.py index 3799825..f4ecfe2 100644 --- a/spanner_orm/api.py +++ b/spanner_orm/api.py @@ -31,6 +31,9 @@ class SpannerRetryableApi(abc.ABC): def _ensure_session(self, api_method, *args, **kwargs): + print(f"@@@@@@@@@@@@@DEBUG: api_method: {api_method.__name__}") + print(f"@@@@@@@@@@@@@DEBUG: args: {args}") + print(f"@@@@@@@@@@@@@DEBUG: kwargs: {kwargs}") try: return api_method(*args, **kwargs) except exceptions.NotFound as e: @@ -99,15 +102,16 @@ def run_write(self, method: Callable[..., CallableReturn], *args: Any, Returns: The return value from `method` will be returned from this method """ - def wrapper_method(transaction, *w_args, **w_kwargs): + def wrapper_method(transaction): # This ensures the transaction object is the first argument passed to # the original method. - return method(transaction, *w_args, **w_kwargs) + print(f"!!!!!!!!!!!!!!DEBUG: Calling method '{method.__name__}' with args={args} and kwargs={kwargs}") + + return method(transaction, *args, **kwargs) # Pass the wrapper_method to run_in_transaction, which will handle the # transaction lifecycle and provide the 'transaction' object. - return self._ensure_session(self._connection.run_in_transaction, wrapper_method, - *args, **kwargs) + return self._ensure_session(self._connection.run_in_transaction, wrapper_method) class SpannerConnection: From 74a2bb430d340a7f3a6637145f3ecb26f006284b Mon Sep 17 00:00:00 2001 From: jagankuamrm Date: Thu, 14 Aug 2025 08:53:56 +0000 Subject: [PATCH 09/11] test commit01 --- spanner_orm/api.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spanner_orm/api.py b/spanner_orm/api.py index f4ecfe2..84c6807 100644 --- a/spanner_orm/api.py +++ b/spanner_orm/api.py @@ -106,8 +106,10 @@ def wrapper_method(transaction): # This ensures the transaction object is the first argument passed to # the original method. print(f"!!!!!!!!!!!!!!DEBUG: Calling method '{method.__name__}' with args={args} and kwargs={kwargs}") - - return method(transaction, *args, **kwargs) + print(f"%%%%%%%%%%%DEBUG: Mutations count before method: {len(transaction._mutations)}") + result = method(transaction, *args, **kwargs) + print(f"DEBUG: Mutations count after method: {len(transaction._mutations)}") + return result # Pass the wrapper_method to run_in_transaction, which will handle the # transaction lifecycle and provide the 'transaction' object. From 4f64476aec4df2c97ae418f442156909c2c550d8 Mon Sep 17 00:00:00 2001 From: jagankuamrm Date: Fri, 15 Aug 2025 15:44:44 +0000 Subject: [PATCH 10/11] Update tot run_write --- spanner_orm/api.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/spanner_orm/api.py b/spanner_orm/api.py index 84c6807..63af010 100644 --- a/spanner_orm/api.py +++ b/spanner_orm/api.py @@ -102,18 +102,22 @@ def run_write(self, method: Callable[..., CallableReturn], *args: Any, Returns: The return value from `method` will be returned from this method """ - def wrapper_method(transaction): + def wrapper(transaction): # This ensures the transaction object is the first argument passed to # the original method. - print(f"!!!!!!!!!!!!!!DEBUG: Calling method '{method.__name__}' with args={args} and kwargs={kwargs}") + print(f"!!!!!!!!!!!!!!DEBUG: Calling method with args={args} and kwargs={kwargs}") print(f"%%%%%%%%%%%DEBUG: Mutations count before method: {len(transaction._mutations)}") result = method(transaction, *args, **kwargs) print(f"DEBUG: Mutations count after method: {len(transaction._mutations)}") + if not transaction._mutations: + # If no mutations, don't attempt a commit. + # Returning None or a specific value can signal this to the wrapper. + return None return result # Pass the wrapper_method to run_in_transaction, which will handle the # transaction lifecycle and provide the 'transaction' object. - return self._ensure_session(self._connection.run_in_transaction, wrapper_method) + return self._ensure_session(self._connection.run_in_transaction, wrapper) class SpannerConnection: From bbd0bf1b2f88012c2a620bebb353752987c89174 Mon Sep 17 00:00:00 2001 From: jagankuamrm Date: Sun, 17 Aug 2025 13:59:37 +0000 Subject: [PATCH 11/11] test commit --- spanner_orm/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spanner_orm/api.py b/spanner_orm/api.py index 63af010..edbf9f9 100644 --- a/spanner_orm/api.py +++ b/spanner_orm/api.py @@ -117,7 +117,8 @@ def wrapper(transaction): # Pass the wrapper_method to run_in_transaction, which will handle the # transaction lifecycle and provide the 'transaction' object. - return self._ensure_session(self._connection.run_in_transaction, wrapper) + if wrapper is not None: + return self._ensure_session(self._connection.run_in_transaction, wrapper) class SpannerConnection: