diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 698fbc5c9..13d359b6e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,6 +17,9 @@ jobs: run: | python -m pip install --upgrade setuptools pip wheel python -m pip install nox + - name: Install pytest + run: | + python -m pip install pytest - name: Run docs run: | nox -s docs @@ -33,6 +36,9 @@ jobs: run: | python -m pip install --upgrade setuptools pip wheel python -m pip install nox + - name: Install pytest + run: | + python -m pip install pytest - name: Run docfx run: | nox -s docfx diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1051da0bd..29499a8b9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,6 +17,9 @@ jobs: run: | python -m pip install --upgrade setuptools pip wheel python -m pip install nox + - name: Install pytest + run: | + python -m pip install pytest - name: Run lint run: | nox -s lint diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index e6a79291d..85f2db3ee 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -17,6 +17,9 @@ jobs: run: | python -m pip install --upgrade setuptools pip wheel python -m pip install nox + - name: Install pytest + run: | + python -m pip install pytest - name: Run mypy run: | nox -s mypy diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index 2cfaada36..20ccd9c36 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - option: ["", "_grpc_gcp", "_wo_grpc"] + option: ["", "_grpc_gcp", "_wo_grpc", "_prerelease"] python: - "3.7" - "3.8" @@ -26,6 +26,16 @@ jobs: python: 3.8 - option: "_wo_grpc" python: 3.9 + - option: "_prerelease" + python: 3.7 + - option: "_prerelease" + python: 3.8 + - option: "_prerelease" + python: 3.9 + - option: "_prerelease" + python: 3.10 + - option: "_prerelease" + python: 3.11 steps: - name: Checkout uses: actions/checkout@v4 @@ -37,6 +47,9 @@ jobs: run: | python -m pip install --upgrade setuptools pip wheel python -m pip install nox + - name: Install pytest + run: | + python -m pip install pytest - name: Run unit tests env: COVERAGE_FILE: .coverage${{ matrix.option }}-${{matrix.python }} diff --git a/google/api_core/operations_v1/transports/rest.py b/google/api_core/operations_v1/transports/rest.py index 49f99d210..038704657 100644 --- a/google/api_core/operations_v1/transports/rest.py +++ b/google/api_core/operations_v1/transports/rest.py @@ -29,9 +29,13 @@ from google.longrunning import operations_pb2 # type: ignore from google.protobuf import empty_pb2 # type: ignore from google.protobuf import json_format # type: ignore +import google.protobuf + import grpc from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO, OperationsTransport +PROTOBUF_VERSION = google.protobuf.__version__ + OptionalRetry = Union[retries.Retry, object] DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( @@ -184,11 +188,22 @@ def _list_operations( "google.longrunning.Operations.ListOperations" ] - request_kwargs = json_format.MessageToDict( - request, - preserving_proto_field_name=True, - including_default_value_fields=True, - ) + # For backwards compatibility with protobuf 3.x 4.x + # Remove once support for protobuf 3.x and 4.x is dropped + # https://github.com/googleapis/python-api-core/issues/643 + if PROTOBUF_VERSION[0:2] in ["3.", "4."]: + request_kwargs = json_format.MessageToDict( + request, + preserving_proto_field_name=True, + including_default_value_fields=True, # type: ignore # backward compatibility + ) + else: + request_kwargs = json_format.MessageToDict( + request, + preserving_proto_field_name=True, + always_print_fields_with_no_presence=True, + ) + transcoded_request = path_template.transcode(http_options, **request_kwargs) uri = transcoded_request["uri"] @@ -199,7 +214,6 @@ def _list_operations( json_format.ParseDict(transcoded_request["query_params"], query_params_request) query_params = json_format.MessageToDict( query_params_request, - including_default_value_fields=False, preserving_proto_field_name=False, use_integers_for_enums=False, ) @@ -265,11 +279,22 @@ def _get_operation( "google.longrunning.Operations.GetOperation" ] - request_kwargs = json_format.MessageToDict( - request, - preserving_proto_field_name=True, - including_default_value_fields=True, - ) + # For backwards compatibility with protobuf 3.x 4.x + # Remove once support for protobuf 3.x and 4.x is dropped + # https://github.com/googleapis/python-api-core/issues/643 + if PROTOBUF_VERSION[0:2] in ["3.", "4."]: + request_kwargs = json_format.MessageToDict( + request, + preserving_proto_field_name=True, + including_default_value_fields=True, # type: ignore # backward compatibility + ) + else: + request_kwargs = json_format.MessageToDict( + request, + preserving_proto_field_name=True, + always_print_fields_with_no_presence=True, + ) + transcoded_request = path_template.transcode(http_options, **request_kwargs) uri = transcoded_request["uri"] @@ -280,7 +305,6 @@ def _get_operation( json_format.ParseDict(transcoded_request["query_params"], query_params_request) query_params = json_format.MessageToDict( query_params_request, - including_default_value_fields=False, preserving_proto_field_name=False, use_integers_for_enums=False, ) @@ -339,11 +363,21 @@ def _delete_operation( "google.longrunning.Operations.DeleteOperation" ] - request_kwargs = json_format.MessageToDict( - request, - preserving_proto_field_name=True, - including_default_value_fields=True, - ) + # For backwards compatibility with protobuf 3.x 4.x + # Remove once support for protobuf 3.x and 4.x is dropped + # https://github.com/googleapis/python-api-core/issues/643 + if PROTOBUF_VERSION[0:2] in ["3.", "4."]: + request_kwargs = json_format.MessageToDict( + request, + preserving_proto_field_name=True, + including_default_value_fields=True, # type: ignore # backward compatibility + ) + else: + request_kwargs = json_format.MessageToDict( + request, + preserving_proto_field_name=True, + always_print_fields_with_no_presence=True, + ) transcoded_request = path_template.transcode(http_options, **request_kwargs) uri = transcoded_request["uri"] @@ -354,7 +388,6 @@ def _delete_operation( json_format.ParseDict(transcoded_request["query_params"], query_params_request) query_params = json_format.MessageToDict( query_params_request, - including_default_value_fields=False, preserving_proto_field_name=False, use_integers_for_enums=False, ) @@ -411,11 +444,21 @@ def _cancel_operation( "google.longrunning.Operations.CancelOperation" ] - request_kwargs = json_format.MessageToDict( - request, - preserving_proto_field_name=True, - including_default_value_fields=True, - ) + # For backwards compatibility with protobuf 3.x 4.x + # Remove once support for protobuf 3.x and 4.x is dropped + # https://github.com/googleapis/python-api-core/issues/643 + if PROTOBUF_VERSION[0:2] in ["3.", "4."]: + request_kwargs = json_format.MessageToDict( + request, + preserving_proto_field_name=True, + including_default_value_fields=True, # type: ignore # backward compatibility + ) + else: + request_kwargs = json_format.MessageToDict( + request, + preserving_proto_field_name=True, + always_print_fields_with_no_presence=True, + ) transcoded_request = path_template.transcode(http_options, **request_kwargs) # Jsonify the request body @@ -423,7 +466,6 @@ def _cancel_operation( json_format.ParseDict(transcoded_request["body"], body_request) body = json_format.MessageToDict( body_request, - including_default_value_fields=False, preserving_proto_field_name=False, use_integers_for_enums=False, ) @@ -435,7 +477,6 @@ def _cancel_operation( json_format.ParseDict(transcoded_request["query_params"], query_params_request) query_params = json_format.MessageToDict( query_params_request, - including_default_value_fields=False, preserving_proto_field_name=False, use_integers_for_enums=False, ) diff --git a/noxfile.py b/noxfile.py index 2c7ec6c72..da4c34ce3 100644 --- a/noxfile.py +++ b/noxfile.py @@ -15,6 +15,8 @@ from __future__ import absolute_import import os import pathlib +import pytest +import re import shutil # https://github.com/google/importlab/issues/25 @@ -26,6 +28,8 @@ # Black and flake8 clash on the syntax for ignoring flake8's F401 in this file. BLACK_EXCLUDES = ["--exclude", "^/google/api_core/operations_v1/__init__.py"] +PYTHON_VERSIONS = ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + DEFAULT_PYTHON_VERSION = "3.10" CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() @@ -72,7 +76,37 @@ def blacken(session): session.run("black", *BLACK_EXCLUDES, *BLACK_PATHS) -def default(session, install_grpc=True): +def install_prerelease_dependencies(session, constraints_path): + with open(constraints_path, encoding="utf-8") as constraints_file: + constraints_text = constraints_file.read() + # Ignore leading whitespace and comment lines. + constraints_deps = [ + match.group(1) + for match in re.finditer( + r"^\s*(\S+)(?===\S+)", constraints_text, flags=re.MULTILINE + ) + ] + session.install(*constraints_deps) + prerel_deps = [ + "google-auth", + "googleapis-common-protos", + "grpcio", + "grpcio-status", + "proto-plus", + "protobuf", + ] + + for dep in prerel_deps: + session.install("--pre", "--no-deps", "--upgrade", dep) + + # Remaining dependencies + other_deps = [ + "requests", + ] + session.install(*other_deps) + + +def default(session, install_grpc=True, prerelease=False): """Default unit test session. This is intended to be run **without** an interpreter set, so @@ -80,9 +114,8 @@ def default(session, install_grpc=True): Python corresponding to the ``nox`` binary the ``PATH`` can run the tests. """ - constraints_path = str( - CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" - ) + if prerelease and not install_grpc: + pytest.skip("The pre-release session cannot be run without grpc") session.install( "dataclasses", @@ -92,10 +125,37 @@ def default(session, install_grpc=True): "pytest-xdist", ) - if install_grpc: - session.install("-e", ".[grpc]", "-c", constraints_path) + constraints_dir = str(CURRENT_DIRECTORY / "testing") + + if prerelease: + install_prerelease_dependencies( + session, f"{constraints_dir}/constraints-{PYTHON_VERSIONS[0]}.txt" + ) + # This *must* be the last install command to get the package from source. + session.install("-e", ".", "--no-deps") else: - session.install("-e", ".", "-c", constraints_path) + if install_grpc: + session.install( + "-e", + ".[grpc]", + "-c", + f"{constraints_dir}/constraints-{session.python}.txt", + ) + else: + session.install( + "-e", ".", "-c", f"{constraints_dir}/constraints-{session.python}.txt" + ) + + # Print out package versions of dependencies + session.run( + "python", "-c", "import google.protobuf; print(google.protobuf.__version__)" + ) + # Support for proto.version was added in v1.23.0 + # https://github.com/googleapis/proto-plus-python/releases/tag/v1.23.0 + session.run("python", "-c", """import proto; hasattr(proto, "version") and print(proto.version.__version__)""") + if install_grpc: + session.run("python", "-c", "import grpc; print(grpc.__version__)") + session.run("python", "-c", "import google.auth; print(google.auth.__version__)") pytest_args = [ "python", @@ -130,15 +190,26 @@ def default(session, install_grpc=True): session.run(*pytest_args) -@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]) +@nox.session(python=PYTHON_VERSIONS) def unit(session): """Run the unit test suite.""" default(session) -@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]) +@nox.session(python=PYTHON_VERSIONS[-1]) +def unit_prerelease(session): + """Run the unit test suite.""" + default(session, prerelease=True) + + +@nox.session(python=PYTHON_VERSIONS) def unit_grpc_gcp(session): - """Run the unit test suite with grpcio-gcp installed.""" + """ + Run the unit test suite with grpcio-gcp installed. + `grpcio-gcp` doesn't support protobuf 4+. + Remove extra `grpcgcp` when protobuf 3.x is dropped. + https://github.com/googleapis/python-api-core/issues/594 + """ constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) @@ -150,7 +221,7 @@ def unit_grpc_gcp(session): default(session) -@nox.session(python=["3.8", "3.10", "3.11", "3.12"]) +@nox.session(python=PYTHON_VERSIONS) def unit_wo_grpc(session): """Run the unit test suite w/o grpcio installed""" default(session, install_grpc=False) @@ -164,10 +235,10 @@ def lint_setup_py(session): session.run("python", "setup.py", "check", "--restructuredtext", "--strict") -@nox.session(python="3.8") +@nox.session(python=DEFAULT_PYTHON_VERSION) def pytype(session): """Run type-checking.""" - session.install(".[grpc]", "pytype >= 2019.3.21") + session.install(".[grpc]", "pytype") session.run("pytype") @@ -175,12 +246,10 @@ def pytype(session): def mypy(session): """Run type-checking.""" session.install(".[grpc]", "mypy") - # Exclude types-protobuf==4.24.0.20240106 - # See https://github.com/python/typeshed/issues/11254 session.install( "types-setuptools", "types-requests", - "types-protobuf!=4.24.0.20240106", + "types-protobuf", "types-mock", "types-dataclasses", ) diff --git a/setup.py b/setup.py index 47e0b454c..a9e01f49c 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ release_status = "Development Status :: 5 - Production/Stable" dependencies = [ "googleapis-common-protos >= 1.56.2, < 2.0.dev0", - "protobuf>=3.19.5,<5.0.0.dev0,!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5", + "protobuf>=3.19.5,<6.0.0.dev0,!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5", "proto-plus >= 1.22.3, <2.0.0dev", "google-auth >= 2.14.1, < 3.0.dev0", "requests >= 2.18.0, < 3.0.0.dev0",