Repository: googleapis/python-spanner-django

Issue:
google-cloud-spanner version 3.12.0 has breaking changes for django tests
google-cloud-spanner version 3.12.0 has breaking changes for django tests

#### Environment details

  - Programming language: Python 3.9
  - OS: Mac os
  - Language runtime version: 3.9
  - Package version: Failing with `google-cloud-spanner==3.12.0`, and passing with `google-cloud-spanner==3.11.1`.

#### Steps to reproduce

1. pip install --upgrade google-cloud-spanner==3.12.0
# Run tests from django/tests directory update module.

2. python3 runtests.py update --settings=settings_emulator -v=3
`
Testing against Django installed in '/Users/vikashksingh/Documents/django/django'
Importing application update
Skipping setup of unused database(s): other.
Creating test database for alias 'default' ('test_db_name')...
Operations to perform:
  Synchronize unmigrated apps: auth, contenttypes, messages, sessions, staticfiles, update
  Apply all migrations: admin, sites
Running pre-migrate handlers for application contenttypes
Running pre-migrate handlers for application auth
Running pre-migrate handlers for application sites
Running pre-migrate handlers for application sessions
Running pre-migrate handlers for application admin
Running pre-migrate handlers for application update
Synchronizing apps without migrations:
  Creating tables...
    Creating table django_content_type
    Creating table auth_permission
    Creating table auth_group
    Creating table auth_user
    Creating table django_session
    Creating table update_datapoint
    Creating table update_relatedpoint
    Creating table update_a
    Creating table update_b
    Creating table update_c
    Creating table update_d
    Creating table update_foo
    Creating table update_bar
    Running deferred SQL...
Running migrations:
  Applying admin.0001_initial... OK (0.046s)
  Applying admin.0002_logentry_remove_auto_add... OK (0.031s)
  Applying admin.0003_logentry_add_action_flag_choices... OK (0.032s)
  Applying sites.0001_initial... OK (0.033s)
  Applying sites.0002_alter_domain_unique... OK (0.033s)
Running post-migrate handlers for application contenttypes
/Users/vikashksingh/Documents/django/django/db/backends/base/base.py:240: UserWarning: This method is non-operational in autocommit mode
  return self.connection.commit()
Adding content type 'contenttypes | contenttype'
Adding permission 'Permission object (4362588814226429155)'
Adding permission 'Permission object (2967634249719600951)'
Adding permission 'Permission object (4481795113432449832)'
Adding permission 'Permission object (1366612094307274520)'
Running post-migrate handlers for application auth
Adding content type 'auth | permission'
Adding content type 'auth | group'
Adding content type 'auth | user'
Adding permission 'Permission object (715656012358365970)'
Adding permission 'Permission object (2082790734310474769)'
Adding permission 'Permission object (918521468358711695)'
Adding permission 'Permission object (3112740880860426141)'
Adding permission 'Permission object (16364364746826556)'
Adding permission 'Permission object (969093102649393517)'
Adding permission 'Permission object (4413651130110496289)'
Adding permission 'Permission object (3356310745133748887)'
Adding permission 'Permission object (965794481936401277)'
Adding permission 'Permission object (1748086242466413853)'
Adding permission 'Permission object (3754759751424240163)'
Adding permission 'Permission object (903233622546495468)'
Running post-migrate handlers for application sites
Adding content type 'sites | site'
Adding permission 'Permission object (3274378424542215144)'
Adding permission 'Permission object (2286076591895297684)'
Adding permission 'Permission object (4571314045727603941)'
Adding permission 'Permission object (2905979564740043062)'
Creating example.com Site object
/Users/vikashksingh/Documents/django/django/db/backends/utils.py:23: UserWarning: The `rowcount` property is non-operational. Request resulting rows are streamed by the `fetch*()` methods and can't be counted before they are all streamed.
  cursor_attr = getattr(self.cursor, attr)
Traceback (most recent call last):
  File "/Users/vikashksingh/Documents/python-spanner-django/django_tests_dir/django/tests/runtests.py", line 503, in <module>
    failures = django_tests(
  File "/Users/vikashksingh/Documents/python-spanner-django/django_tests_dir/django/tests/runtests.py", line 292, in django_tests
    failures = test_runner.run_tests(
  File "/Users/vikashksingh/Documents/django/django/test/runner.py", line 629, in run_tests
    old_config = self.setup_databases(aliases=databases)
  File "/Users/vikashksingh/Documents/django/django/test/runner.py", line 552, in setup_databases
    return _setup_databases(
  File "/Users/vikashksingh/Documents/django/django/test/utils.py", line 170, in setup_databases
    connection.creation.create_test_db(
  File "/Users/vikashksingh/Documents/python-spanner-django/django_spanner/creation.py", line 47, in create_test_db
    super().create_test_db(*args, **kwargs)
  File "/Users/vikashksingh/Documents/django/django/db/backends/base/creation.py", line 67, in create_test_db
    call_command(
  File "/Users/vikashksingh/Documents/django/django/core/management/__init__.py", line 148, in call_command
    return command.execute(*args, **defaults)
  File "/Users/vikashksingh/Documents/django/django/core/management/base.py", line 364, in execute
    output = self.handle(*args, **options)
  File "/Users/vikashksingh/Documents/django/django/core/management/base.py", line 83, in wrapped
    res = handle_func(*args, **kwargs)
  File "/Users/vikashksingh/Documents/django/django/core/management/commands/migrate.py", line 256, in handle
    emit_post_migrate_signal(
  File "/Users/vikashksingh/Documents/django/django/core/management/sql.py", line 45, in emit_post_migrate_signal
    models.signals.post_migrate.send(
  File "/Users/vikashksingh/Documents/django/django/dispatch/dispatcher.py", line 173, in send
    return [
  File "/Users/vikashksingh/Documents/django/django/dispatch/dispatcher.py", line 174, in <listcomp>
    (receiver, receiver(signal=self, sender=sender, **named))
  File "/Users/vikashksingh/Documents/django/django/contrib/sites/management.py", line 28, in create_default_site
    Site(pk=getattr(settings, 'SITE_ID', 1), domain="example.com", name="example.com").save(using=using)
  File "/Users/vikashksingh/Documents/django/django/db/models/base.py", line 810, in save
    self.save_base(
  File "/Users/vikashksingh/Documents/django/django/db/models/base.py", line 861, in save_base
    updated = self._save_table(
  File "/Users/vikashksingh/Documents/django/django/db/models/base.py", line 965, in _save_table
    updated = self._do_update(
  File "/Users/vikashksingh/Documents/django/django/db/models/base.py", line 1018, in _do_update
    return filtered._update(values) > 0
TypeError: '>' not supported between instances of 'NoneType' and 'int'
`

# Downgrade the module
1. pip install --upgrade google-cloud-spanner==3.11.1
# Run the tests again.
2. ` python3 runtests.py update --settings=settings_emulator -v=3
Testing against Django installed in '/Users/vikashksingh/Documents/django/django'
Importing application update
Skipping setup of unused database(s): other.
Creating test database for alias 'default' ('test_db_name')...
Operations to perform:
  Synchronize unmigrated apps: auth, contenttypes, messages, sessions, staticfiles, update
  Apply all migrations: admin, sites
Running pre-migrate handlers for application contenttypes
Running pre-migrate handlers for application auth
Running pre-migrate handlers for application sites
Running pre-migrate handlers for application sessions
Running pre-migrate handlers for application admin
Running pre-migrate handlers for application update
Synchronizing apps without migrations:
  Creating tables...
    Creating table django_content_type
    Creating table auth_permission
    Creating table auth_group
    Creating table auth_user
    Creating table django_session
    Creating table update_datapoint
    Creating table update_relatedpoint
    Creating table update_a
    Creating table update_b
    Creating table update_c
    Creating table update_d
    Creating table update_foo
    Creating table update_bar
    Running deferred SQL...
Running migrations:
  Applying admin.0001_initial... OK (0.038s)
  Applying admin.0002_logentry_remove_auto_add... OK (0.023s)
  Applying admin.0003_logentry_add_action_flag_choices... OK (0.024s)
  Applying sites.0001_initial... OK (0.024s)
  Applying sites.0002_alter_domain_unique... OK (0.023s)
Running post-migrate handlers for application contenttypes
/Users/vikashksingh/Documents/django/django/db/backends/base/base.py:240: UserWarning: This method is non-operational in autocommit mode
  return self.connection.commit()
Adding content type 'contenttypes | contenttype'
Adding permission 'Permission object (3525277605650423382)'
Adding permission 'Permission object (4013415706134264071)'
Adding permission 'Permission object (2018686648871462149)'
Adding permission 'Permission object (3551506609038315091)'
Running post-migrate handlers for application auth
Adding content type 'auth | permission'
Adding content type 'auth | group'
Adding content type 'auth | user'
Adding permission 'Permission object (4384850809221257114)'
Adding permission 'Permission object (3197175033253349318)'
Adding permission 'Permission object (402400493283777869)'
Adding permission 'Permission object (710761551859464285)'
Adding permission 'Permission object (1681324163958586529)'
Adding permission 'Permission object (1012629662461498328)'
Adding permission 'Permission object (183700731866748242)'
Adding permission 'Permission object (3751780142241756317)'
Adding permission 'Permission object (3081767167228036312)'
Adding permission 'Permission object (2909052543102056336)'
Adding permission 'Permission object (2253052833335052245)'
Adding permission 'Permission object (1656432236181945777)'
Running post-migrate handlers for application sites
Adding content type 'sites | site'
Adding permission 'Permission object (1823637983986330801)'
Adding permission 'Permission object (2030597358957037947)'
Adding permission 'Permission object (1067911458432849323)'
Adding permission 'Permission object (4218628487978200040)'
Creating example.com Site object
Running post-migrate handlers for application sessions
Adding content type 'sessions | session'
Adding permission 'Permission object (93738861031287901)'
Adding permission 'Permission object (1235242571832069544)'
Adding permission 'Permission object (3135514601920288555)'
Adding permission 'Permission object (3243216062043376112)'
Running post-migrate handlers for application admin
Adding content type 'admin | logentry'
Adding permission 'Permission object (3875864294545390349)'
Adding permission 'Permission object (1168710879911368575)'
Adding permission 'Permission object (1823917002073095326)'
Adding permission 'Permission object (3912324087383026907)'
Running post-migrate handlers for application update
Adding content type 'update | datapoint'
Adding content type 'update | relatedpoint'
Adding content type 'update | a'
Adding content type 'update | b'
Adding content type 'update | c'
Adding content type 'update | d'
Adding content type 'update | foo'
Adding content type 'update | bar'
Adding permission 'Permission object (4448939834654202451)'
Adding permission 'Permission object (3882420249583068956)'
Adding permission 'Permission object (3152265850471895471)'
Adding permission 'Permission object (1745581012392653099)'
Adding permission 'Permission object (2347219098456841396)'
Adding permission 'Permission object (769218716725384672)'
Adding permission 'Permission object (3951825610497441793)'
Adding permission 'Permission object (1116774422292551319)'
Adding permission 'Permission object (786671814918751923)'
Adding permission 'Permission object (1754769067014251085)'
Adding permission 'Permission object (947423290072544581)'
Adding permission 'Permission object (1268866686597276510)'
Adding permission 'Permission object (1238999723741855617)'
Adding permission 'Permission object (1834238613395149773)'
Adding permission 'Permission object (515597464163625469)'
Adding permission 'Permission object (4031524391118614964)'
Adding permission 'Permission object (4583346725116478656)'
Adding permission 'Permission object (2322857065120216943)'
Adding permission 'Permission object (1239498040057856019)'
Adding permission 'Permission object (4478487281661264144)'
Adding permission 'Permission object (4431169480105194563)'
Adding permission 'Permission object (3059951386255990813)'
Adding permission 'Permission object (2352934731451435484)'
Adding permission 'Permission object (3227278489471367501)'
Adding permission 'Permission object (2093525068429916813)'
Adding permission 'Permission object (3773196473206107828)'
Adding permission 'Permission object (2665928986604359715)'
Adding permission 'Permission object (808651684883249681)'
Adding permission 'Permission object (779163951283803679)'
Adding permission 'Permission object (614332850464145479)'
Adding permission 'Permission object (4053131546293374686)'
Adding permission 'Permission object (1523787340855618732)'
System check identified no issues (0 silenced).
test_update (update.tests.AdvancedTests)
Objects are updated by first filtering the candidates into a queryset ... ok
/Users/vikashksingh/Documents/django/django/db/backends/base/base.py:240: UserWarning: This method is non-operational in autocommit mode
  return self.connection.commit()
test_update_all (update.tests.AdvancedTests)
In the rare case you want to update every instance of a model, update() ... ok
test_update_annotated_multi_table_queryset (update.tests.AdvancedTests)
Update of a queryset that's been annotated and involves multiple tables. ... ok
test_update_annotated_queryset (update.tests.AdvancedTests)
Update of a queryset that's been annotated. ... ok
test_update_fk (update.tests.AdvancedTests)
Foreign key fields can also be updated, although you can only update ... ok
test_update_m2m_field (update.tests.AdvancedTests) ... ok
test_update_multiple_fields (update.tests.AdvancedTests)
Multiple fields can be updated at once ... ok
test_update_multiple_objects (update.tests.AdvancedTests)
We can update multiple objects at once. ... ok
test_update_respects_to_field (update.tests.AdvancedTests)
Update of an FK field which specifies a to_field works. ... ok
test_update_slice_fail (update.tests.AdvancedTests)
We do not support update on already sliced query sets. ... ok
test_empty_update (update.tests.SimpleTest)
Update changes the right number of rows for an empty queryset ... ok
test_empty_update_with_inheritance (update.tests.SimpleTest)
Update changes the right number of rows for an empty queryset ... ok
test_foreign_key_update_with_id (update.tests.SimpleTest)
Update works using <field>_id for foreign keys ... ok
test_nonempty_update (update.tests.SimpleTest)
Update changes the right number of rows for a nonempty queryset ... ok
test_nonempty_update_with_inheritance (update.tests.SimpleTest)
Update changes the right number of rows for an empty queryset ... ok

----------------------------------------------------------------------
Ran 15 tests in 15.323s

OK
Destroying test database for alias 'default' ('test_db_name')...
`


Discussion:

Resolving patch:
diff --git a/noxfile.py b/noxfile.py
--- a/noxfile.py
+++ b/noxfile.py
@@ -76,7 +76,7 @@ def default(session, django_version="2.2"):
         "pytest-cov",
         "coverage",
         "sqlparse==0.3.0",
-        "google-cloud-spanner==3.11.1",
+        "google-cloud-spanner>=3.13.0",
         "opentelemetry-api==1.1.0",
         "opentelemetry-sdk==1.1.0",
         "opentelemetry-instrumentation==0.20b0",
@@ -146,7 +146,6 @@ def system_test(session, django_version="2.2"):
         constraints_path,
     )
     session.install("-e", ".[tracing]", "-c", constraints_path)
-
     # Run py.test against the system tests.
     if system_test_exists:
         session.run("py.test", "--quiet", system_test_path, *session.posargs)
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -17,7 +17,7 @@
 # 'Development Status :: 4 - Beta'
 # 'Development Status :: 5 - Production/Stable'
 release_status = "Development Status :: 5 - Production/Stable"
-dependencies = ["sqlparse >= 0.3.0", "google-cloud-spanner == 3.11.1"]
+dependencies = ["sqlparse >= 0.3.0", "google-cloud-spanner >= 3.13.0"]
 extras = {
     "tracing": [
         "opentelemetry-api >= 1.1.0",


Test patch:
diff --git a/testing/constraints-3.6.txt b/testing/constraints-3.6.txt
--- a/testing/constraints-3.6.txt
+++ b/testing/constraints-3.6.txt
@@ -6,7 +6,7 @@
 # e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev",
 # Then this file should have foo==1.14.0
 sqlparse==0.3.0
-google-cloud-spanner==3.11.1
+google-cloud-spanner>=3.13.0
 opentelemetry-api==1.1.0
 opentelemetry-sdk==1.1.0
 opentelemetry-instrumentation==0.20b0