google-cloud-spanner 3.53.0__tar.gz → 3.54.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. {google_cloud_spanner-3.53.0/google_cloud_spanner.egg-info → google_cloud_spanner-3.54.0}/PKG-INFO +1 -1
  2. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/gapic_version.py +1 -1
  3. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/gapic_version.py +1 -1
  4. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/batch_dml_executor.py +3 -0
  5. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/client_side_statement_executor.py +19 -2
  6. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/client_side_statement_parser.py +15 -8
  7. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/connection.py +39 -11
  8. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/cursor.py +3 -0
  9. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/parse_utils.py +16 -6
  10. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/parsed_statement.py +1 -0
  11. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/__init__.py +2 -1
  12. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/_helpers.py +12 -1
  13. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/data_types.py +148 -1
  14. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/gapic_version.py +1 -1
  15. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/param_types.py +1 -0
  16. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/streamed.py +1 -0
  17. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0/google_cloud_spanner.egg-info}/PKG-INFO +1 -1
  18. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google_cloud_spanner.egg-info/SOURCES.txt +2 -0
  19. google_cloud_spanner-3.54.0/tests/mockserver_tests/test_dbapi_isolation_level.py +150 -0
  20. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/_helpers.py +12 -1
  21. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/conftest.py +10 -3
  22. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/test_session_api.py +207 -0
  23. google_cloud_spanner-3.54.0/tests/unit/spanner_dbapi/test_client_side_statement_executor.py +54 -0
  24. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_parse_utils.py +95 -1
  25. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test__helpers.py +481 -0
  26. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_metrics.py +0 -1
  27. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/LICENSE +0 -0
  28. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/MANIFEST.in +0 -0
  29. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/README.rst +0 -0
  30. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner.py +0 -0
  31. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/__init__.py +0 -0
  32. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/gapic_metadata.json +0 -0
  33. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/py.typed +0 -0
  34. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/__init__.py +0 -0
  35. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/__init__.py +0 -0
  36. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/async_client.py +0 -0
  37. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/client.py +0 -0
  38. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/pagers.py +0 -0
  39. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/transports/__init__.py +0 -0
  40. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/transports/base.py +0 -0
  41. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/transports/grpc.py +0 -0
  42. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/transports/grpc_asyncio.py +0 -0
  43. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/transports/rest.py +0 -0
  44. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/transports/rest_base.py +0 -0
  45. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/types/__init__.py +0 -0
  46. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/types/backup.py +0 -0
  47. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/types/backup_schedule.py +0 -0
  48. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/types/common.py +0 -0
  49. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/types/spanner_database_admin.py +0 -0
  50. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/__init__.py +0 -0
  51. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/gapic_metadata.json +0 -0
  52. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/py.typed +0 -0
  53. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/__init__.py +0 -0
  54. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/__init__.py +0 -0
  55. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/async_client.py +0 -0
  56. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/client.py +0 -0
  57. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/pagers.py +0 -0
  58. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/__init__.py +0 -0
  59. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/base.py +0 -0
  60. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/grpc.py +0 -0
  61. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/grpc_asyncio.py +0 -0
  62. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/rest.py +0 -0
  63. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/rest_base.py +0 -0
  64. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/types/__init__.py +0 -0
  65. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/types/common.py +0 -0
  66. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/types/spanner_instance_admin.py +0 -0
  67. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/__init__.py +0 -0
  68. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/_helpers.py +0 -0
  69. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/checksum.py +0 -0
  70. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/exceptions.py +0 -0
  71. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/parser.py +0 -0
  72. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/partition_helper.py +0 -0
  73. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/transaction_helper.py +0 -0
  74. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/types.py +0 -0
  75. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/utils.py +0 -0
  76. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/version.py +0 -0
  77. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/_opentelemetry_tracing.py +0 -0
  78. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/backup.py +0 -0
  79. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/batch.py +0 -0
  80. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/client.py +0 -0
  81. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/database.py +0 -0
  82. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/gapic_metadata.json +0 -0
  83. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/instance.py +0 -0
  84. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/keyset.py +0 -0
  85. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/merged_result_set.py +0 -0
  86. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/metrics/constants.py +0 -0
  87. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/metrics/metrics_capture.py +0 -0
  88. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/metrics/metrics_exporter.py +0 -0
  89. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/metrics/metrics_interceptor.py +0 -0
  90. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/metrics/metrics_tracer.py +0 -0
  91. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/metrics/metrics_tracer_factory.py +0 -0
  92. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/metrics/spanner_metrics_tracer_factory.py +0 -0
  93. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/pool.py +0 -0
  94. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/py.typed +0 -0
  95. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/request_id_header.py +0 -0
  96. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/__init__.py +0 -0
  97. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/__init__.py +0 -0
  98. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/async_client.py +0 -0
  99. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/client.py +0 -0
  100. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/pagers.py +0 -0
  101. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/transports/__init__.py +0 -0
  102. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/transports/base.py +0 -0
  103. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/transports/grpc.py +0 -0
  104. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/transports/grpc_asyncio.py +0 -0
  105. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/transports/rest.py +0 -0
  106. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/transports/rest_base.py +0 -0
  107. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/session.py +0 -0
  108. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/snapshot.py +0 -0
  109. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/table.py +0 -0
  110. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/testing/__init__.py +0 -0
  111. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/testing/database_test.py +0 -0
  112. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/testing/interceptors.py +0 -0
  113. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/testing/mock_database_admin.py +0 -0
  114. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/testing/mock_spanner.py +0 -0
  115. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/testing/spanner_database_admin_pb2_grpc.py +0 -0
  116. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/testing/spanner_pb2_grpc.py +0 -0
  117. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/transaction.py +0 -0
  118. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/__init__.py +0 -0
  119. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/commit_response.py +0 -0
  120. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/keys.py +0 -0
  121. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/mutation.py +0 -0
  122. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/query_plan.py +0 -0
  123. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/result_set.py +0 -0
  124. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/spanner.py +0 -0
  125. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/transaction.py +0 -0
  126. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/type.py +0 -0
  127. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google_cloud_spanner.egg-info/dependency_links.txt +0 -0
  128. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google_cloud_spanner.egg-info/not-zip-safe +0 -0
  129. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google_cloud_spanner.egg-info/requires.txt +0 -0
  130. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google_cloud_spanner.egg-info/top_level.txt +0 -0
  131. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/setup.cfg +0 -0
  132. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/setup.py +0 -0
  133. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/__init__.py +0 -0
  134. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/_fixtures.py +0 -0
  135. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/_helpers.py +0 -0
  136. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/mockserver_tests/__init__.py +0 -0
  137. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/mockserver_tests/mock_server_test_base.py +0 -0
  138. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/mockserver_tests/test_aborted_transaction.py +0 -0
  139. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/mockserver_tests/test_basics.py +0 -0
  140. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/mockserver_tests/test_dbapi_autocommit.py +0 -0
  141. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/mockserver_tests/test_tags.py +0 -0
  142. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/__init__.py +0 -0
  143. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/_sample_data.py +0 -0
  144. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/test_backup_api.py +0 -0
  145. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/test_database_api.py +0 -0
  146. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/test_dbapi.py +0 -0
  147. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/test_instance_api.py +0 -0
  148. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/test_observability_options.py +0 -0
  149. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/test_streaming_chunking.py +0 -0
  150. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/test_table_api.py +0 -0
  151. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/testdata/descriptors.pb +0 -0
  152. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/testdata/singer.proto +0 -0
  153. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/testdata/singer_pb2.py +0 -0
  154. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/utils/__init__.py +0 -0
  155. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/utils/clear_streaming.py +0 -0
  156. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/utils/populate_streaming.py +0 -0
  157. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/utils/scrub_instances.py +0 -0
  158. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/utils/streaming_utils.py +0 -0
  159. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/__init__.py +0 -0
  160. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/gapic/__init__.py +0 -0
  161. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/gapic/spanner_admin_database_v1/__init__.py +0 -0
  162. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/gapic/spanner_admin_database_v1/test_database_admin.py +0 -0
  163. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/gapic/spanner_admin_instance_v1/__init__.py +0 -0
  164. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/gapic/spanner_admin_instance_v1/test_instance_admin.py +0 -0
  165. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/gapic/spanner_v1/__init__.py +0 -0
  166. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/gapic/spanner_v1/test_spanner.py +0 -0
  167. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/__init__.py +0 -0
  168. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test__helpers.py +0 -0
  169. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_batch_dml_executor.py +0 -0
  170. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_checksum.py +0 -0
  171. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_connect.py +0 -0
  172. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_connection.py +0 -0
  173. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_cursor.py +0 -0
  174. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_globals.py +0 -0
  175. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_parser.py +0 -0
  176. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_transaction_helper.py +0 -0
  177. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_types.py +0 -0
  178. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_utils.py +0 -0
  179. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/streaming-read-acceptance-test.json +0 -0
  180. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test__opentelemetry_tracing.py +0 -0
  181. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_atomic_counter.py +0 -0
  182. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_backup.py +0 -0
  183. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_batch.py +0 -0
  184. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_client.py +0 -0
  185. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_database.py +0 -0
  186. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_datatypes.py +0 -0
  187. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_instance.py +0 -0
  188. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_keyset.py +0 -0
  189. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_metrics_capture.py +0 -0
  190. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_metrics_exporter.py +0 -0
  191. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_metrics_interceptor.py +0 -0
  192. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_metrics_tracer.py +0 -0
  193. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_metrics_tracer_factory.py +0 -0
  194. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_packaging.py +0 -0
  195. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_param_types.py +0 -0
  196. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_pool.py +0 -0
  197. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_session.py +0 -0
  198. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_snapshot.py +0 -0
  199. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_spanner.py +0 -0
  200. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_spanner_metrics_tracer_factory.py +0 -0
  201. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_streamed.py +0 -0
  202. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_table.py +0 -0
  203. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_transaction.py +0 -0
  204. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/testdata/singer.proto +0 -0
  205. {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/testdata/singer_pb2.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: google-cloud-spanner
3
- Version: 3.53.0
3
+ Version: 3.54.0
4
4
  Summary: Google Cloud Spanner API client library
5
5
  Home-page: https://github.com/googleapis/python-spanner
6
6
  Author: Google LLC
@@ -13,4 +13,4 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
- __version__ = "3.53.0" # {x-release-please-version}
16
+ __version__ = "3.54.0" # {x-release-please-version}
@@ -13,4 +13,4 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
- __version__ = "3.53.0" # {x-release-please-version}
16
+ __version__ = "3.54.0" # {x-release-please-version}
@@ -54,9 +54,12 @@ class BatchDmlExecutor:
54
54
  """
55
55
  from google.cloud.spanner_dbapi import ProgrammingError
56
56
 
57
+ # Note: Let the server handle it if the client-side parser did not
58
+ # recognize the type of statement.
57
59
  if (
58
60
  parsed_statement.statement_type != StatementType.UPDATE
59
61
  and parsed_statement.statement_type != StatementType.INSERT
62
+ and parsed_statement.statement_type != StatementType.UNKNOWN
60
63
  ):
61
64
  raise ProgrammingError("Only DML statements are allowed in batch DML mode.")
62
65
  self._statements.append(parsed_statement.statement)
@@ -11,7 +11,8 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- from typing import TYPE_CHECKING
14
+ from typing import TYPE_CHECKING, Union
15
+ from google.cloud.spanner_v1 import TransactionOptions
15
16
 
16
17
  if TYPE_CHECKING:
17
18
  from google.cloud.spanner_dbapi.cursor import Cursor
@@ -58,7 +59,7 @@ def execute(cursor: "Cursor", parsed_statement: ParsedStatement):
58
59
  connection.commit()
59
60
  return None
60
61
  if statement_type == ClientSideStatementType.BEGIN:
61
- connection.begin()
62
+ connection.begin(isolation_level=_get_isolation_level(parsed_statement))
62
63
  return None
63
64
  if statement_type == ClientSideStatementType.ROLLBACK:
64
65
  connection.rollback()
@@ -121,3 +122,19 @@ def _get_streamed_result_set(column_name, type_code, column_values):
121
122
  column_values_pb.append(_make_value_pb(column_value))
122
123
  result_set.values.extend(column_values_pb)
123
124
  return StreamedResultSet(iter([result_set]))
125
+
126
+
127
+ def _get_isolation_level(
128
+ statement: ParsedStatement,
129
+ ) -> Union[TransactionOptions.IsolationLevel, None]:
130
+ if (
131
+ statement.client_side_statement_params is None
132
+ or len(statement.client_side_statement_params) == 0
133
+ ):
134
+ return None
135
+ level = statement.client_side_statement_params[0]
136
+ if not isinstance(level, str) or level == "":
137
+ return None
138
+ # Replace (duplicate) whitespaces in the string with an underscore.
139
+ level = "_".join(level.split()).upper()
140
+ return TransactionOptions.IsolationLevel[level]
@@ -21,18 +21,21 @@ from google.cloud.spanner_dbapi.parsed_statement import (
21
21
  Statement,
22
22
  )
23
23
 
24
- RE_BEGIN = re.compile(r"^\s*(BEGIN|START)(TRANSACTION)?", re.IGNORECASE)
25
- RE_COMMIT = re.compile(r"^\s*(COMMIT)(TRANSACTION)?", re.IGNORECASE)
26
- RE_ROLLBACK = re.compile(r"^\s*(ROLLBACK)(TRANSACTION)?", re.IGNORECASE)
24
+ RE_BEGIN = re.compile(
25
+ r"^\s*(?:BEGIN|START)(?:\s+TRANSACTION)?(?:\s+ISOLATION\s+LEVEL\s+(REPEATABLE\s+READ|SERIALIZABLE))?\s*$",
26
+ re.IGNORECASE,
27
+ )
28
+ RE_COMMIT = re.compile(r"^\s*(COMMIT)(\s+TRANSACTION)?\s*$", re.IGNORECASE)
29
+ RE_ROLLBACK = re.compile(r"^\s*(ROLLBACK)(\s+TRANSACTION)?\s*$", re.IGNORECASE)
27
30
  RE_SHOW_COMMIT_TIMESTAMP = re.compile(
28
- r"^\s*(SHOW)\s+(VARIABLE)\s+(COMMIT_TIMESTAMP)", re.IGNORECASE
31
+ r"^\s*(SHOW)\s+(VARIABLE)\s+(COMMIT_TIMESTAMP)\s*$", re.IGNORECASE
29
32
  )
30
33
  RE_SHOW_READ_TIMESTAMP = re.compile(
31
- r"^\s*(SHOW)\s+(VARIABLE)\s+(READ_TIMESTAMP)", re.IGNORECASE
34
+ r"^\s*(SHOW)\s+(VARIABLE)\s+(READ_TIMESTAMP)\s*$", re.IGNORECASE
32
35
  )
33
- RE_START_BATCH_DML = re.compile(r"^\s*(START)\s+(BATCH)\s+(DML)", re.IGNORECASE)
34
- RE_RUN_BATCH = re.compile(r"^\s*(RUN)\s+(BATCH)", re.IGNORECASE)
35
- RE_ABORT_BATCH = re.compile(r"^\s*(ABORT)\s+(BATCH)", re.IGNORECASE)
36
+ RE_START_BATCH_DML = re.compile(r"^\s*(START)\s+(BATCH)\s+(DML)\s*$", re.IGNORECASE)
37
+ RE_RUN_BATCH = re.compile(r"^\s*(RUN)\s+(BATCH)\s*$", re.IGNORECASE)
38
+ RE_ABORT_BATCH = re.compile(r"^\s*(ABORT)\s+(BATCH)\s*$", re.IGNORECASE)
36
39
  RE_PARTITION_QUERY = re.compile(r"^\s*(PARTITION)\s+(.+)", re.IGNORECASE)
37
40
  RE_RUN_PARTITION = re.compile(r"^\s*(RUN)\s+(PARTITION)\s+(.+)", re.IGNORECASE)
38
41
  RE_RUN_PARTITIONED_QUERY = re.compile(
@@ -68,6 +71,10 @@ def parse_stmt(query):
68
71
  elif RE_START_BATCH_DML.match(query):
69
72
  client_side_statement_type = ClientSideStatementType.START_BATCH_DML
70
73
  elif RE_BEGIN.match(query):
74
+ match = re.search(RE_BEGIN, query)
75
+ isolation_level = match.group(1)
76
+ if isolation_level is not None:
77
+ client_side_statement_params.append(isolation_level)
71
78
  client_side_statement_type = ClientSideStatementType.BEGIN
72
79
  elif RE_RUN_BATCH.match(query):
73
80
  client_side_statement_type = ClientSideStatementType.RUN_BATCH
@@ -20,16 +20,12 @@ from google.api_core.gapic_v1.client_info import ClientInfo
20
20
  from google.cloud import spanner_v1 as spanner
21
21
  from google.cloud.spanner_dbapi import partition_helper
22
22
  from google.cloud.spanner_dbapi.batch_dml_executor import BatchMode, BatchDmlExecutor
23
- from google.cloud.spanner_dbapi.parse_utils import _get_statement_type
24
- from google.cloud.spanner_dbapi.parsed_statement import (
25
- StatementType,
26
- AutocommitDmlMode,
27
- )
23
+ from google.cloud.spanner_dbapi.parsed_statement import AutocommitDmlMode
28
24
  from google.cloud.spanner_dbapi.partition_helper import PartitionId
29
25
  from google.cloud.spanner_dbapi.parsed_statement import ParsedStatement, Statement
30
26
  from google.cloud.spanner_dbapi.transaction_helper import TransactionRetryHelper
31
27
  from google.cloud.spanner_dbapi.cursor import Cursor
32
- from google.cloud.spanner_v1 import RequestOptions
28
+ from google.cloud.spanner_v1 import RequestOptions, TransactionOptions
33
29
  from google.cloud.spanner_v1.snapshot import Snapshot
34
30
 
35
31
  from google.cloud.spanner_dbapi.exceptions import (
@@ -112,6 +108,7 @@ class Connection:
112
108
  self._staleness = None
113
109
  self.request_priority = None
114
110
  self._transaction_begin_marked = False
111
+ self._transaction_isolation_level = None
115
112
  # whether transaction started at Spanner. This means that we had
116
113
  # made at least one call to Spanner.
117
114
  self._spanner_transaction_started = False
@@ -283,6 +280,33 @@ class Connection:
283
280
  """
284
281
  self._connection_variables["transaction_tag"] = value
285
282
 
283
+ @property
284
+ def isolation_level(self):
285
+ """The default isolation level that is used for all read/write
286
+ transactions on this `Connection`.
287
+
288
+ Returns:
289
+ google.cloud.spanner_v1.types.TransactionOptions.IsolationLevel:
290
+ The isolation level that is used for read/write transactions on
291
+ this `Connection`.
292
+ """
293
+ return self._connection_variables.get(
294
+ "isolation_level",
295
+ TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED,
296
+ )
297
+
298
+ @isolation_level.setter
299
+ def isolation_level(self, value: TransactionOptions.IsolationLevel):
300
+ """Sets the isolation level that is used for all read/write
301
+ transactions on this `Connection`.
302
+
303
+ Args:
304
+ value (google.cloud.spanner_v1.types.TransactionOptions.IsolationLevel):
305
+ The isolation level for all read/write transactions on this
306
+ `Connection`.
307
+ """
308
+ self._connection_variables["isolation_level"] = value
309
+
286
310
  @property
287
311
  def staleness(self):
288
312
  """Current read staleness option value of this `Connection`.
@@ -363,6 +387,12 @@ class Connection:
363
387
  if not self._spanner_transaction_started:
364
388
  self._transaction = self._session_checkout().transaction()
365
389
  self._transaction.transaction_tag = self.transaction_tag
390
+ if self._transaction_isolation_level:
391
+ self._transaction.isolation_level = (
392
+ self._transaction_isolation_level
393
+ )
394
+ else:
395
+ self._transaction.isolation_level = self.isolation_level
366
396
  self.transaction_tag = None
367
397
  self._snapshot = None
368
398
  self._spanner_transaction_started = True
@@ -405,7 +435,7 @@ class Connection:
405
435
  self.is_closed = True
406
436
 
407
437
  @check_not_closed
408
- def begin(self):
438
+ def begin(self, isolation_level=None):
409
439
  """
410
440
  Marks the transaction as started.
411
441
 
@@ -421,6 +451,7 @@ class Connection:
421
451
  "is already running"
422
452
  )
423
453
  self._transaction_begin_marked = True
454
+ self._transaction_isolation_level = isolation_level
424
455
 
425
456
  def commit(self):
426
457
  """Commits any pending transaction to the database.
@@ -465,6 +496,7 @@ class Connection:
465
496
  self._release_session()
466
497
  self._transaction_helper.reset()
467
498
  self._transaction_begin_marked = False
499
+ self._transaction_isolation_level = None
468
500
  self._spanner_transaction_started = False
469
501
 
470
502
  @check_not_closed
@@ -666,10 +698,6 @@ class Connection:
666
698
  self._autocommit_dml_mode = autocommit_dml_mode
667
699
 
668
700
  def _partitioned_query_validation(self, partitioned_query, statement):
669
- if _get_statement_type(Statement(partitioned_query)) is not StatementType.QUERY:
670
- raise ProgrammingError(
671
- "Only queries can be partitioned. Invalid statement: " + statement.sql
672
- )
673
701
  if self.read_only is not True and self._client_transaction_started is True:
674
702
  raise ProgrammingError(
675
703
  "Partitioned query is not supported, because the connection is in a read/write transaction."
@@ -404,9 +404,12 @@ class Cursor(object):
404
404
  # For every operation, we've got to ensure that any prior DDL
405
405
  # statements were run.
406
406
  self.connection.run_prior_DDL_statements()
407
+ # Treat UNKNOWN statements as if they are DML and let the server
408
+ # determine what is wrong with it.
407
409
  if self._parsed_statement.statement_type in (
408
410
  StatementType.INSERT,
409
411
  StatementType.UPDATE,
412
+ StatementType.UNKNOWN,
410
413
  ):
411
414
  statements = []
412
415
  for params in seq_of_params:
@@ -155,6 +155,7 @@ STMT_UPDATING = "UPDATING"
155
155
  STMT_INSERT = "INSERT"
156
156
 
157
157
  # Heuristic for identifying statements that don't need to be run as updates.
158
+ # TODO: This and the other regexes do not match statements that start with a hint.
158
159
  RE_NON_UPDATE = re.compile(r"^\W*(SELECT|GRAPH|FROM)", re.IGNORECASE)
159
160
 
160
161
  RE_WITH = re.compile(r"^\s*(WITH)", re.IGNORECASE)
@@ -162,18 +163,22 @@ RE_WITH = re.compile(r"^\s*(WITH)", re.IGNORECASE)
162
163
  # DDL statements follow
163
164
  # https://cloud.google.com/spanner/docs/data-definition-language
164
165
  RE_DDL = re.compile(
165
- r"^\s*(CREATE|ALTER|DROP|GRANT|REVOKE|RENAME)", re.IGNORECASE | re.DOTALL
166
+ r"^\s*(CREATE|ALTER|DROP|GRANT|REVOKE|RENAME|ANALYZE)", re.IGNORECASE | re.DOTALL
166
167
  )
167
168
 
168
- RE_IS_INSERT = re.compile(r"^\s*(INSERT)", re.IGNORECASE | re.DOTALL)
169
+ # TODO: These do not match statements that start with a hint.
170
+ RE_IS_INSERT = re.compile(r"^\s*(INSERT\s+)", re.IGNORECASE | re.DOTALL)
171
+ RE_IS_UPDATE = re.compile(r"^\s*(UPDATE\s+)", re.IGNORECASE | re.DOTALL)
172
+ RE_IS_DELETE = re.compile(r"^\s*(DELETE\s+)", re.IGNORECASE | re.DOTALL)
169
173
 
170
174
  RE_INSERT = re.compile(
171
175
  # Only match the `INSERT INTO <table_name> (columns...)
172
176
  # otherwise the rest of the statement could be a complex
173
177
  # operation.
174
- r"^\s*INSERT INTO (?P<table_name>[^\s\(\)]+)\s*\((?P<columns>[^\(\)]+)\)",
178
+ r"^\s*INSERT(?:\s+INTO)?\s+(?P<table_name>[^\s\(\)]+)\s*\((?P<columns>[^\(\)]+)\)",
175
179
  re.IGNORECASE | re.DOTALL,
176
180
  )
181
+ """Deprecated: Use the RE_IS_INSERT, RE_IS_UPDATE, and RE_IS_DELETE regexes"""
177
182
 
178
183
  RE_VALUES_TILL_END = re.compile(r"VALUES\s*\(.+$", re.IGNORECASE | re.DOTALL)
179
184
 
@@ -259,8 +264,13 @@ def _get_statement_type(statement):
259
264
  # statements and doesn't yet support WITH for DML statements.
260
265
  return StatementType.QUERY
261
266
 
262
- statement.sql = ensure_where_clause(query)
263
- return StatementType.UPDATE
267
+ if RE_IS_UPDATE.match(query) or RE_IS_DELETE.match(query):
268
+ # TODO: Remove this? It makes more sense to have this in SQLAlchemy and
269
+ # Django than here.
270
+ statement.sql = ensure_where_clause(query)
271
+ return StatementType.UPDATE
272
+
273
+ return StatementType.UNKNOWN
264
274
 
265
275
 
266
276
  def sql_pyformat_args_to_spanner(sql, params):
@@ -355,7 +365,7 @@ def get_param_types(params):
355
365
  def ensure_where_clause(sql):
356
366
  """
357
367
  Cloud Spanner requires a WHERE clause on UPDATE and DELETE statements.
358
- Add a dummy WHERE clause if non detected.
368
+ Add a dummy WHERE clause if not detected.
359
369
 
360
370
  :type sql: str
361
371
  :param sql: SQL code to check.
@@ -17,6 +17,7 @@ from typing import Any, List
17
17
 
18
18
 
19
19
  class StatementType(Enum):
20
+ UNKNOWN = 0
20
21
  CLIENT_SIDE = 1
21
22
  DDL = 2
22
23
  QUERY = 3
@@ -63,7 +63,7 @@ from .types.type import StructType
63
63
  from .types.type import Type
64
64
  from .types.type import TypeAnnotationCode
65
65
  from .types.type import TypeCode
66
- from .data_types import JsonObject
66
+ from .data_types import JsonObject, Interval
67
67
  from .transaction import BatchTransactionId, DefaultTransactionOptions
68
68
 
69
69
  from google.cloud.spanner_v1 import param_types
@@ -145,6 +145,7 @@ __all__ = (
145
145
  "TypeCode",
146
146
  # Custom spanner related data types
147
147
  "JsonObject",
148
+ "Interval",
148
149
  # google.cloud.spanner_v1.services
149
150
  "SpannerClient",
150
151
  "SpannerAsyncClient",
@@ -31,7 +31,7 @@ from google.api_core.exceptions import Aborted
31
31
  from google.cloud._helpers import _date_from_iso8601_date
32
32
  from google.cloud.spanner_v1 import TypeCode
33
33
  from google.cloud.spanner_v1 import ExecuteSqlRequest
34
- from google.cloud.spanner_v1 import JsonObject
34
+ from google.cloud.spanner_v1 import JsonObject, Interval
35
35
  from google.cloud.spanner_v1 import TransactionOptions
36
36
  from google.cloud.spanner_v1.request_id_header import with_request_id
37
37
  from google.rpc.error_details_pb2 import RetryInfo
@@ -251,6 +251,8 @@ def _make_value_pb(value):
251
251
  return Value(null_value="NULL_VALUE")
252
252
  else:
253
253
  return Value(string_value=base64.b64encode(value))
254
+ if isinstance(value, Interval):
255
+ return Value(string_value=str(value))
254
256
 
255
257
  raise ValueError("Unknown type: %s" % (value,))
256
258
 
@@ -367,6 +369,8 @@ def _get_type_decoder(field_type, field_name, column_info=None):
367
369
  for item_field in field_type.struct_type.fields
368
370
  ]
369
371
  return lambda value_pb: _parse_struct(value_pb, element_decoders)
372
+ elif type_code == TypeCode.INTERVAL:
373
+ return _parse_interval
370
374
  else:
371
375
  raise ValueError("Unknown type: %s" % (field_type,))
372
376
 
@@ -473,6 +477,13 @@ def _parse_nullable(value_pb, decoder):
473
477
  return decoder(value_pb)
474
478
 
475
479
 
480
+ def _parse_interval(value_pb):
481
+ """Parse a Value protobuf containing an interval."""
482
+ if hasattr(value_pb, "string_value"):
483
+ return Interval.from_str(value_pb.string_value)
484
+ return Interval.from_str(value_pb)
485
+
486
+
476
487
  class _SessionWrapper(object):
477
488
  """Base class for objects wrapping a session.
478
489
 
@@ -16,7 +16,8 @@
16
16
 
17
17
  import json
18
18
  import types
19
-
19
+ import re
20
+ from dataclasses import dataclass
20
21
  from google.protobuf.message import Message
21
22
  from google.protobuf.internal.enum_type_wrapper import EnumTypeWrapper
22
23
 
@@ -97,6 +98,152 @@ class JsonObject(dict):
97
98
  return json.dumps(self, sort_keys=True, separators=(",", ":"))
98
99
 
99
100
 
101
+ @dataclass
102
+ class Interval:
103
+ """Represents a Spanner INTERVAL type.
104
+
105
+ An interval is a combination of months, days and nanoseconds.
106
+ Internally, Spanner supports Interval value with the following range of individual fields:
107
+ months: [-120000, 120000]
108
+ days: [-3660000, 3660000]
109
+ nanoseconds: [-316224000000000000000, 316224000000000000000]
110
+ """
111
+
112
+ months: int = 0
113
+ days: int = 0
114
+ nanos: int = 0
115
+
116
+ def __str__(self) -> str:
117
+ """Returns the ISO8601 duration format string representation."""
118
+ result = ["P"]
119
+
120
+ # Handle years and months
121
+ if self.months:
122
+ is_negative = self.months < 0
123
+ abs_months = abs(self.months)
124
+ years, months = divmod(abs_months, 12)
125
+ if years:
126
+ result.append(f"{'-' if is_negative else ''}{years}Y")
127
+ if months:
128
+ result.append(f"{'-' if is_negative else ''}{months}M")
129
+
130
+ # Handle days
131
+ if self.days:
132
+ result.append(f"{self.days}D")
133
+
134
+ # Handle time components
135
+ if self.nanos:
136
+ result.append("T")
137
+ nanos = abs(self.nanos)
138
+ is_negative = self.nanos < 0
139
+
140
+ # Convert to hours, minutes, seconds
141
+ nanos_per_hour = 3600000000000
142
+ hours, nanos = divmod(nanos, nanos_per_hour)
143
+ if hours:
144
+ if is_negative:
145
+ result.append("-")
146
+ result.append(f"{hours}H")
147
+
148
+ nanos_per_minute = 60000000000
149
+ minutes, nanos = divmod(nanos, nanos_per_minute)
150
+ if minutes:
151
+ if is_negative:
152
+ result.append("-")
153
+ result.append(f"{minutes}M")
154
+
155
+ nanos_per_second = 1000000000
156
+ seconds, nanos_fraction = divmod(nanos, nanos_per_second)
157
+
158
+ if seconds or nanos_fraction:
159
+ if is_negative:
160
+ result.append("-")
161
+ if seconds:
162
+ result.append(str(seconds))
163
+ elif nanos_fraction:
164
+ result.append("0")
165
+
166
+ if nanos_fraction:
167
+ nano_str = f"{nanos_fraction:09d}"
168
+ trimmed = nano_str.rstrip("0")
169
+ if len(trimmed) <= 3:
170
+ while len(trimmed) < 3:
171
+ trimmed += "0"
172
+ elif len(trimmed) <= 6:
173
+ while len(trimmed) < 6:
174
+ trimmed += "0"
175
+ else:
176
+ while len(trimmed) < 9:
177
+ trimmed += "0"
178
+ result.append(f".{trimmed}")
179
+ result.append("S")
180
+
181
+ if len(result) == 1:
182
+ result.append("0Y") # Special case for zero interval
183
+
184
+ return "".join(result)
185
+
186
+ @classmethod
187
+ def from_str(cls, s: str) -> "Interval":
188
+ """Parse an ISO8601 duration format string into an Interval."""
189
+ pattern = r"^P(-?\d+Y)?(-?\d+M)?(-?\d+D)?(T(-?\d+H)?(-?\d+M)?(-?((\d+([.,]\d{1,9})?)|([.,]\d{1,9}))S)?)?$"
190
+ match = re.match(pattern, s)
191
+ if not match or len(s) == 1:
192
+ raise ValueError(f"Invalid interval format: {s}")
193
+
194
+ parts = match.groups()
195
+ if not any(parts[:3]) and not parts[3]:
196
+ raise ValueError(
197
+ f"Invalid interval format: at least one component (Y/M/D/H/M/S) is required: {s}"
198
+ )
199
+
200
+ if parts[3] == "T" and not any(parts[4:7]):
201
+ raise ValueError(
202
+ f"Invalid interval format: time designator 'T' present but no time components specified: {s}"
203
+ )
204
+
205
+ def parse_num(s: str, suffix: str) -> int:
206
+ if not s:
207
+ return 0
208
+ return int(s.rstrip(suffix))
209
+
210
+ years = parse_num(parts[0], "Y")
211
+ months = parse_num(parts[1], "M")
212
+ total_months = years * 12 + months
213
+
214
+ days = parse_num(parts[2], "D")
215
+
216
+ nanos = 0
217
+ if parts[3]: # Has time component
218
+ # Convert hours to nanoseconds
219
+ hours = parse_num(parts[4], "H")
220
+ nanos += hours * 3600000000000
221
+
222
+ # Convert minutes to nanoseconds
223
+ minutes = parse_num(parts[5], "M")
224
+ nanos += minutes * 60000000000
225
+
226
+ # Handle seconds and fractional seconds
227
+ if parts[6]:
228
+ seconds = parts[6].rstrip("S")
229
+ if "," in seconds:
230
+ seconds = seconds.replace(",", ".")
231
+
232
+ if "." in seconds:
233
+ sec_parts = seconds.split(".")
234
+ whole_seconds = sec_parts[0] if sec_parts[0] else "0"
235
+ nanos += int(whole_seconds) * 1000000000
236
+ frac = sec_parts[1][:9].ljust(9, "0")
237
+ frac_nanos = int(frac)
238
+ if seconds.startswith("-"):
239
+ frac_nanos = -frac_nanos
240
+ nanos += frac_nanos
241
+ else:
242
+ nanos += int(seconds) * 1000000000
243
+
244
+ return cls(months=total_months, days=days, nanos=nanos)
245
+
246
+
100
247
  def _proto_message(bytes_val, proto_message_object):
101
248
  """Helper for :func:`get_proto_message`.
102
249
  parses serialized protocol buffer bytes data into proto message.
@@ -13,4 +13,4 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
- __version__ = "3.53.0" # {x-release-please-version}
16
+ __version__ = "3.54.0" # {x-release-please-version}
@@ -36,6 +36,7 @@ JSON = Type(code=TypeCode.JSON)
36
36
  PG_NUMERIC = Type(code=TypeCode.NUMERIC, type_annotation=TypeAnnotationCode.PG_NUMERIC)
37
37
  PG_JSONB = Type(code=TypeCode.JSON, type_annotation=TypeAnnotationCode.PG_JSONB)
38
38
  PG_OID = Type(code=TypeCode.INT64, type_annotation=TypeAnnotationCode.PG_OID)
39
+ INTERVAL = Type(code=TypeCode.INTERVAL)
39
40
 
40
41
 
41
42
  def Array(element_type):
@@ -391,6 +391,7 @@ _MERGE_BY_TYPE = {
391
391
  TypeCode.NUMERIC: _merge_string,
392
392
  TypeCode.JSON: _merge_string,
393
393
  TypeCode.PROTO: _merge_string,
394
+ TypeCode.INTERVAL: _merge_string,
394
395
  TypeCode.ENUM: _merge_string,
395
396
  }
396
397
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: google-cloud-spanner
3
- Version: 3.53.0
3
+ Version: 3.54.0
4
4
  Summary: Google Cloud Spanner API client library
5
5
  Home-page: https://github.com/googleapis/python-spanner
6
6
  Author: Google LLC
@@ -129,6 +129,7 @@ tests/mockserver_tests/mock_server_test_base.py
129
129
  tests/mockserver_tests/test_aborted_transaction.py
130
130
  tests/mockserver_tests/test_basics.py
131
131
  tests/mockserver_tests/test_dbapi_autocommit.py
132
+ tests/mockserver_tests/test_dbapi_isolation_level.py
132
133
  tests/mockserver_tests/test_tags.py
133
134
  tests/system/__init__.py
134
135
  tests/system/_helpers.py
@@ -189,6 +190,7 @@ tests/unit/spanner_dbapi/__init__.py
189
190
  tests/unit/spanner_dbapi/test__helpers.py
190
191
  tests/unit/spanner_dbapi/test_batch_dml_executor.py
191
192
  tests/unit/spanner_dbapi/test_checksum.py
193
+ tests/unit/spanner_dbapi/test_client_side_statement_executor.py
192
194
  tests/unit/spanner_dbapi/test_connect.py
193
195
  tests/unit/spanner_dbapi/test_connection.py
194
196
  tests/unit/spanner_dbapi/test_cursor.py