diff --git a/.librarian/generator-input/client-post-processing/spanner-integration.yaml b/.librarian/generator-input/client-post-processing/spanner-integration.yaml index 1de5d9c7ad5e..71dd03579fec 100644 --- a/.librarian/generator-input/client-post-processing/spanner-integration.yaml +++ b/.librarian/generator-input/client-post-processing/spanner-integration.yaml @@ -122,17 +122,22 @@ replacements: \g<3>\g<4> count: 1 - paths: [packages/google-cloud-spanner/setup.py] - before: '(?s)dependencies = \[.*?\]\nextras = \{\}' + before: '(?s)dependencies = \[.*?\]\nextras = \{\s*\}' after: | dependencies = [ - "google-api-core[grpc] >= 1.34.0, <3.0.0,!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,!=2.10.*", - "google-cloud-core >= 1.4.4, < 3.0.0", + "google-api-core[grpc] >= 2.17.1, <3.0.0", + # Exclude incompatible versions of `google-auth` + # See https://github.com/googleapis/google-cloud-python/issues/12364 + "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", + "google-cloud-core >= 2.0.0, < 3.0.0", + "grpcio >= 1.49.1, < 2.0.0", + "grpcio >= 1.75.1, < 2.0.0; python_version >= '3.14'", "grpc-google-iam-v1 >= 0.12.4, <1.0.0", - "proto-plus >= 1.22.0, <2.0.0", - "sqlparse >= 0.4.4", - "proto-plus >= 1.22.2, <2.0.0; python_version>='3.11'", + "proto-plus >= 1.22.3, <2.0.0", + "proto-plus >= 1.25.0, <2.0.0; python_version >= '3.13'", "protobuf >= 4.25.8, < 8.0.0", "grpc-interceptor >= 0.15.4", + "sqlparse >= 0.4.4", # Make OpenTelemetry a core dependency "opentelemetry-api >= 1.22.0", "opentelemetry-sdk >= 1.22.0", @@ -601,6 +606,40 @@ replacements: Next Steps count: 1 + + - paths: [ + packages/google-cloud-spanner/README.rst + ] + before: | + Supported Python Versions + \^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^ + Our client libraries are compatible with all current `active`_ and `maintenance`_ versions of + Python\. + + Python >= 3\.9, including 3\.14 + + \.\. _active: https://devguide\.python\.org/devcycle/#in-development-main-branch + \.\. _maintenance: https://devguide\.python\.org/devcycle/#maintenance-branches + + Unsupported Python Versions + \^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^ + Python <= 3\.8 + after: | + Supported Python Versions + ^^^^^^^^^^^^^^^^^^^^^^^^^ + Our client libraries are compatible with all current `active`_ and `maintenance`_ versions of + Python. + + Python >= 3.10, including 3.14 + + .. _active: https://devguide.python.org/devcycle/#in-development-main-branch + .. _maintenance: https://devguide.python.org/devcycle/#maintenance-branches + + Unsupported Python Versions + ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Python <= 3.9 + count: 1 + # Note: noxfile.py is heavily customized so we clobber the whole file. - paths: [ packages/google-cloud-spanner/noxfile.py @@ -627,7 +666,6 @@ replacements: SYSTEM_TEST_PYTHON_VERSIONS: List[str] = ["3.12"] ALL_PYTHON: List[str] = [ - "3.9", "3.10", "3.11", "3.12", @@ -667,7 +705,6 @@ replacements: CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() nox.options.sessions = [ - "unit-3.9", "unit-3.10", "unit-3.11", "unit-3.12", @@ -816,8 +853,6 @@ replacements: def unit(session, protobuf_implementation): # Install all test dependencies, then install this package in-place. - if session.python in ("3.7",): - session.skip("Python 3.7 is no longer supported") if protobuf_implementation == "cpp" and session.python in ( "3.11", "3.12", @@ -1379,4 +1414,27 @@ replacements: ) def core_deps_from_source(session, protobuf_implementation): """Run all tests with core dependencies installed from source, + count: 1 + - paths: [packages/google-cloud-spanner/testing/constraints-3.10.txt] + before: '(?s)protobuf==4.25.8\n(?!google-cloud-core)' + after: | + protobuf==4.25.8 + google-cloud-core==2.0.0 + grpc-google-iam-v1==0.12.4 + sqlparse==0.4.4 + grpc-interceptor==0.15.4 + opentelemetry-api==1.22.0 + opentelemetry-sdk==1.22.0 + opentelemetry-semantic-conventions==0.43b0 + opentelemetry-resourcedetector-gcp==1.8.0a0 + google-cloud-monitoring==2.16.0 + mmh3==4.1.0 + libcst==0.2.5 + googleapis-common-protos==1.60.0 + count: 1 + - paths: [packages/google-cloud-spanner/testing/constraints-3.10.txt] + before: 'grpcio==1\.44\.0\n(?!grpcio-status)' + after: | + grpcio==1.49.1 + grpcio-status==1.49.1 count: 1 \ No newline at end of file diff --git a/librarian.yaml b/librarian.yaml index 51c51b3ccc83..833df66084c9 100644 --- a/librarian.yaml +++ b/librarian.yaml @@ -1909,7 +1909,6 @@ libraries: - docs/spanner_v1/table.rst - docs/spanner_v1/transaction.rst - tests/unit/gapic/conftest.py - skip_generate: true python: library_type: GAPIC_COMBO opt_args_by_api: diff --git a/packages/google-cloud-spanner/.devcontainer/devcontainer.json b/packages/google-cloud-spanner/.devcontainer/devcontainer.json index 7b0126cb8ab3..b259473b76b9 100644 --- a/packages/google-cloud-spanner/.devcontainer/devcontainer.json +++ b/packages/google-cloud-spanner/.devcontainer/devcontainer.json @@ -4,7 +4,7 @@ "name": "Python 3", "build": { // Sets the run context to one level up instead of the .devcontainer folder. - "args": { "VARIANT": "3.8" }, + "args": { "VARIANT": "3.10" }, // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. "dockerfile": "Dockerfile" }, diff --git a/packages/google-cloud-spanner/.devcontainer/requirements.txt b/packages/google-cloud-spanner/.devcontainer/requirements.txt index bb6f0e8ebd30..d8354b9a2bf6 100644 --- a/packages/google-cloud-spanner/.devcontainer/requirements.txt +++ b/packages/google-cloud-spanner/.devcontainer/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.8 # version-scanner: ignore # by the following command: # # pip-compile --generate-hashes requirements.in diff --git a/packages/google-cloud-spanner/docs/conf.py b/packages/google-cloud-spanner/docs/conf.py index 7af5ba74fea1..a6c2e6611eb7 100644 --- a/packages/google-cloud-spanner/docs/conf.py +++ b/packages/google-cloud-spanner/docs/conf.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -83,7 +83,7 @@ # General information about the project. project = "google-cloud-spanner" -copyright = "2025, Google, LLC" +copyright = "2026, Google, LLC" author = "Google APIs" # The version info for the project you're documenting, acts as replacement for diff --git a/packages/google-cloud-spanner/google/cloud/aio/_cross_sync/cross_sync.py b/packages/google-cloud-spanner/google/cloud/aio/_cross_sync/cross_sync.py index 7c66d82604e6..28305e2b6b16 100644 --- a/packages/google-cloud-spanner/google/cloud/aio/_cross_sync/cross_sync.py +++ b/packages/google-cloud-spanner/google/cloud/aio/_cross_sync/cross_sync.py @@ -42,7 +42,6 @@ async def async_func(self, arg: int) -> int: import concurrent.futures import inspect import queue -import sys import threading import time import typing @@ -269,7 +268,7 @@ def create_task( sync_executor: ThreadPoolExecutor to use for sync operations. Ignored in async version """ task: CrossSync.Task[T] = asyncio.create_task(fn(*fn_args, **fn_kwargs)) - if task_name and sys.version_info >= (3, 8): + if task_name: task.set_name(task_name) return task diff --git a/packages/google-cloud-spanner/google/cloud/spanner_admin_database_v1/__init__.py b/packages/google-cloud-spanner/google/cloud/spanner_admin_database_v1/__init__.py index 0254ea8bbfbe..470cf062a455 100644 --- a/packages/google-cloud-spanner/google/cloud/spanner_admin_database_v1/__init__.py +++ b/packages/google-cloud-spanner/google/cloud/spanner_admin_database_v1/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ __version__ = package_version.__version__ +from importlib import metadata from .services.database_admin import DatabaseAdminAsyncClient, DatabaseAdminClient from .types.backup import ( @@ -99,18 +100,81 @@ api_core.check_python_version("google.cloud.spanner_admin_database_v1") # type: ignore api_core.check_dependency_versions("google.cloud.spanner_admin_database_v1") # type: ignore else: # pragma: NO COVER - import warnings + # An older version of api_core is installed which does not define the + # functions above. We do equivalent checks manually. + try: + import warnings - _py_version_str = sys.version.split()[0] - # version-scanner: ignore-next-line - if sys.version_info < (3, 10): + _py_version_str = sys.version.split()[0] + _package_label = "google.cloud.spanner_admin_database_v1" + if sys.version_info < (3, 10): + warnings.warn( + "You are using a non-supported Python version " + + f"({_py_version_str}). Google will not post any further " + + f"updates to {_package_label} supporting this Python version. " + + "Please upgrade to the latest Python version, or at " + + f"least to Python 3.10, and then update {_package_label}.", + FutureWarning, + ) + + def parse_version_to_tuple(version_string: str): + """Safely converts a semantic version string to a comparable tuple of integers. + Example: "4.25.8" -> (4, 25, 8) + Ignores non-numeric parts and handles common version formats. + Args: + version_string: Version string in the format "x.y.z" or "x.y.z" + Returns: + Tuple of integers for the parsed version string. + """ + parts = [] + for part in version_string.split("."): + try: + parts.append(int(part)) + except ValueError: + # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here. + # This is a simplification compared to 'packaging.parse_version', but sufficient + # for comparing strictly numeric semantic versions. + break + return tuple(parts) + + def _get_version(dependency_name): + try: + version_string: str = metadata.version(dependency_name) + parsed_version = parse_version_to_tuple(version_string) + return (parsed_version, version_string) + except Exception: + # Catch exceptions from metadata.version() (e.g., PackageNotFoundError) + # or errors during parse_version_to_tuple + return (None, "--") + + _dependency_package = "google.protobuf" + _next_supported_version = "4.25.8" + _next_supported_version_tuple = (4, 25, 8) + _recommendation = " (we recommend 6.x)" + (_version_used, _version_used_string) = _get_version(_dependency_package) + if _version_used and _version_used < _next_supported_version_tuple: + warnings.warn( + f"Package {_package_label} depends on " + + f"{_dependency_package}, currently installed at version " + + f"{_version_used_string}. Future updates to " + + f"{_package_label} will require {_dependency_package} at " + + f"version {_next_supported_version} or higher{_recommendation}." + + " Please ensure " + + "that either (a) your Python environment doesn't pin the " + + f"version of {_dependency_package}, so that updates to " + + f"{_package_label} can require the higher version, or " + + "(b) you manually update your Python environment to use at " + + f"least version {_next_supported_version} of " + + f"{_dependency_package}.", + FutureWarning, + ) + except Exception: warnings.warn( - "You are using a non-supported Python version " - + f"({_py_version_str}). Google will not post any further " - + "updates to google.cloud.spanner_admin_database_v1 supporting this Python version. " - + "Please upgrade to the latest Python version, or at " - + "least to Python 3.10, and then update google.cloud.spanner_admin_database_v1.", - FutureWarning, + "Could not determine the version of Python " + + "currently being used. To continue receiving " + + "updates for {_package_label}, ensure you are " + + "using a supported version of Python; see " + + "https://devguide.python.org/versions/" ) __all__ = ( diff --git a/packages/google-cloud-spanner/google/cloud/spanner_admin_database_v1/services/database_admin/async_client.py b/packages/google-cloud-spanner/google/cloud/spanner_admin_database_v1/services/database_admin/async_client.py index 639e73a689f6..989428b9b164 100644 --- a/packages/google-cloud-spanner/google/cloud/spanner_admin_database_v1/services/database_admin/async_client.py +++ b/packages/google-cloud-spanner/google/cloud/spanner_admin_database_v1/services/database_admin/async_client.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -518,11 +518,11 @@ async def sample_create_database(): ) # Make the request - operation = client.create_database(request=request) + operation = await client.create_database(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) @@ -814,11 +814,11 @@ async def sample_update_database(): ) # Make the request - operation = client.update_database(request=request) + operation = await client.update_database(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) @@ -964,11 +964,11 @@ async def sample_update_database_ddl(): ) # Make the request - operation = client.update_database_ddl(request=request) + operation = await client.update_database_ddl(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) @@ -1778,11 +1778,11 @@ async def sample_create_backup(): ) # Make the request - operation = client.create_backup(request=request) + operation = await client.create_backup(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) @@ -1944,11 +1944,11 @@ async def sample_copy_backup(): ) # Make the request - operation = client.copy_backup(request=request) + operation = await client.copy_backup(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) @@ -2602,11 +2602,11 @@ async def sample_restore_database(): ) # Make the request - operation = client.restore_database(request=request) + operation = await client.restore_database(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) diff --git a/packages/google-cloud-spanner/google/cloud/spanner_admin_instance_v1/__init__.py b/packages/google-cloud-spanner/google/cloud/spanner_admin_instance_v1/__init__.py index 9e3c73a6220d..09d1033f7db1 100644 --- a/packages/google-cloud-spanner/google/cloud/spanner_admin_instance_v1/__init__.py +++ b/packages/google-cloud-spanner/google/cloud/spanner_admin_instance_v1/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ __version__ = package_version.__version__ +from importlib import metadata from .services.instance_admin import InstanceAdminAsyncClient, InstanceAdminClient from .types.common import FulfillmentPeriod, OperationProgress, ReplicaSelection @@ -71,18 +72,81 @@ api_core.check_python_version("google.cloud.spanner_admin_instance_v1") # type: ignore api_core.check_dependency_versions("google.cloud.spanner_admin_instance_v1") # type: ignore else: # pragma: NO COVER - import warnings + # An older version of api_core is installed which does not define the + # functions above. We do equivalent checks manually. + try: + import warnings - _py_version_str = sys.version.split()[0] - # version-scanner: ignore-next-line - if sys.version_info < (3, 10): + _py_version_str = sys.version.split()[0] + _package_label = "google.cloud.spanner_admin_instance_v1" + if sys.version_info < (3, 10): + warnings.warn( + "You are using a non-supported Python version " + + f"({_py_version_str}). Google will not post any further " + + f"updates to {_package_label} supporting this Python version. " + + "Please upgrade to the latest Python version, or at " + + f"least to Python 3.10, and then update {_package_label}.", + FutureWarning, + ) + + def parse_version_to_tuple(version_string: str): + """Safely converts a semantic version string to a comparable tuple of integers. + Example: "4.25.8" -> (4, 25, 8) + Ignores non-numeric parts and handles common version formats. + Args: + version_string: Version string in the format "x.y.z" or "x.y.z" + Returns: + Tuple of integers for the parsed version string. + """ + parts = [] + for part in version_string.split("."): + try: + parts.append(int(part)) + except ValueError: + # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here. + # This is a simplification compared to 'packaging.parse_version', but sufficient + # for comparing strictly numeric semantic versions. + break + return tuple(parts) + + def _get_version(dependency_name): + try: + version_string: str = metadata.version(dependency_name) + parsed_version = parse_version_to_tuple(version_string) + return (parsed_version, version_string) + except Exception: + # Catch exceptions from metadata.version() (e.g., PackageNotFoundError) + # or errors during parse_version_to_tuple + return (None, "--") + + _dependency_package = "google.protobuf" + _next_supported_version = "4.25.8" + _next_supported_version_tuple = (4, 25, 8) + _recommendation = " (we recommend 6.x)" + (_version_used, _version_used_string) = _get_version(_dependency_package) + if _version_used and _version_used < _next_supported_version_tuple: + warnings.warn( + f"Package {_package_label} depends on " + + f"{_dependency_package}, currently installed at version " + + f"{_version_used_string}. Future updates to " + + f"{_package_label} will require {_dependency_package} at " + + f"version {_next_supported_version} or higher{_recommendation}." + + " Please ensure " + + "that either (a) your Python environment doesn't pin the " + + f"version of {_dependency_package}, so that updates to " + + f"{_package_label} can require the higher version, or " + + "(b) you manually update your Python environment to use at " + + f"least version {_next_supported_version} of " + + f"{_dependency_package}.", + FutureWarning, + ) + except Exception: warnings.warn( - "You are using a non-supported Python version " - + f"({_py_version_str}). Google will not post any further " - + "updates to google.cloud.spanner_admin_instance_v1 supporting this Python version. " - + "Please upgrade to the latest Python version, or at " - + "least to Python 3.10, and then update google.cloud.spanner_admin_instance_v1.", - FutureWarning, + "Could not determine the version of Python " + + "currently being used. To continue receiving " + + "updates for {_package_label}, ensure you are " + + "using a supported version of Python; see " + + "https://devguide.python.org/versions/" ) __all__ = ( diff --git a/packages/google-cloud-spanner/google/cloud/spanner_admin_instance_v1/services/instance_admin/async_client.py b/packages/google-cloud-spanner/google/cloud/spanner_admin_instance_v1/services/instance_admin/async_client.py index 585cacbf4c5a..ced151c26542 100644 --- a/packages/google-cloud-spanner/google/cloud/spanner_admin_instance_v1/services/instance_admin/async_client.py +++ b/packages/google-cloud-spanner/google/cloud/spanner_admin_instance_v1/services/instance_admin/async_client.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -659,11 +659,11 @@ async def sample_create_instance_config(): ) # Make the request - operation = client.create_instance_config(request=request) + operation = await client.create_instance_config(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) @@ -859,11 +859,11 @@ async def sample_update_instance_config(): ) # Make the request - operation = client.update_instance_config(request=request) + operation = await client.update_instance_config(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) @@ -1684,11 +1684,11 @@ async def sample_create_instance(): ) # Make the request - operation = client.create_instance(request=request) + operation = await client.create_instance(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) @@ -1881,11 +1881,11 @@ async def sample_update_instance(): ) # Make the request - operation = client.update_instance(request=request) + operation = await client.update_instance(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) @@ -2716,11 +2716,11 @@ async def sample_create_instance_partition(): ) # Make the request - operation = client.create_instance_partition(request=request) + operation = await client.create_instance_partition(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) @@ -3030,11 +3030,11 @@ async def sample_update_instance_partition(): ) # Make the request - operation = client.update_instance_partition(request=request) + operation = await client.update_instance_partition(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) @@ -3387,11 +3387,11 @@ async def sample_move_instance(): ) # Make the request - operation = client.move_instance(request=request) + operation = await client.move_instance(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) diff --git a/packages/google-cloud-spanner/google/cloud/spanner_v1/_opentelemetry_tracing.py b/packages/google-cloud-spanner/google/cloud/spanner_v1/_opentelemetry_tracing.py index 8bda1daaed5a..372f40d02be9 100644 --- a/packages/google-cloud-spanner/google/cloud/spanner_v1/_opentelemetry_tracing.py +++ b/packages/google-cloud-spanner/google/cloud/spanner_v1/_opentelemetry_tracing.py @@ -19,7 +19,6 @@ from datetime import datetime from opentelemetry import trace - from opentelemetry.trace.status import Status, StatusCode from google.cloud.spanner_v1._helpers import ( diff --git a/packages/google-cloud-spanner/google/cloud/spanner_v1/client.py b/packages/google-cloud-spanner/google/cloud/spanner_v1/client.py index 132da6ca337c..62c2b24d93d5 100644 --- a/packages/google-cloud-spanner/google/cloud/spanner_v1/client.py +++ b/packages/google-cloud-spanner/google/cloud/spanner_v1/client.py @@ -32,11 +32,13 @@ import threading import warnings from typing import Optional + import google.api_core.client_options import grpc from google.api_core.gapic_v1 import client_info from google.auth.credentials import AnonymousCredentials from google.cloud.client import ClientWithProject + from google.cloud.spanner_admin_database_v1 import ( DatabaseAdminClient as DatabaseAdminClient, ) @@ -53,7 +55,6 @@ from google.cloud.spanner_admin_instance_v1.services.instance_admin.transports.grpc import ( InstanceAdminGrpcTransport, ) -from google.cloud.spanner_v1.instance import Instance from google.cloud.spanner_v1._helpers import ( AtomicCounter, _merge_query_options, @@ -61,6 +62,7 @@ _validate_client_context, ) from google.cloud.spanner_v1.gapic_version import __version__ +from google.cloud.spanner_v1.instance import Instance from google.cloud.spanner_v1.metrics.constants import METRIC_EXPORT_INTERVAL_MS from google.cloud.spanner_v1.metrics.metrics_exporter import ( CloudMonitoringMetricsExporter, diff --git a/packages/google-cloud-spanner/google/cloud/spanner_v1/metrics/metrics_interceptor.py b/packages/google-cloud-spanner/google/cloud/spanner_v1/metrics/metrics_interceptor.py index be3ebc178cf2..3e38c4e0191d 100644 --- a/packages/google-cloud-spanner/google/cloud/spanner_v1/metrics/metrics_interceptor.py +++ b/packages/google-cloud-spanner/google/cloud/spanner_v1/metrics/metrics_interceptor.py @@ -67,25 +67,6 @@ def _extract_resource_from_path(metadata: Dict[str, str]) -> Dict[str, str]: resources = MetricsInterceptor._parse_resource_path(path) return resources - @staticmethod - def _remove_prefix(s: str, prefix: str) -> str: - """ - This function removes the prefix from the given string. - - Args: - s (str): The string from which the prefix is to be removed. - prefix (str): The prefix to be removed from the string. - - Returns: - str: The string with the prefix removed. - - Note: - This function is used because the `removeprefix` method does not exist in Python 3.8. - """ - if s.startswith(prefix): - return s[len(prefix) :] - return s - def _set_metrics_tracer_attributes(self, resources: Dict[str, str]) -> None: """ Sets the metric tracer attributes based on the provided resources. @@ -134,9 +115,9 @@ def intercept(self, invoked_method, request_or_iterator, call_details): self._set_metrics_tracer_attributes(resources) ## Format method to be be spanner. - method_name = self._remove_prefix( - call_details.method, SPANNER_METHOD_PREFIX - ).replace("/", ".") + method_name = call_details.method.removeprefix(SPANNER_METHOD_PREFIX).replace( + "/", "." + ) tracer.set_method(method_name) tracer.record_attempt_start() diff --git a/packages/google-cloud-spanner/noxfile.py b/packages/google-cloud-spanner/noxfile.py index e1a13b2ea098..307c90c8c7f6 100644 --- a/packages/google-cloud-spanner/noxfile.py +++ b/packages/google-cloud-spanner/noxfile.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -218,8 +218,6 @@ def install_unittest_dependencies(session, *constraints): def unit(session, protobuf_implementation): # Install all test dependencies, then install this package in-place. - if session.python in ("3.7",): - session.skip("Python 3.7 is no longer supported") if protobuf_implementation == "cpp" and session.python in ( "3.11", "3.12", diff --git a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_copy_backup_async.py b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_copy_backup_async.py index 08dc74522edf..1ab0f8baff82 100644 --- a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_copy_backup_async.py +++ b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_copy_backup_async.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -46,11 +46,11 @@ async def sample_copy_backup(): ) # Make the request - operation = client.copy_backup(request=request) + operation = await client.copy_backup(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) diff --git a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_create_backup_async.py b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_create_backup_async.py index 8a9f0e7c5fc5..5abaae5b758a 100644 --- a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_create_backup_async.py +++ b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_create_backup_async.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -45,11 +45,11 @@ async def sample_create_backup(): ) # Make the request - operation = client.create_backup(request=request) + operation = await client.create_backup(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) diff --git a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_create_database_async.py b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_create_database_async.py index 5e9febb6336f..43d6eac3b549 100644 --- a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_create_database_async.py +++ b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_create_database_async.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -45,11 +45,11 @@ async def sample_create_database(): ) # Make the request - operation = client.create_database(request=request) + operation = await client.create_database(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) diff --git a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_restore_database_async.py b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_restore_database_async.py index 5c0b9ba3a858..0395cacdf195 100644 --- a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_restore_database_async.py +++ b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_restore_database_async.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -46,11 +46,11 @@ async def sample_restore_database(): ) # Make the request - operation = client.restore_database(request=request) + operation = await client.restore_database(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) diff --git a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_update_database_async.py b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_update_database_async.py index 857fe6aca8c9..e02a6f18941b 100644 --- a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_update_database_async.py +++ b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_update_database_async.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -47,11 +47,11 @@ async def sample_update_database(): ) # Make the request - operation = client.update_database(request=request) + operation = await client.update_database(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) diff --git a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_update_database_ddl_async.py b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_update_database_ddl_async.py index 7c4d5d2e6461..79bc38d0b6c4 100644 --- a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_update_database_ddl_async.py +++ b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_database_admin_update_database_ddl_async.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -45,11 +45,11 @@ async def sample_update_database_ddl(): ) # Make the request - operation = client.update_database_ddl(request=request) + operation = await client.update_database_ddl(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) diff --git a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_create_instance_async.py b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_create_instance_async.py index 428adf0f9ea9..94e637cf27dd 100644 --- a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_create_instance_async.py +++ b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_create_instance_async.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -51,11 +51,11 @@ async def sample_create_instance(): ) # Make the request - operation = client.create_instance(request=request) + operation = await client.create_instance(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) diff --git a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_create_instance_config_async.py b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_create_instance_config_async.py index 24c5858d502a..39e1c3727e57 100644 --- a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_create_instance_config_async.py +++ b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_create_instance_config_async.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -45,11 +45,11 @@ async def sample_create_instance_config(): ) # Make the request - operation = client.create_instance_config(request=request) + operation = await client.create_instance_config(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) diff --git a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_create_instance_partition_async.py b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_create_instance_partition_async.py index fc67a82472f1..d2b77ce251b7 100644 --- a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_create_instance_partition_async.py +++ b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_create_instance_partition_async.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -52,11 +52,11 @@ async def sample_create_instance_partition(): ) # Make the request - operation = client.create_instance_partition(request=request) + operation = await client.create_instance_partition(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) diff --git a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_move_instance_async.py b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_move_instance_async.py index 549cdbd5fc33..d251a2b82e55 100644 --- a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_move_instance_async.py +++ b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_move_instance_async.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -45,11 +45,11 @@ async def sample_move_instance(): ) # Make the request - operation = client.move_instance(request=request) + operation = await client.move_instance(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) diff --git a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_update_instance_async.py b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_update_instance_async.py index 9d1de61fc00d..6d07b4ac7279 100644 --- a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_update_instance_async.py +++ b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_update_instance_async.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -49,11 +49,11 @@ async def sample_update_instance(): ) # Make the request - operation = client.update_instance(request=request) + operation = await client.update_instance(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) diff --git a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_update_instance_config_async.py b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_update_instance_config_async.py index 54959a7b2d33..e07331cb401d 100644 --- a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_update_instance_config_async.py +++ b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_update_instance_config_async.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -42,11 +42,11 @@ async def sample_update_instance_config(): request = spanner_admin_instance_v1.UpdateInstanceConfigRequest() # Make the request - operation = client.update_instance_config(request=request) + operation = await client.update_instance_config(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) diff --git a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_update_instance_partition_async.py b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_update_instance_partition_async.py index 79a5084a9828..c9a4ae553a48 100644 --- a/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_update_instance_partition_async.py +++ b/packages/google-cloud-spanner/samples/generated_samples/spanner_v1_generated_instance_admin_update_instance_partition_async.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -50,11 +50,11 @@ async def sample_update_instance_partition(): ) # Make the request - operation = client.update_instance_partition(request=request) + operation = await client.update_instance_partition(request=request) print("Waiting for operation to complete...") - response = (await operation).result() + response = await operation.result() # Handle the response print(response) diff --git a/packages/google-cloud-spanner/setup.py b/packages/google-cloud-spanner/setup.py index 6f4123d0e7fd..34eace7a4506 100644 --- a/packages/google-cloud-spanner/setup.py +++ b/packages/google-cloud-spanner/setup.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -39,13 +39,19 @@ release_status = "Development Status :: 5 - Production/Stable" dependencies = [ - "google-api-core[grpc] >= 2.19.0, <3.0.0", + "google-api-core[grpc] >= 2.17.1, <3.0.0", + # Exclude incompatible versions of `google-auth` + # See https://github.com/googleapis/google-cloud-python/issues/12364 + "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", "google-cloud-core >= 2.0.0, < 3.0.0", + "grpcio >= 1.49.1, < 2.0.0", + "grpcio >= 1.75.1, < 2.0.0; python_version >= '3.14'", "grpc-google-iam-v1 >= 0.12.4, <1.0.0", "proto-plus >= 1.22.3, <2.0.0", - "sqlparse >= 0.4.4", + "proto-plus >= 1.25.0, <2.0.0; python_version >= '3.13'", "protobuf >= 4.25.8, < 8.0.0", "grpc-interceptor >= 0.15.4", + "sqlparse >= 0.4.4", # Make OpenTelemetry a core dependency "opentelemetry-api >= 1.22.0", "opentelemetry-sdk >= 1.22.0", @@ -77,7 +83,7 @@ long_description=readme, author="Google LLC", author_email="googleapis-packages@google.com", - license="Apache 2.0", + license="Apache-2.0", url=url, classifiers=[ release_status, diff --git a/packages/google-cloud-spanner/testing/constraints-3.10.txt b/packages/google-cloud-spanner/testing/constraints-3.10.txt index 5dfc1a2a325a..77670a5b9fc6 100644 --- a/packages/google-cloud-spanner/testing/constraints-3.10.txt +++ b/packages/google-cloud-spanner/testing/constraints-3.10.txt @@ -4,12 +4,15 @@ # pinning their versions to their lower bounds. # For example, if setup.py has "google-cloud-foo >= 1.14.0, < 2.0.0", # then this file should have google-cloud-foo==1.14.0 -google-api-core==2.19.0 +google-api-core==2.17.1 +google-auth==2.14.1 +grpcio==1.49.1 +grpcio-status==1.49.1 +proto-plus==1.22.3 +protobuf==4.25.8 google-cloud-core==2.0.0 grpc-google-iam-v1==0.12.4 -proto-plus==1.22.3 sqlparse==0.4.4 -protobuf==4.25.8 grpc-interceptor==0.15.4 opentelemetry-api==1.22.0 opentelemetry-sdk==1.22.0 @@ -18,3 +21,4 @@ opentelemetry-resourcedetector-gcp==1.8.0a0 google-cloud-monitoring==2.16.0 mmh3==4.1.0 libcst==0.2.5 +googleapis-common-protos==1.60.0 diff --git a/packages/google-cloud-spanner/tests/_helpers.py b/packages/google-cloud-spanner/tests/_helpers.py index f2f67e96cbea..2298d2d6b6fe 100644 --- a/packages/google-cloud-spanner/tests/_helpers.py +++ b/packages/google-cloud-spanner/tests/_helpers.py @@ -16,7 +16,6 @@ InMemorySpanExporter, ) from opentelemetry.sdk.trace.sampling import TraceIdRatioBased - from opentelemetry.trace.status import StatusCode trace.set_tracer_provider(TracerProvider(sampler=TraceIdRatioBased(1.0))) diff --git a/packages/google-cloud-spanner/tests/unit/gapic/spanner_admin_database_v1/test_database_admin.py b/packages/google-cloud-spanner/tests/unit/gapic/spanner_admin_database_v1/test_database_admin.py index ff908520c496..3ed4c7d208b3 100644 --- a/packages/google-cloud-spanner/tests/unit/gapic/spanner_admin_database_v1/test_database_admin.py +++ b/packages/google-cloud-spanner/tests/unit/gapic/spanner_admin_database_v1/test_database_admin.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,19 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. # -import os -import re - -# try/except added for compatibility with python < 3.8 -try: - from unittest import mock - from unittest.mock import AsyncMock # pragma: NO COVER -except ImportError: # pragma: NO COVER - import mock - import json import math +import os +import re from collections.abc import AsyncIterable, Iterable, Mapping, Sequence +from unittest import mock +from unittest.mock import AsyncMock import grpc import pytest @@ -11848,7 +11842,7 @@ def test_list_databases_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_list_databases_rest_unset_required_fields(): @@ -12102,7 +12096,7 @@ def test_create_database_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_create_database_rest_unset_required_fields(): @@ -12287,7 +12281,7 @@ def test_get_database_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_get_database_rest_unset_required_fields(): @@ -12465,7 +12459,7 @@ def test_update_database_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_update_database_rest_unset_required_fields(): @@ -12663,7 +12657,7 @@ def test_update_database_ddl_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_update_database_ddl_rest_unset_required_fields(): @@ -12848,7 +12842,7 @@ def test_drop_database_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_drop_database_rest_unset_required_fields(): @@ -13030,7 +13024,7 @@ def test_get_database_ddl_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_get_database_ddl_rest_unset_required_fields(): @@ -13209,7 +13203,7 @@ def test_set_iam_policy_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_set_iam_policy_rest_unset_required_fields(): @@ -13394,7 +13388,7 @@ def test_get_iam_policy_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_get_iam_policy_rest_unset_required_fields(): @@ -13579,7 +13573,7 @@ def test_test_iam_permissions_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_test_iam_permissions_rest_unset_required_fields(): @@ -13789,7 +13783,7 @@ def test_create_backup_rest_required_fields( ("$alt", "json;enum-encoding=int"), ] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_create_backup_rest_unset_required_fields(): @@ -13990,7 +13984,7 @@ def test_copy_backup_rest_required_fields(request_type=backup.CopyBackupRequest) expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_copy_backup_rest_unset_required_fields(): @@ -14180,7 +14174,7 @@ def test_get_backup_rest_required_fields(request_type=backup.GetBackupRequest): expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_get_backup_rest_unset_required_fields(): @@ -14355,7 +14349,7 @@ def test_update_backup_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_update_backup_rest_unset_required_fields(): @@ -14540,7 +14534,7 @@ def test_delete_backup_rest_required_fields(request_type=backup.DeleteBackupRequ expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_delete_backup_rest_unset_required_fields(): @@ -14721,7 +14715,7 @@ def test_list_backups_rest_required_fields(request_type=backup.ListBackupsReques expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_list_backups_rest_unset_required_fields(): @@ -14976,7 +14970,7 @@ def test_restore_database_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_restore_database_rest_unset_required_fields(): @@ -15178,7 +15172,7 @@ def test_list_database_operations_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_list_database_operations_rest_unset_required_fields(): @@ -15444,7 +15438,7 @@ def test_list_backup_operations_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_list_backup_operations_rest_unset_required_fields(): @@ -15707,7 +15701,7 @@ def test_list_database_roles_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_list_database_roles_rest_unset_required_fields(): @@ -15966,7 +15960,7 @@ def test_add_split_points_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_add_split_points_rest_unset_required_fields(): @@ -16177,7 +16171,7 @@ def test_create_backup_schedule_rest_required_fields( ("$alt", "json;enum-encoding=int"), ] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_create_backup_schedule_rest_unset_required_fields(): @@ -16374,7 +16368,7 @@ def test_get_backup_schedule_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_get_backup_schedule_rest_unset_required_fields(): @@ -16557,7 +16551,7 @@ def test_update_backup_schedule_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_update_backup_schedule_rest_unset_required_fields(): @@ -16751,7 +16745,7 @@ def test_delete_backup_schedule_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_delete_backup_schedule_rest_unset_required_fields(): @@ -16941,7 +16935,7 @@ def test_list_backup_schedules_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_list_backup_schedules_rest_unset_required_fields(): diff --git a/packages/google-cloud-spanner/tests/unit/gapic/spanner_admin_instance_v1/test_instance_admin.py b/packages/google-cloud-spanner/tests/unit/gapic/spanner_admin_instance_v1/test_instance_admin.py index 9c5c96e5d164..5900599958c8 100644 --- a/packages/google-cloud-spanner/tests/unit/gapic/spanner_admin_instance_v1/test_instance_admin.py +++ b/packages/google-cloud-spanner/tests/unit/gapic/spanner_admin_instance_v1/test_instance_admin.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,19 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. # -import os -import re - -# try/except added for compatibility with python < 3.8 -try: - from unittest import mock - from unittest.mock import AsyncMock # pragma: NO COVER -except ImportError: # pragma: NO COVER - import mock - import json import math +import os +import re from collections.abc import AsyncIterable, Iterable, Mapping, Sequence +from unittest import mock +from unittest.mock import AsyncMock import grpc import pytest @@ -9754,7 +9748,7 @@ def test_list_instance_configs_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_list_instance_configs_rest_unset_required_fields(): @@ -10011,7 +10005,7 @@ def test_get_instance_config_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_get_instance_config_rest_unset_required_fields(): @@ -10199,7 +10193,7 @@ def test_create_instance_config_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_create_instance_config_rest_unset_required_fields(): @@ -10389,7 +10383,7 @@ def test_update_instance_config_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_update_instance_config_rest_unset_required_fields(): @@ -10586,7 +10580,7 @@ def test_delete_instance_config_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_delete_instance_config_rest_unset_required_fields(): @@ -10786,7 +10780,7 @@ def test_list_instance_config_operations_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_list_instance_config_operations_rest_unset_required_fields(): @@ -11052,7 +11046,7 @@ def test_list_instances_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_list_instances_rest_unset_required_fields(): @@ -11316,7 +11310,7 @@ def test_list_instance_partitions_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_list_instance_partitions_rest_unset_required_fields(): @@ -11573,7 +11567,7 @@ def test_get_instance_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_get_instance_rest_unset_required_fields(): @@ -11755,7 +11749,7 @@ def test_create_instance_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_create_instance_rest_unset_required_fields(): @@ -11939,7 +11933,7 @@ def test_update_instance_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_update_instance_rest_unset_required_fields(): @@ -12121,7 +12115,7 @@ def test_delete_instance_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_delete_instance_rest_unset_required_fields(): @@ -12294,7 +12288,7 @@ def test_set_iam_policy_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_set_iam_policy_rest_unset_required_fields(): @@ -12477,7 +12471,7 @@ def test_get_iam_policy_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_get_iam_policy_rest_unset_required_fields(): @@ -12660,7 +12654,7 @@ def test_test_iam_permissions_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_test_iam_permissions_rest_unset_required_fields(): @@ -12851,7 +12845,7 @@ def test_get_instance_partition_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_get_instance_partition_rest_unset_required_fields(): @@ -13042,7 +13036,7 @@ def test_create_instance_partition_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_create_instance_partition_rest_unset_required_fields(): @@ -13239,7 +13233,7 @@ def test_delete_instance_partition_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_delete_instance_partition_rest_unset_required_fields(): @@ -13419,7 +13413,7 @@ def test_update_instance_partition_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_update_instance_partition_rest_unset_required_fields(): @@ -13631,7 +13625,7 @@ def test_list_instance_partition_operations_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_list_instance_partition_operations_rest_unset_required_fields(): @@ -13899,7 +13893,7 @@ def test_move_instance_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_move_instance_rest_unset_required_fields(): diff --git a/packages/google-cloud-spanner/tests/unit/gapic/spanner_v1/test_spanner.py b/packages/google-cloud-spanner/tests/unit/gapic/spanner_v1/test_spanner.py index 59945612e175..8e0fb7de7f62 100644 --- a/packages/google-cloud-spanner/tests/unit/gapic/spanner_v1/test_spanner.py +++ b/packages/google-cloud-spanner/tests/unit/gapic/spanner_v1/test_spanner.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,18 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. # -import os - -# try/except added for compatibility with python < 3.8 -try: - from unittest import mock - from unittest.mock import AsyncMock # pragma: NO COVER -except ImportError: # pragma: NO COVER - import mock - import json import math +import os from collections.abc import AsyncIterable, Iterable, Mapping, Sequence +from unittest import mock +from unittest.mock import AsyncMock import grpc import pytest @@ -6697,7 +6691,7 @@ def test_create_session_rest_required_fields(request_type=spanner.CreateSessionR expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_create_session_rest_unset_required_fields(): @@ -6895,7 +6889,7 @@ def test_batch_create_sessions_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_batch_create_sessions_rest_unset_required_fields(): @@ -7083,7 +7077,7 @@ def test_get_session_rest_required_fields(request_type=spanner.GetSessionRequest expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_get_session_rest_unset_required_fields(): @@ -7269,7 +7263,7 @@ def test_list_sessions_rest_required_fields(request_type=spanner.ListSessionsReq expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_list_sessions_rest_unset_required_fields(): @@ -7516,7 +7510,7 @@ def test_delete_session_rest_required_fields(request_type=spanner.DeleteSessionR expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_delete_session_rest_unset_required_fields(): @@ -7697,7 +7691,7 @@ def test_execute_sql_rest_required_fields(request_type=spanner.ExecuteSqlRequest expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_execute_sql_rest_unset_required_fields(): @@ -7838,7 +7832,7 @@ def test_execute_streaming_sql_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_execute_streaming_sql_rest_unset_required_fields(): @@ -7973,7 +7967,7 @@ def test_execute_batch_dml_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_execute_batch_dml_rest_unset_required_fields(): @@ -8110,7 +8104,7 @@ def test_read_rest_required_fields(request_type=spanner.ReadRequest): expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_read_rest_unset_required_fields(): @@ -8250,7 +8244,7 @@ def test_streaming_read_rest_required_fields(request_type=spanner.ReadRequest): expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_streaming_read_rest_unset_required_fields(): @@ -8383,7 +8377,7 @@ def test_begin_transaction_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_begin_transaction_rest_unset_required_fields(): @@ -8580,7 +8574,7 @@ def test_commit_rest_required_fields(request_type=spanner.CommitRequest): expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_commit_rest_unset_required_fields(): @@ -8772,7 +8766,7 @@ def test_rollback_rest_required_fields(request_type=spanner.RollbackRequest): expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_rollback_rest_unset_required_fields(): @@ -8965,7 +8959,7 @@ def test_partition_query_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_partition_query_rest_unset_required_fields(): @@ -9096,7 +9090,7 @@ def test_partition_read_rest_required_fields(request_type=spanner.PartitionReadR expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_partition_read_rest_unset_required_fields(): @@ -9227,7 +9221,7 @@ def test_batch_write_rest_required_fields(request_type=spanner.BatchWriteRequest expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_batch_write_rest_unset_required_fields(): @@ -9444,7 +9438,7 @@ def test_fetch_cache_update_rest_required_fields( expected_params = [("$alt", "json;enum-encoding=int")] actual_params = req.call_args.kwargs["params"] - assert expected_params == actual_params + assert sorted(expected_params) == sorted(actual_params) def test_fetch_cache_update_rest_unset_required_fields():