From 18ad4173125bfb8f8a159a4918b0629ce2f543ad Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Wed, 10 Mar 2021 14:35:17 -0800 Subject: [PATCH 1/8] chore: re-generated to pick up changes from self (#97) * changes without context autosynth cannot find the source of changes triggered by earlier changes in this repository, or by version upgrades to tools such as linters. * ignore some proposed changes from autosynth * remove docs/multiprocessing * remove unwanted modules from docs * ask synth not to generate certain docs Co-authored-by: Tianzi Cai --- .gitignore | 1 - google/cloud/pubsublite_v1/__init__.py | 4 +- noxfile.py | 4 +- synth.metadata | 134 ++++++++++++++++++++++++- synth.py | 7 +- 5 files changed, 140 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index efad5203..b9daa52f 100644 --- a/.gitignore +++ b/.gitignore @@ -50,7 +50,6 @@ docs.metadata # Virtual environment env/ -venv/ coverage.xml sponge_log.xml diff --git a/google/cloud/pubsublite_v1/__init__.py b/google/cloud/pubsublite_v1/__init__.py index 40a270af..3d423c80 100644 --- a/google/cloud/pubsublite_v1/__init__.py +++ b/google/cloud/pubsublite_v1/__init__.py @@ -77,7 +77,6 @@ __all__ = ( - "AdminServiceClient", "AttributeValues", "CommitCursorRequest", "CommitCursorResponse", @@ -114,6 +113,7 @@ "PartitionAssignment", "PartitionAssignmentAck", "PartitionAssignmentRequest", + "PartitionAssignmentServiceClient", "PartitionCursor", "PubSubMessage", "PublishRequest", @@ -135,5 +135,5 @@ "TopicStatsServiceClient", "UpdateSubscriptionRequest", "UpdateTopicRequest", - "PartitionAssignmentServiceClient", + "AdminServiceClient", ) diff --git a/noxfile.py b/noxfile.py index 776269dd..a2791746 100644 --- a/noxfile.py +++ b/noxfile.py @@ -26,7 +26,7 @@ BLACK_VERSION = "black==19.10b0" BLACK_PATHS = ["docs", "google", "tests", "noxfile.py", "setup.py"] -DEFAULT_PYTHON_VERSION = "3.6" +DEFAULT_PYTHON_VERSION = "3.8" SYSTEM_TEST_PYTHON_VERSIONS = ["3.8"] UNIT_TEST_PYTHON_VERSIONS = ["3.6", "3.7", "3.8", "3.9"] @@ -45,7 +45,7 @@ def lint(session): session.run("flake8", "google", "tests") -@nox.session(python=DEFAULT_PYTHON_VERSION) +@nox.session(python="3.6") def blacken(session): """Run black. diff --git a/synth.metadata b/synth.metadata index e4207efc..576e4390 100644 --- a/synth.metadata +++ b/synth.metadata @@ -3,8 +3,8 @@ { "git": { "name": ".", - "remote": "git@github.com:googleapis/python-pubsublite.git", - "sha": "78345176c1eaf07cd143cee941c665b3c99dbc6c" + "remote": "https://github.com/googleapis/python-pubsublite.git", + "sha": "7861ffea044dce4dc142dfd52d39dd64b9e79d65" } }, { @@ -40,5 +40,135 @@ "generator": "bazel" } } + ], + "generatedFiles": [ + ".flake8", + ".github/CONTRIBUTING.md", + ".github/ISSUE_TEMPLATE/bug_report.md", + ".github/ISSUE_TEMPLATE/feature_request.md", + ".github/ISSUE_TEMPLATE/support_request.md", + ".github/PULL_REQUEST_TEMPLATE.md", + ".github/release-please.yml", + ".github/snippet-bot.yml", + ".gitignore", + ".kokoro/build.sh", + ".kokoro/continuous/common.cfg", + ".kokoro/continuous/continuous.cfg", + ".kokoro/docker/docs/Dockerfile", + ".kokoro/docker/docs/fetch_gpg_keys.sh", + ".kokoro/docs/common.cfg", + ".kokoro/docs/docs-presubmit.cfg", + ".kokoro/docs/docs.cfg", + ".kokoro/populate-secrets.sh", + ".kokoro/presubmit/common.cfg", + ".kokoro/presubmit/presubmit.cfg", + ".kokoro/publish-docs.sh", + ".kokoro/release.sh", + ".kokoro/release/common.cfg", + ".kokoro/release/release.cfg", + ".kokoro/samples/lint/common.cfg", + ".kokoro/samples/lint/continuous.cfg", + ".kokoro/samples/lint/periodic.cfg", + ".kokoro/samples/lint/presubmit.cfg", + ".kokoro/samples/python3.6/common.cfg", + ".kokoro/samples/python3.6/continuous.cfg", + ".kokoro/samples/python3.6/periodic.cfg", + ".kokoro/samples/python3.6/presubmit.cfg", + ".kokoro/samples/python3.7/common.cfg", + ".kokoro/samples/python3.7/continuous.cfg", + ".kokoro/samples/python3.7/periodic.cfg", + ".kokoro/samples/python3.7/presubmit.cfg", + ".kokoro/samples/python3.8/common.cfg", + ".kokoro/samples/python3.8/continuous.cfg", + ".kokoro/samples/python3.8/periodic.cfg", + ".kokoro/samples/python3.8/presubmit.cfg", + ".kokoro/test-samples.sh", + ".kokoro/trampoline.sh", + ".kokoro/trampoline_v2.sh", + ".trampolinerc", + "CODE_OF_CONDUCT.md", + "CONTRIBUTING.rst", + "LICENSE", + "MANIFEST.in", + "docs/_static/custom.css", + "docs/_templates/layout.html", + "docs/conf.py", + "docs/multiprocessing.rst", + "docs/pubsublite_v1/services.rst", + "docs/pubsublite_v1/types.rst", + "google/cloud/pubsublite/py.typed", + "google/cloud/pubsublite_v1/__init__.py", + "google/cloud/pubsublite_v1/py.typed", + "google/cloud/pubsublite_v1/services/__init__.py", + "google/cloud/pubsublite_v1/services/admin_service/__init__.py", + "google/cloud/pubsublite_v1/services/admin_service/async_client.py", + "google/cloud/pubsublite_v1/services/admin_service/client.py", + "google/cloud/pubsublite_v1/services/admin_service/pagers.py", + "google/cloud/pubsublite_v1/services/admin_service/transports/__init__.py", + "google/cloud/pubsublite_v1/services/admin_service/transports/base.py", + "google/cloud/pubsublite_v1/services/admin_service/transports/grpc.py", + "google/cloud/pubsublite_v1/services/admin_service/transports/grpc_asyncio.py", + "google/cloud/pubsublite_v1/services/cursor_service/__init__.py", + "google/cloud/pubsublite_v1/services/cursor_service/async_client.py", + "google/cloud/pubsublite_v1/services/cursor_service/client.py", + "google/cloud/pubsublite_v1/services/cursor_service/pagers.py", + "google/cloud/pubsublite_v1/services/cursor_service/transports/__init__.py", + "google/cloud/pubsublite_v1/services/cursor_service/transports/base.py", + "google/cloud/pubsublite_v1/services/cursor_service/transports/grpc.py", + "google/cloud/pubsublite_v1/services/cursor_service/transports/grpc_asyncio.py", + "google/cloud/pubsublite_v1/services/partition_assignment_service/__init__.py", + "google/cloud/pubsublite_v1/services/partition_assignment_service/async_client.py", + "google/cloud/pubsublite_v1/services/partition_assignment_service/client.py", + "google/cloud/pubsublite_v1/services/partition_assignment_service/transports/__init__.py", + "google/cloud/pubsublite_v1/services/partition_assignment_service/transports/base.py", + "google/cloud/pubsublite_v1/services/partition_assignment_service/transports/grpc.py", + "google/cloud/pubsublite_v1/services/partition_assignment_service/transports/grpc_asyncio.py", + "google/cloud/pubsublite_v1/services/publisher_service/__init__.py", + "google/cloud/pubsublite_v1/services/publisher_service/async_client.py", + "google/cloud/pubsublite_v1/services/publisher_service/client.py", + "google/cloud/pubsublite_v1/services/publisher_service/transports/__init__.py", + "google/cloud/pubsublite_v1/services/publisher_service/transports/base.py", + "google/cloud/pubsublite_v1/services/publisher_service/transports/grpc.py", + "google/cloud/pubsublite_v1/services/publisher_service/transports/grpc_asyncio.py", + "google/cloud/pubsublite_v1/services/subscriber_service/__init__.py", + "google/cloud/pubsublite_v1/services/subscriber_service/async_client.py", + "google/cloud/pubsublite_v1/services/subscriber_service/client.py", + "google/cloud/pubsublite_v1/services/subscriber_service/transports/__init__.py", + "google/cloud/pubsublite_v1/services/subscriber_service/transports/base.py", + "google/cloud/pubsublite_v1/services/subscriber_service/transports/grpc.py", + "google/cloud/pubsublite_v1/services/subscriber_service/transports/grpc_asyncio.py", + "google/cloud/pubsublite_v1/services/topic_stats_service/__init__.py", + "google/cloud/pubsublite_v1/services/topic_stats_service/async_client.py", + "google/cloud/pubsublite_v1/services/topic_stats_service/client.py", + "google/cloud/pubsublite_v1/services/topic_stats_service/transports/__init__.py", + "google/cloud/pubsublite_v1/services/topic_stats_service/transports/base.py", + "google/cloud/pubsublite_v1/services/topic_stats_service/transports/grpc.py", + "google/cloud/pubsublite_v1/services/topic_stats_service/transports/grpc_asyncio.py", + "google/cloud/pubsublite_v1/types/__init__.py", + "google/cloud/pubsublite_v1/types/admin.py", + "google/cloud/pubsublite_v1/types/common.py", + "google/cloud/pubsublite_v1/types/cursor.py", + "google/cloud/pubsublite_v1/types/publisher.py", + "google/cloud/pubsublite_v1/types/subscriber.py", + "google/cloud/pubsublite_v1/types/topic_stats.py", + "mypy.ini", + "noxfile.py", + "renovate.json", + "scripts/decrypt-secrets.sh", + "scripts/readme-gen/readme_gen.py", + "scripts/readme-gen/templates/README.tmpl.rst", + "scripts/readme-gen/templates/auth.tmpl.rst", + "scripts/readme-gen/templates/auth_api_key.tmpl.rst", + "scripts/readme-gen/templates/install_deps.tmpl.rst", + "scripts/readme-gen/templates/install_portaudio.tmpl.rst", + "setup.cfg", + "testing/.gitignore", + "tests/unit/gapic/pubsublite_v1/__init__.py", + "tests/unit/gapic/pubsublite_v1/test_admin_service.py", + "tests/unit/gapic/pubsublite_v1/test_cursor_service.py", + "tests/unit/gapic/pubsublite_v1/test_partition_assignment_service.py", + "tests/unit/gapic/pubsublite_v1/test_publisher_service.py", + "tests/unit/gapic/pubsublite_v1/test_subscriber_service.py", + "tests/unit/gapic/pubsublite_v1/test_topic_stats_service.py" ] } \ No newline at end of file diff --git a/synth.py b/synth.py index 367d403c..ae6ef06a 100644 --- a/synth.py +++ b/synth.py @@ -32,11 +32,12 @@ ) excludes = [ + "docs/**/*", + "docs/index.rst", + "google/cloud/pubsublite/__init__.py", + "README.rst", "scripts/fixup*.py", # new libraries do not need the keyword fixup script "setup.py", - "README.rst", - "docs/index.rst", - "google/cloud/pubsublite/__init__.py" ] s.move(library, excludes=excludes) From b7757cc0c5fdec32bdcaeafa4a30f5284851f26c Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Thu, 11 Mar 2021 00:33:32 +0100 Subject: [PATCH 2/8] chore(deps): update dependency google-cloud-pubsublite to v0.3.0 (#99) --- samples/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index b42844d6..77fd6823 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1 +1 @@ -google-cloud-pubsublite==0.2.0 \ No newline at end of file +google-cloud-pubsublite==0.3.0 \ No newline at end of file From f913ed00453d3fe924e75caccd2486b24e12a6ae Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 10 Mar 2021 16:46:18 -0700 Subject: [PATCH 3/8] chore: exclude some docs/ files from synth copy (#101) --- synth.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/synth.py b/synth.py index ae6ef06a..9eb6b85a 100644 --- a/synth.py +++ b/synth.py @@ -32,7 +32,7 @@ ) excludes = [ - "docs/**/*", + "docs/pubsublite_v1", # generated GAPIC docs should be ignored "docs/index.rst", "google/cloud/pubsublite/__init__.py", "README.rst", @@ -47,15 +47,17 @@ templated_files = common.py_library( cov_level=70, microgenerator=True, - system_test_external_dependencies = ['asynctest'], - unit_test_external_dependencies = ['asynctest'], + system_test_external_dependencies=["asynctest"], + unit_test_external_dependencies=["asynctest"], ) s.move( - templated_files, excludes=[".coveragerc"] -) # the microgenerator has a good coveragerc file + templated_files, + excludes=[ + ".coveragerc", # the microgenerator has a good coveragerc file + "docs/multiprocessing.rst", # exclude multiprocessing note + ] +) s.shell.run(["nox", "-s", "blacken"], hide_output=False) - - From 129e70ffb82d78ae03c57623e6b220bddd82cf28 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Thu, 11 Mar 2021 07:53:52 -0800 Subject: [PATCH 4/8] [CHANGE ME] Re-generated to pick up changes from synthtool. (#100) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * changes without context autosynth cannot find the source of changes triggered by earlier changes in this repository, or by version upgrades to tools such as linters. * test(python): fix template defaults for noxfile default py versions * test(python): fix template defaults for noxfile default py versions * fix: firestore builds were having issue with 3.9 missing, default to 3.8 * test: update test to match new default * test: for now use 3.8, as we may not have 3.9 images for all CI yet Source-Author: Christopher Wilcox Source-Date: Thu Nov 12 17:27:23 2020 -0800 Source-Repo: googleapis/synthtool Source-Sha: d5fc0bcf9ea9789c5b0e3154a9e3b29e5cea6116 Source-Link: https://github.com/googleapis/synthtool/commit/d5fc0bcf9ea9789c5b0e3154a9e3b29e5cea6116 * docs(python): update intersphinx for grpc and auth * docs(python): update intersphinx for grpc and auth * use https for python intersphinx Co-authored-by: Tim Swast Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Wed Nov 18 14:37:25 2020 -0700 Source-Repo: googleapis/synthtool Source-Sha: 9a7d9fbb7045c34c9d3d22c1ff766eeae51f04c9 Source-Link: https://github.com/googleapis/synthtool/commit/9a7d9fbb7045c34c9d3d22c1ff766eeae51f04c9 * docs(python): fix intersphinx link for google-auth Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Thu Nov 19 10:16:05 2020 -0700 Source-Repo: googleapis/synthtool Source-Sha: a073c873f3928c561bdf87fdfbf1d081d1998984 Source-Link: https://github.com/googleapis/synthtool/commit/a073c873f3928c561bdf87fdfbf1d081d1998984 * chore: add config / docs for 'pre-commit' support Source-Author: Tres Seaver Source-Date: Tue Dec 1 16:01:20 2020 -0500 Source-Repo: googleapis/synthtool Source-Sha: 32af6da519a6b042e3da62008e2a75e991efb6b4 Source-Link: https://github.com/googleapis/synthtool/commit/32af6da519a6b042e3da62008e2a75e991efb6b4 * chore(deps): update precommit hook pre-commit/pre-commit-hooks to v3.3.0 Source-Author: WhiteSource Renovate Source-Date: Wed Dec 2 17:18:24 2020 +0100 Source-Repo: googleapis/synthtool Source-Sha: 69629b64b83c6421d616be2b8e11795738ec8a6c Source-Link: https://github.com/googleapis/synthtool/commit/69629b64b83c6421d616be2b8e11795738ec8a6c * test(python): give filesystem paths to pytest-cov https://pytest-cov.readthedocs.io/en/latest/config.html The pytest-cov docs seem to suggest a filesystem path is expected. Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Wed Dec 2 09:28:04 2020 -0700 Source-Repo: googleapis/synthtool Source-Sha: f94318521f63085b9ccb43d42af89f153fb39f15 Source-Link: https://github.com/googleapis/synthtool/commit/f94318521f63085b9ccb43d42af89f153fb39f15 * chore(deps): update precommit hook pre-commit/pre-commit-hooks to v3.4.0 Co-authored-by: Tres Seaver Source-Author: WhiteSource Renovate Source-Date: Wed Dec 16 18:13:24 2020 +0100 Source-Repo: googleapis/synthtool Source-Sha: aa255b15d52b6d8950cca48cfdf58f7d27a60c8a Source-Link: https://github.com/googleapis/synthtool/commit/aa255b15d52b6d8950cca48cfdf58f7d27a60c8a * docs(python): document adding Python 3.9 support, dropping 3.5 support Closes #787 Source-Author: Tres Seaver Source-Date: Thu Dec 17 16:08:02 2020 -0500 Source-Repo: googleapis/synthtool Source-Sha: b670a77a454f415d247907908e8ee7943e06d718 Source-Link: https://github.com/googleapis/synthtool/commit/b670a77a454f415d247907908e8ee7943e06d718 * chore: exclude `.nox` directories from linting The samples tests create `.nox` directories with all dependencies installed. These directories should be excluded from linting. I've tested this change locally, and it significantly speeds up linting on my machine. Source-Author: Tim Swast Source-Date: Tue Dec 22 13:04:04 2020 -0600 Source-Repo: googleapis/synthtool Source-Sha: 373861061648b5fe5e0ac4f8a38b32d639ee93e4 Source-Link: https://github.com/googleapis/synthtool/commit/373861061648b5fe5e0ac4f8a38b32d639ee93e4 * chore(python): fix column sizing issue in docs Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Thu Jan 7 11:58:32 2021 -0700 Source-Repo: googleapis/synthtool Source-Sha: f15b57ccfd71106c2299e9b89835fe6e55015662 Source-Link: https://github.com/googleapis/synthtool/commit/f15b57ccfd71106c2299e9b89835fe6e55015662 * chore(python): use 'http' in LICENSE Co-authored-by: Tim Swast Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Thu Jan 7 13:05:12 2021 -0700 Source-Repo: googleapis/synthtool Source-Sha: 41a4e56982620d3edcf110d76f4fcdfdec471ac8 Source-Link: https://github.com/googleapis/synthtool/commit/41a4e56982620d3edcf110d76f4fcdfdec471ac8 * chore(python): skip docfx in main presubmit * chore(python): skip docfx in main presubmit * fix: properly template the repo name Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Fri Jan 8 10:32:13 2021 -0700 Source-Repo: googleapis/synthtool Source-Sha: fb53b6fb373b7c3edf4e55f3e8036bc6d73fa483 Source-Link: https://github.com/googleapis/synthtool/commit/fb53b6fb373b7c3edf4e55f3e8036bc6d73fa483 * chore: add missing quotation mark Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Mon Jan 11 09:43:06 2021 -0700 Source-Repo: googleapis/synthtool Source-Sha: 16ec872dd898d7de6e1822badfac32484b5d9031 Source-Link: https://github.com/googleapis/synthtool/commit/16ec872dd898d7de6e1822badfac32484b5d9031 * build(python): make `NOX_SESSION` optional I added this accidentally in #889. `NOX_SESSION` should be passed down if it is set but not marked required. Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Tue Jan 19 09:38:04 2021 -0700 Source-Repo: googleapis/synthtool Source-Sha: ba960d730416fe05c50547e975ce79fcee52c671 Source-Link: https://github.com/googleapis/synthtool/commit/ba960d730416fe05c50547e975ce79fcee52c671 * chore: Add header checker config to python library synth Now that we have it working in [python-docs-samples](https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/.github/header-checker-lint.yml) we should consider adding it to the ๐Ÿ libraries :) Source-Author: Leah E. Cole <6719667+leahecole@users.noreply.github.com> Source-Date: Mon Jan 25 13:24:08 2021 -0800 Source-Repo: googleapis/synthtool Source-Sha: 573f7655311b553a937f9123bee17bf78497db95 Source-Link: https://github.com/googleapis/synthtool/commit/573f7655311b553a937f9123bee17bf78497db95 * chore: add noxfile parameters for extra dependencies Also, add tests for some noxfile parameters for assurance that the template generates valid Python. Co-authored-by: Jeffrey Rennie Source-Author: Tim Swast Source-Date: Tue Jan 26 12:26:57 2021 -0600 Source-Repo: googleapis/synthtool Source-Sha: 778d8beae28d6d87eb01fdc839a4b4d966ed2ebe Source-Link: https://github.com/googleapis/synthtool/commit/778d8beae28d6d87eb01fdc839a4b4d966ed2ebe * build: migrate to flakybot Source-Author: Justin Beckwith Source-Date: Thu Jan 28 22:22:38 2021 -0800 Source-Repo: googleapis/synthtool Source-Sha: d1bb9173100f62c0cfc8f3138b62241e7f47ca6a Source-Link: https://github.com/googleapis/synthtool/commit/d1bb9173100f62c0cfc8f3138b62241e7f47ca6a * chore(python): include py.typed files in release A py.typed file must be included in the released package for it to be considered typed by type checkers. https://www.python.org/dev/peps/pep-0561/#packaging-type-information. See https://github.com/googleapis/python-secret-manager/issues/79 Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Fri Feb 5 17:32:06 2021 -0700 Source-Repo: googleapis/synthtool Source-Sha: 33366574ffb9e11737b3547eb6f020ecae0536e8 Source-Link: https://github.com/googleapis/synthtool/commit/33366574ffb9e11737b3547eb6f020ecae0536e8 * docs: update python contributing guide Adds details about blacken, updates version for system tests, and shows how to pass through pytest arguments. Source-Author: Chris Cotter Source-Date: Mon Feb 8 17:13:36 2021 -0500 Source-Repo: googleapis/synthtool Source-Sha: 4679e7e415221f03ff2a71e3ffad75b9ec41d87e Source-Link: https://github.com/googleapis/synthtool/commit/4679e7e415221f03ff2a71e3ffad75b9ec41d87e * build(python): enable flakybot on library unit and system tests Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Wed Feb 17 14:10:46 2021 -0700 Source-Repo: googleapis/synthtool Source-Sha: d17674372e27fb8f23013935e794aa37502071aa Source-Link: https://github.com/googleapis/synthtool/commit/d17674372e27fb8f23013935e794aa37502071aa * test: install pyopenssl for mtls testing Source-Author: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Source-Date: Tue Mar 2 12:27:56 2021 -0800 Source-Repo: googleapis/synthtool Source-Sha: 0780323da96d5a53925fe0547757181fe76e8f1e Source-Link: https://github.com/googleapis/synthtool/commit/0780323da96d5a53925fe0547757181fe76e8f1e --- .flake8 | 1 + .github/header-checker-lint.yml | 15 +++++++++ .gitignore | 4 ++- .kokoro/build.sh | 26 ++++++++++++---- .kokoro/docs/docs-presubmit.cfg | 11 +++++++ .pre-commit-config.yaml | 17 ++++++++++ .trampolinerc | 1 + CONTRIBUTING.rst | 43 ++++++++++++++++++++------ LICENSE | 7 +++-- MANIFEST.in | 4 +-- docs/_static/custom.css | 7 ++++- docs/conf.py | 4 +-- google/cloud/pubsublite_v1/__init__.py | 4 +-- noxfile.py | 37 +++++++++++++++++++--- synth.metadata | 11 +++---- 15 files changed, 154 insertions(+), 38 deletions(-) create mode 100644 .github/header-checker-lint.yml create mode 100644 .pre-commit-config.yaml diff --git a/.flake8 b/.flake8 index ed931638..29227d4c 100644 --- a/.flake8 +++ b/.flake8 @@ -26,6 +26,7 @@ exclude = *_pb2.py # Standard linting exemptions. + **/.nox/** __pycache__, .git, *.pyc, diff --git a/.github/header-checker-lint.yml b/.github/header-checker-lint.yml new file mode 100644 index 00000000..fc281c05 --- /dev/null +++ b/.github/header-checker-lint.yml @@ -0,0 +1,15 @@ +{"allowedCopyrightHolders": ["Google LLC"], + "allowedLicenses": ["Apache-2.0", "MIT", "BSD-3"], + "ignoreFiles": ["**/requirements.txt", "**/requirements-test.txt"], + "sourceFileExtensions": [ + "ts", + "js", + "java", + "sh", + "Dockerfile", + "yaml", + "py", + "html", + "txt" + ] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index b9daa52f..b4243ced 100644 --- a/.gitignore +++ b/.gitignore @@ -50,8 +50,10 @@ docs.metadata # Virtual environment env/ + +# Test logs coverage.xml -sponge_log.xml +*sponge_log.xml # System test environment variables. system_tests/local_test_setup diff --git a/.kokoro/build.sh b/.kokoro/build.sh index f98dd91c..1e7cc13e 100755 --- a/.kokoro/build.sh +++ b/.kokoro/build.sh @@ -15,7 +15,11 @@ set -eo pipefail -cd github/python-pubsublite +if [[ -z "${PROJECT_ROOT:-}" ]]; then + PROJECT_ROOT="github/python-pubsublite" +fi + +cd "${PROJECT_ROOT}" # Disable buffering, so that the logs stream through. export PYTHONUNBUFFERED=1 @@ -30,16 +34,26 @@ export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/service-account.json export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.json") # Remove old nox -python3.6 -m pip uninstall --yes --quiet nox-automation +python3 -m pip uninstall --yes --quiet nox-automation # Install nox -python3.6 -m pip install --upgrade --quiet nox -python3.6 -m nox --version +python3 -m pip install --upgrade --quiet nox +python3 -m nox --version + +# If this is a continuous build, send the test log to the FlakyBot. +# See https://github.com/googleapis/repo-automation-bots/tree/master/packages/flakybot. +if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]]; then + cleanup() { + chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot + $KOKORO_GFILE_DIR/linux_amd64/flakybot + } + trap cleanup EXIT HUP +fi # If NOX_SESSION is set, it only runs the specified session, # otherwise run all the sessions. if [[ -n "${NOX_SESSION:-}" ]]; then - python3.6 -m nox -s "${NOX_SESSION:-}" + python3 -m nox -s ${NOX_SESSION:-} else - python3.6 -m nox + python3 -m nox fi diff --git a/.kokoro/docs/docs-presubmit.cfg b/.kokoro/docs/docs-presubmit.cfg index 11181078..1328c435 100644 --- a/.kokoro/docs/docs-presubmit.cfg +++ b/.kokoro/docs/docs-presubmit.cfg @@ -15,3 +15,14 @@ env_vars: { key: "TRAMPOLINE_IMAGE_UPLOAD" value: "false" } + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-pubsublite/.kokoro/build.sh" +} + +# Only run this nox session. +env_vars: { + key: "NOX_SESSION" + value: "docs docfx" +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..a9024b15 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml +- repo: https://github.com/psf/black + rev: 19.10b0 + hooks: + - id: black +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.4 + hooks: + - id: flake8 diff --git a/.trampolinerc b/.trampolinerc index 995ee291..383b6ec8 100644 --- a/.trampolinerc +++ b/.trampolinerc @@ -24,6 +24,7 @@ required_envvars+=( pass_down_envvars+=( "STAGING_BUCKET" "V2_STAGING_BUCKET" + "NOX_SESSION" ) # Prevent unintentional override on the default image. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index a6c4c49c..20aca1aa 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -21,8 +21,8 @@ In order to add a feature: - The feature must be documented in both the API and narrative documentation. -- The feature must work fully on the following CPython versions: 2.7, - 3.5, 3.6, 3.7 and 3.8 on both UNIX and Windows. +- The feature must work fully on the following CPython versions: + 3.6, 3.7, 3.8 and 3.9 on both UNIX and Windows. - The feature must not add unnecessary dependencies (where "unnecessary" is of course subjective, but new dependencies should @@ -70,9 +70,14 @@ We use `nox `__ to instrument our tests. - To test your changes, run unit tests with ``nox``:: $ nox -s unit-2.7 - $ nox -s unit-3.7 + $ nox -s unit-3.8 $ ... +- Args to pytest can be passed through the nox command separated by a `--`. For + example, to run a single test:: + + $ nox -s unit-3.8 -- -k + .. note:: The unit tests and system tests are described in the @@ -93,8 +98,12 @@ On Debian/Ubuntu:: ************ Coding Style ************ +- We use the automatic code formatter ``black``. You can run it using + the nox session ``blacken``. This will eliminate many lint errors. Run via:: -- PEP8 compliance, with exceptions defined in the linter configuration. + $ nox -s blacken + +- PEP8 compliance is required, with exceptions defined in the linter configuration. If you have ``nox`` installed, you can test that you have not introduced any non-compliant code via:: @@ -111,6 +120,16 @@ Coding Style should point to the official ``googleapis`` checkout and the the branch should be the main branch on that remote (``master``). +- This repository contains configuration for the + `pre-commit `__ tool, which automates checking + our linters during a commit. If you have it installed on your ``$PATH``, + you can enable enforcing those checks via: + +.. code-block:: bash + + $ pre-commit install + pre-commit installed at .git/hooks/pre-commit + Exceptions to PEP8: - Many unit tests use a helper method, ``_call_fut`` ("FUT" is short for @@ -123,13 +142,18 @@ Running System Tests - To run system tests, you can execute:: - $ nox -s system-3.7 + # Run all system tests + $ nox -s system-3.8 $ nox -s system-2.7 + # Run a single system test + $ nox -s system-3.8 -- -k + + .. note:: System tests are only configured to run under Python 2.7 and - Python 3.7. For expediency, we do not run them in older versions + Python 3.8. For expediency, we do not run them in older versions of Python 3. This alone will not run the tests. You'll need to change some local @@ -192,25 +216,24 @@ Supported Python Versions We support: -- `Python 3.5`_ - `Python 3.6`_ - `Python 3.7`_ - `Python 3.8`_ +- `Python 3.9`_ -.. _Python 3.5: https://docs.python.org/3.5/ .. _Python 3.6: https://docs.python.org/3.6/ .. _Python 3.7: https://docs.python.org/3.7/ .. _Python 3.8: https://docs.python.org/3.8/ +.. _Python 3.9: https://docs.python.org/3.9/ Supported versions can be found in our ``noxfile.py`` `config`_. .. _config: https://github.com/googleapis/python-pubsublite/blob/master/noxfile.py -Python 2.7 support is deprecated. All code changes should maintain Python 2.7 compatibility until January 1, 2020. We also explicitly decided to support Python 3 beginning with version -3.5. Reasons for this include: +3.6. Reasons for this include: - Encouraging use of newest versions of Python 3 - Taking the lead of `prominent`_ open-source `projects`_ diff --git a/LICENSE b/LICENSE index a8ee855d..d6456956 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ - Apache License + + Apache License Version 2.0, January 2004 - https://www.apache.org/licenses/ + http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION @@ -192,7 +193,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - https://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/MANIFEST.in b/MANIFEST.in index e9e29d12..e783f4c6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -16,10 +16,10 @@ # Generated by synthtool. DO NOT EDIT! include README.rst LICENSE -recursive-include google *.json *.proto +recursive-include google *.json *.proto py.typed recursive-include tests * global-exclude *.py[co] global-exclude __pycache__ # Exclude scripts for samples readmegen -prune scripts/readme-gen \ No newline at end of file +prune scripts/readme-gen diff --git a/docs/_static/custom.css b/docs/_static/custom.css index 0abaf229..bcd37bbd 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -1,4 +1,9 @@ div#python2-eol { border-color: red; border-width: medium; -} \ No newline at end of file +} + +/* Ensure minimum width for 'Parameters' / 'Returns' column */ +dl.field-list > dt { + min-width: 100px +} diff --git a/docs/conf.py b/docs/conf.py index ff0e2917..4ec5f60d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -345,8 +345,8 @@ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - "python": ("https://python.readthedocs.io/en/latest/", None), - "google-auth": ("https://google-auth.readthedocs.io/en/stable", None), + "python": ("https://python.readthedocs.org/en/latest/", None), + "google-auth": ("https://googleapis.dev/python/google-auth/latest/", None), "google.api_core": ("https://googleapis.dev/python/google-api-core/latest/", None,), "grpc": ("https://grpc.github.io/grpc/python/", None), "proto-plus": ("https://proto-plus-python.readthedocs.io/en/latest/", None), diff --git a/google/cloud/pubsublite_v1/__init__.py b/google/cloud/pubsublite_v1/__init__.py index 3d423c80..ae46c0b5 100644 --- a/google/cloud/pubsublite_v1/__init__.py +++ b/google/cloud/pubsublite_v1/__init__.py @@ -77,6 +77,7 @@ __all__ = ( + "AdminServiceClient", "AttributeValues", "CommitCursorRequest", "CommitCursorResponse", @@ -128,12 +129,11 @@ "StreamingCommitCursorResponse", "SubscribeRequest", "SubscribeResponse", - "SubscriberServiceClient", "Subscription", "Topic", "TopicPartitions", "TopicStatsServiceClient", "UpdateSubscriptionRequest", "UpdateTopicRequest", - "AdminServiceClient", + "SubscriberServiceClient", ) diff --git a/noxfile.py b/noxfile.py index a2791746..afdeb126 100644 --- a/noxfile.py +++ b/noxfile.py @@ -30,6 +30,17 @@ SYSTEM_TEST_PYTHON_VERSIONS = ["3.8"] UNIT_TEST_PYTHON_VERSIONS = ["3.6", "3.7", "3.8", "3.9"] +# 'docfx' is excluded since it only needs to run in 'docs-presubmit' +nox.options.sessions = [ + "unit", + "system", + "cover", + "lint", + "lint_setup_py", + "blacken", + "docs", +] + @nox.session(python=DEFAULT_PYTHON_VERSION) def lint(session): @@ -73,15 +84,16 @@ def default(session): session.install("asyncmock", "pytest-asyncio") session.install("mock", "pytest", "pytest-cov", "asynctest") + session.install("-e", ".") # Run py.test against the unit tests. session.run( "py.test", "--quiet", - "--cov=google.cloud.pubsublite", - "--cov=google.cloud", - "--cov=tests.unit", + f"--junitxml=unit_{session.python}_sponge_log.xml", + "--cov=google/cloud", + "--cov=tests/unit", "--cov-append", "--cov-config=.coveragerc", "--cov-report=", @@ -109,6 +121,9 @@ def system(session): # Sanity check: Only run tests if the environment variable is set. if not os.environ.get("GOOGLE_APPLICATION_CREDENTIALS", ""): session.skip("Credentials must be set via environment variable") + # Install pyopenssl for mTLS testing. + if os.environ.get("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true": + session.install("pyopenssl") system_test_exists = os.path.exists(system_test_path) system_test_folder_exists = os.path.exists(system_test_folder_path) @@ -126,9 +141,21 @@ def system(session): # Run py.test against the system tests. if system_test_exists: - session.run("py.test", "--quiet", system_test_path, *session.posargs) + session.run( + "py.test", + "--quiet", + f"--junitxml=system_{session.python}_sponge_log.xml", + system_test_path, + *session.posargs, + ) if system_test_folder_exists: - session.run("py.test", "--quiet", system_test_folder_path, *session.posargs) + session.run( + "py.test", + "--quiet", + f"--junitxml=system_{session.python}_sponge_log.xml", + system_test_folder_path, + *session.posargs, + ) @nox.session(python=DEFAULT_PYTHON_VERSION) diff --git a/synth.metadata b/synth.metadata index 576e4390..57d521d2 100644 --- a/synth.metadata +++ b/synth.metadata @@ -4,7 +4,7 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/python-pubsublite.git", - "sha": "7861ffea044dce4dc142dfd52d39dd64b9e79d65" + "sha": "f913ed00453d3fe924e75caccd2486b24e12a6ae" } }, { @@ -19,14 +19,14 @@ "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "d5fc0bcf9ea9789c5b0e3154a9e3b29e5cea6116" + "sha": "0780323da96d5a53925fe0547757181fe76e8f1e" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "d5fc0bcf9ea9789c5b0e3154a9e3b29e5cea6116" + "sha": "0780323da96d5a53925fe0547757181fe76e8f1e" } } ], @@ -48,6 +48,7 @@ ".github/ISSUE_TEMPLATE/feature_request.md", ".github/ISSUE_TEMPLATE/support_request.md", ".github/PULL_REQUEST_TEMPLATE.md", + ".github/header-checker-lint.yml", ".github/release-please.yml", ".github/snippet-bot.yml", ".gitignore", @@ -85,6 +86,7 @@ ".kokoro/test-samples.sh", ".kokoro/trampoline.sh", ".kokoro/trampoline_v2.sh", + ".pre-commit-config.yaml", ".trampolinerc", "CODE_OF_CONDUCT.md", "CONTRIBUTING.rst", @@ -93,9 +95,6 @@ "docs/_static/custom.css", "docs/_templates/layout.html", "docs/conf.py", - "docs/multiprocessing.rst", - "docs/pubsublite_v1/services.rst", - "docs/pubsublite_v1/types.rst", "google/cloud/pubsublite/py.typed", "google/cloud/pubsublite_v1/__init__.py", "google/cloud/pubsublite_v1/py.typed", From 153deb5d48bfb8baf5dccd0857718d87d0167110 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Thu, 11 Mar 2021 09:26:45 -0800 Subject: [PATCH 5/8] chore: pick up changes from googleapis (#98) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * changes without context autosynth cannot find the source of changes triggered by earlier changes in this repository, or by version upgrades to tools such as linters. * chore: migrate example/library to the Java microgenerator Committer: @miraleung PiperOrigin-RevId: 345498172 Source-Author: Google APIs Source-Date: Thu Dec 3 11:36:52 2020 -0800 Source-Repo: googleapis/googleapis Source-Sha: 07027f95bf0a5b2451984c01a87ea86ee715c15d Source-Link: https://github.com/googleapis/googleapis/commit/07027f95bf0a5b2451984c01a87ea86ee715c15d * feat: migrate bigtable retry/timeout settings to gRPC's service configs Committer: @miraleung PiperOrigin-RevId: 346894665 Source-Author: Google APIs Source-Date: Thu Dec 10 16:55:31 2020 -0800 Source-Repo: googleapis/googleapis Source-Sha: cbbd3170bcf217e36ae72f4ac522449bf861346f Source-Link: https://github.com/googleapis/googleapis/commit/cbbd3170bcf217e36ae72f4ac522449bf861346f * fix: remove client recv msg limit fix: add enums to `types/__init__.py` PiperOrigin-RevId: 347055288 Source-Author: Google APIs Source-Date: Fri Dec 11 12:44:37 2020 -0800 Source-Repo: googleapis/googleapis Source-Sha: dd372aa22ded7a8ba6f0e03a80e06358a3fa0907 Source-Link: https://github.com/googleapis/googleapis/commit/dd372aa22ded7a8ba6f0e03a80e06358a3fa0907 * feat: Add support for "billingAccounts" as another parent resource name for recommendations and insights APIs. PiperOrigin-RevId: 347651882 Source-Author: Google APIs Source-Date: Tue Dec 15 11:11:56 2020 -0800 Source-Repo: googleapis/googleapis Source-Sha: e689e62a5540d4b98639f0e444a0edf5b2d94043 Source-Link: https://github.com/googleapis/googleapis/commit/e689e62a5540d4b98639f0e444a0edf5b2d94043 * feat: Add ComputeHeadCursor RPC for Pub/Sub Lite. PiperOrigin-RevId: 347681363 Source-Author: Google APIs Source-Date: Tue Dec 15 13:31:04 2020 -0800 Source-Repo: googleapis/googleapis Source-Sha: f967ea0c0437a269515665ff9dbb69fcf134ddd9 Source-Link: https://github.com/googleapis/googleapis/commit/f967ea0c0437a269515665ff9dbb69fcf134ddd9 * build: add gapic yaml config and regenerate BUILD files for APIs PiperOrigin-RevId: 347695607 Source-Author: Google APIs Source-Date: Tue Dec 15 14:40:01 2020 -0800 Source-Repo: googleapis/googleapis Source-Sha: 64f1e20354393415d5da54141ed39574a0cd1f30 Source-Link: https://github.com/googleapis/googleapis/commit/64f1e20354393415d5da54141ed39574a0cd1f30 * feat: public protos for API Gateway API PiperOrigin-RevId: 347907714 Source-Author: Google APIs Source-Date: Wed Dec 16 15:18:35 2020 -0800 Source-Repo: googleapis/googleapis Source-Sha: 48fa7512d385dd8ea8e51aab083086858b4dbffe Source-Link: https://github.com/googleapis/googleapis/commit/48fa7512d385dd8ea8e51aab083086858b4dbffe * chore: upgrade gapic-generator-python to 0.39.1 feat: add 'from_service_account_info' factory to clients fix: fix sphinx identifiers PiperOrigin-RevId: 350246057 Source-Author: Google APIs Source-Date: Tue Jan 5 16:44:11 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 520682435235d9c503983a360a2090025aa47cd1 Source-Link: https://github.com/googleapis/googleapis/commit/520682435235d9c503983a360a2090025aa47cd1 * fix: generated package names for C#, Ruby, and PHP docs: minor cleanup, formatting and edits PiperOrigin-RevId: 351455519 Source-Author: Google APIs Source-Date: Tue Jan 12 14:55:09 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: b71a6e53e944167cb4bcec042210d8cf2a9e40ca Source-Link: https://github.com/googleapis/googleapis/commit/b71a6e53e944167cb4bcec042210d8cf2a9e40ca * chore: migrate websecurityscanner to the Python microgenerator Committer: @miraleung PiperOrigin-RevId: 351457221 Source-Author: Google APIs Source-Date: Tue Jan 12 15:03:33 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: e4a9d34833c20d6bc4836b886e8ae268d9dc9f1d Source-Link: https://github.com/googleapis/googleapis/commit/e4a9d34833c20d6bc4836b886e8ae268d9dc9f1d * feat: added expire_time and ttl fields to Secret PiperOrigin-RevId: 352563582 Source-Author: Google APIs Source-Date: Tue Jan 19 07:29:20 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 9ecdacc9a00e1dd443b11bf10215d6e7648db8a7 Source-Link: https://github.com/googleapis/googleapis/commit/9ecdacc9a00e1dd443b11bf10215d6e7648db8a7 * chore: update Go generator, rules_go, and protobuf PiperOrigin-RevId: 352816749 Source-Author: Google APIs Source-Date: Wed Jan 20 10:06:23 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: ceaaf31b3d13badab7cf9d3b570f5639db5593d9 Source-Link: https://github.com/googleapis/googleapis/commit/ceaaf31b3d13badab7cf9d3b570f5639db5593d9 * chore: upgrade gapic-generator-python to 0.40.5 PiperOrigin-RevId: 354996675 Source-Author: Google APIs Source-Date: Mon Feb 1 12:11:49 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 20712b8fe95001b312f62c6c5f33e3e3ec92cfaf Source-Link: https://github.com/googleapis/googleapis/commit/20712b8fe95001b312f62c6c5f33e3e3ec92cfaf * feat: Add Pub/Sub endpoints for Cloud Channel API. PiperOrigin-RevId: 355059873 Source-Author: Google APIs Source-Date: Mon Feb 1 17:13:22 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 6ef9eaea379fc1cc0355e06a5a20b594543ee693 Source-Link: https://github.com/googleapis/googleapis/commit/6ef9eaea379fc1cc0355e06a5a20b594543ee693 * feat: Add `ErrorReason` enum from `google.api.error_reason` for Google API and minor proto updates. PiperOrigin-RevId: 355218181 Source-Author: Google APIs Source-Date: Tue Feb 2 11:53:01 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 2f8d204b9bc1126439565e25a456eaf6435f38f4 Source-Link: https://github.com/googleapis/googleapis/commit/2f8d204b9bc1126439565e25a456eaf6435f38f4 * chore: remove more unused package options in java_gapic_library rules Committer: @miraleung PiperOrigin-RevId: 355442508 Source-Author: Google APIs Source-Date: Wed Feb 3 11:32:57 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: e038e2a5b2104dfae83edada1dfb41b319fc363f Source-Link: https://github.com/googleapis/googleapis/commit/e038e2a5b2104dfae83edada1dfb41b319fc363f * build: add package name to BUILD file for nodejs-access-approval PiperOrigin-RevId: 355768486 Source-Author: Google APIs Source-Date: Thu Feb 4 20:12:35 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 53dd6b53c7af308c69581c364ad20c52aa4c6ca0 Source-Link: https://github.com/googleapis/googleapis/commit/53dd6b53c7af308c69581c364ad20c52aa4c6ca0 * chore: re-enable python generation for dialogflow cx. PiperOrigin-RevId: 355906816 Source-Author: Google APIs Source-Date: Fri Feb 5 12:40:52 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 254325ed3c9caa6bf6d9c8b11ce2674544ba6e7c Source-Link: https://github.com/googleapis/googleapis/commit/254325ed3c9caa6bf6d9c8b11ce2674544ba6e7c * chore: update gapic-generator-python PiperOrigin-RevId: 355923884 Source-Author: Google APIs Source-Date: Fri Feb 5 14:04:52 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 5e3dacee19405529b841b53797df799c2383536c Source-Link: https://github.com/googleapis/googleapis/commit/5e3dacee19405529b841b53797df799c2383536c * chore: remove non-existent package option in java_gapic_library rules for cloud APIs Committer: @miraleung PiperOrigin-RevId: 356328938 Source-Author: Google APIs Source-Date: Mon Feb 8 12:39:42 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 78e0057d81c6969507bf1195b5aad8ac3e7feafd Source-Link: https://github.com/googleapis/googleapis/commit/78e0057d81c6969507bf1195b5aad8ac3e7feafd * feat: added ApplySoftwareUpdate API docs: various clarifications, new documentation for ApplySoftwareUpdate chore: update proto annotations PiperOrigin-RevId: 356380191 Source-Author: Google APIs Source-Date: Mon Feb 8 16:30:59 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 84cf54e45ed5970980ae868e0a1e5ad1266a8830 Source-Link: https://github.com/googleapis/googleapis/commit/84cf54e45ed5970980ae868e0a1e5ad1266a8830 * feat: Add Traffic data to the leg and step level. PiperOrigin-RevId: 357215334 Source-Author: Google APIs Source-Date: Fri Feb 12 10:11:29 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 5a11760c9dcdf63deba111e44bc12df5a4b5cd3a Source-Link: https://github.com/googleapis/googleapis/commit/5a11760c9dcdf63deba111e44bc12df5a4b5cd3a * chore: update gapic-generator-python to 0.40.11 PiperOrigin-RevId: 359562873 Source-Author: Google APIs Source-Date: Thu Feb 25 10:52:32 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 07932bb995e7dc91b43620ea8402c6668c7d102c Source-Link: https://github.com/googleapis/googleapis/commit/07932bb995e7dc91b43620ea8402c6668c7d102c * chore: clean up unused gapic.legacy.yaml files Committer: @miraleung PiperOrigin-RevId: 359580699 Source-Author: Google APIs Source-Date: Thu Feb 25 12:03:52 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: d9b32e92fa57c37e5af0dc03badfe741170c5849 Source-Link: https://github.com/googleapis/googleapis/commit/d9b32e92fa57c37e5af0dc03badfe741170c5849 * build: add package name for artifact registry PiperOrigin-RevId: 359593247 Source-Author: Google APIs Source-Date: Thu Feb 25 13:01:50 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: e69f9d4f00a558a359a55f3bb33ba582d6bfc805 Source-Link: https://github.com/googleapis/googleapis/commit/e69f9d4f00a558a359a55f3bb33ba582d6bfc805 * chore: update protobuf v3.15.3 This release makes --experimental_allow_proto3_optional no longer necessary. PiperOrigin-RevId: 359781040 Source-Author: Google APIs Source-Date: Fri Feb 26 09:59:49 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: f6dd7e47620566925a4b3f1ce029e74e1b2f2516 Source-Link: https://github.com/googleapis/googleapis/commit/f6dd7e47620566925a4b3f1ce029e74e1b2f2516 * feat: publish documentai/v1 protos PiperOrigin-RevId: 360214832 Source-Author: Google APIs Source-Date: Mon Mar 1 09:59:41 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: be7020ff106658f75d50e14693b13d822d910325 Source-Link: https://github.com/googleapis/googleapis/commit/be7020ff106658f75d50e14693b13d822d910325 * feat: add skip_backlog field to allow subscriptions to be created at HEAD Committer: @hannahrogers-google PiperOrigin-RevId: 360987981 Source-Author: Google APIs Source-Date: Thu Mar 4 13:12:18 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: ead17216f9bc591177041c6aeccee751ad7df0d1 Source-Link: https://github.com/googleapis/googleapis/commit/ead17216f9bc591177041c6aeccee751ad7df0d1 * feat: Add CCAI API PiperOrigin-RevId: 361000862 Source-Author: Google APIs Source-Date: Thu Mar 4 14:10:49 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 4b4e8be384ce5d3420e3af430ca50fa72c561bfd Source-Link: https://github.com/googleapis/googleapis/commit/4b4e8be384ce5d3420e3af430ca50fa72c561bfd * feat: add PHP ยต-generator build targets to googleads Committer: @aohren PiperOrigin-RevId: 361555541 Source-Author: Google APIs Source-Date: Mon Mar 8 07:00:53 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 65d71a60baf9650404a4d9d65f29e9ba8db490d1 Source-Link: https://github.com/googleapis/googleapis/commit/65d71a60baf9650404a4d9d65f29e9ba8db490d1 * chore: upgrade gapic-generator-python to 0.42.2 PiperOrigin-RevId: 361662015 Source-Author: Google APIs Source-Date: Mon Mar 8 14:47:18 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 28a591963253d52ce3a25a918cafbdd9928de8cf Source-Link: https://github.com/googleapis/googleapis/commit/28a591963253d52ce3a25a918cafbdd9928de8cf * build: use gapic-generator-typescript v1.2.11. Fixed IAM v1 library generation. Committer: @alexander-fenster PiperOrigin-RevId: 361676678 Source-Author: Google APIs Source-Date: Mon Mar 8 15:51:18 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: 3aeb3a70f66457a9e6b07caff841719bb9873b57 Source-Link: https://github.com/googleapis/googleapis/commit/3aeb3a70f66457a9e6b07caff841719bb9873b57 * docs: fix broken link in doc string PiperOrigin-RevId: 362183999 Source-Author: Google APIs Source-Date: Wed Mar 10 17:50:58 2021 -0800 Source-Repo: googleapis/googleapis Source-Sha: d652c6370bf66e325da6ac9ad82989fe7ee7bb4b Source-Link: https://github.com/googleapis/googleapis/commit/d652c6370bf66e325da6ac9ad82989fe7ee7bb4b * remove unwanted changes Co-authored-by: Tianzi Cai --- google/cloud/pubsublite_v1/__init__.py | 4 + .../services/admin_service/async_client.py | 105 +++-- .../services/admin_service/client.py | 138 +++--- .../services/admin_service/pagers.py | 59 ++- .../admin_service/transports/__init__.py | 1 - .../services/admin_service/transports/grpc.py | 33 +- .../admin_service/transports/grpc_asyncio.py | 31 +- .../services/cursor_service/async_client.py | 44 +- .../services/cursor_service/client.py | 55 ++- .../services/cursor_service/pagers.py | 27 +- .../cursor_service/transports/__init__.py | 1 - .../cursor_service/transports/grpc.py | 33 +- .../cursor_service/transports/grpc_asyncio.py | 31 +- .../async_client.py | 37 +- .../partition_assignment_service/client.py | 44 +- .../transports/__init__.py | 1 - .../transports/grpc.py | 33 +- .../transports/grpc_asyncio.py | 31 +- .../publisher_service/async_client.py | 35 +- .../services/publisher_service/client.py | 44 +- .../publisher_service/transports/__init__.py | 1 - .../publisher_service/transports/grpc.py | 33 +- .../transports/grpc_asyncio.py | 31 +- .../subscriber_service/async_client.py | 35 +- .../services/subscriber_service/client.py | 44 +- .../subscriber_service/transports/__init__.py | 1 - .../subscriber_service/transports/grpc.py | 33 +- .../transports/grpc_asyncio.py | 31 +- .../topic_stats_service/async_client.py | 93 +++- .../services/topic_stats_service/client.py | 103 ++++- .../transports/__init__.py | 1 - .../topic_stats_service/transports/base.py | 15 + .../topic_stats_service/transports/grpc.py | 67 ++- .../transports/grpc_asyncio.py | 66 ++- google/cloud/pubsublite_v1/types/__init__.py | 113 ++--- google/cloud/pubsublite_v1/types/admin.py | 24 +- google/cloud/pubsublite_v1/types/common.py | 30 +- google/cloud/pubsublite_v1/types/cursor.py | 16 +- google/cloud/pubsublite_v1/types/publisher.py | 12 +- .../cloud/pubsublite_v1/types/subscriber.py | 26 +- .../cloud/pubsublite_v1/types/topic_stats.py | 49 +- synth.metadata | 5 +- tests/unit/gapic/pubsublite_v1/__init__.py | 15 + .../gapic/pubsublite_v1/test_admin_service.py | 428 ++++++++++++++---- .../pubsublite_v1/test_cursor_service.py | 258 +++++++---- .../test_partition_assignment_service.py | 227 ++++++---- .../pubsublite_v1/test_publisher_service.py | 224 +++++---- .../pubsublite_v1/test_subscriber_service.py | 224 +++++---- .../pubsublite_v1/test_topic_stats_service.py | 397 ++++++++++++---- 49 files changed, 2482 insertions(+), 907 deletions(-) diff --git a/google/cloud/pubsublite_v1/__init__.py b/google/cloud/pubsublite_v1/__init__.py index ae46c0b5..97f01058 100644 --- a/google/cloud/pubsublite_v1/__init__.py +++ b/google/cloud/pubsublite_v1/__init__.py @@ -72,6 +72,8 @@ from .types.subscriber import SeekResponse from .types.subscriber import SubscribeRequest from .types.subscriber import SubscribeResponse +from .types.topic_stats import ComputeHeadCursorRequest +from .types.topic_stats import ComputeHeadCursorResponse from .types.topic_stats import ComputeMessageStatsRequest from .types.topic_stats import ComputeMessageStatsResponse @@ -81,6 +83,8 @@ "AttributeValues", "CommitCursorRequest", "CommitCursorResponse", + "ComputeHeadCursorRequest", + "ComputeHeadCursorResponse", "ComputeMessageStatsRequest", "ComputeMessageStatsResponse", "CreateSubscriptionRequest", diff --git a/google/cloud/pubsublite_v1/services/admin_service/async_client.py b/google/cloud/pubsublite_v1/services/admin_service/async_client.py index 1cdb674b..c063c75d 100644 --- a/google/cloud/pubsublite_v1/services/admin_service/async_client.py +++ b/google/cloud/pubsublite_v1/services/admin_service/async_client.py @@ -79,7 +79,36 @@ class AdminServiceAsyncClient: AdminServiceClient.parse_common_location_path ) - from_service_account_file = AdminServiceClient.from_service_account_file + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + AdminServiceAsyncClient: The constructed client. + """ + return AdminServiceClient.from_service_account_info.__func__(AdminServiceAsyncClient, info, *args, **kwargs) # type: ignore + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + AdminServiceAsyncClient: The constructed client. + """ + return AdminServiceClient.from_service_account_file.__func__(AdminServiceAsyncClient, filename, *args, **kwargs) # type: ignore + from_service_account_json = from_service_account_file @property @@ -157,18 +186,20 @@ async def create_topic( r"""Creates a new topic. Args: - request (:class:`~.admin.CreateTopicRequest`): + request (:class:`google.cloud.pubsublite_v1.types.CreateTopicRequest`): The request object. Request for CreateTopic. parent (:class:`str`): Required. The parent location in which to create the topic. Structured like ``projects/{project_number}/locations/{location}``. + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - topic (:class:`~.common.Topic`): + topic (:class:`google.cloud.pubsublite_v1.types.Topic`): Required. Configuration of the topic to create. Its ``name`` field is ignored. + This corresponds to the ``topic`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -177,6 +208,7 @@ async def create_topic( the final component of the topic's name. This value is structured like: ``my-topic-name``. + This corresponds to the ``topic_id`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -188,7 +220,7 @@ async def create_topic( sent along with the request as metadata. Returns: - ~.common.Topic: + google.cloud.pubsublite_v1.types.Topic: Metadata about a topic resource. """ # Create or coerce a protobuf request object. @@ -245,11 +277,12 @@ async def get_topic( r"""Returns the topic configuration. Args: - request (:class:`~.admin.GetTopicRequest`): + request (:class:`google.cloud.pubsublite_v1.types.GetTopicRequest`): The request object. Request for GetTopic. name (:class:`str`): Required. The name of the topic whose configuration to return. + This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -261,7 +294,7 @@ async def get_topic( sent along with the request as metadata. Returns: - ~.common.Topic: + google.cloud.pubsublite_v1.types.Topic: Metadata about a topic resource. """ # Create or coerce a protobuf request object. @@ -315,11 +348,12 @@ async def get_topic_partitions( topic. Args: - request (:class:`~.admin.GetTopicPartitionsRequest`): + request (:class:`google.cloud.pubsublite_v1.types.GetTopicPartitionsRequest`): The request object. Request for GetTopicPartitions. name (:class:`str`): Required. The topic whose partition information to return. + This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -331,7 +365,7 @@ async def get_topic_partitions( sent along with the request as metadata. Returns: - ~.admin.TopicPartitions: + google.cloud.pubsublite_v1.types.TopicPartitions: Response for GetTopicPartitions. """ # Create or coerce a protobuf request object. @@ -384,12 +418,13 @@ async def list_topics( r"""Returns the list of topics for the given project. Args: - request (:class:`~.admin.ListTopicsRequest`): + request (:class:`google.cloud.pubsublite_v1.types.ListTopicsRequest`): The request object. Request for ListTopics. parent (:class:`str`): Required. The parent whose topics are to be listed. Structured like ``projects/{project_number}/locations/{location}``. + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -401,7 +436,7 @@ async def list_topics( sent along with the request as metadata. Returns: - ~.pagers.ListTopicsAsyncPager: + google.cloud.pubsublite_v1.services.admin_service.pagers.ListTopicsAsyncPager: Response for ListTopics. Iterating over this object will yield results and resolve additional pages @@ -465,17 +500,19 @@ async def update_topic( r"""Updates properties of the specified topic. Args: - request (:class:`~.admin.UpdateTopicRequest`): + request (:class:`google.cloud.pubsublite_v1.types.UpdateTopicRequest`): The request object. Request for UpdateTopic. - topic (:class:`~.common.Topic`): + topic (:class:`google.cloud.pubsublite_v1.types.Topic`): Required. The topic to update. Its ``name`` field must be populated. + This corresponds to the ``topic`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - update_mask (:class:`~.field_mask.FieldMask`): + update_mask (:class:`google.protobuf.field_mask_pb2.FieldMask`): Required. A mask specifying the topic fields to change. + This corresponds to the ``update_mask`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -487,7 +524,7 @@ async def update_topic( sent along with the request as metadata. Returns: - ~.common.Topic: + google.cloud.pubsublite_v1.types.Topic: Metadata about a topic resource. """ # Create or coerce a protobuf request object. @@ -544,11 +581,12 @@ async def delete_topic( r"""Deletes the specified topic. Args: - request (:class:`~.admin.DeleteTopicRequest`): + request (:class:`google.cloud.pubsublite_v1.types.DeleteTopicRequest`): The request object. Request for DeleteTopic. name (:class:`str`): Required. The name of the topic to delete. + This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -609,11 +647,12 @@ async def list_topic_subscriptions( topic. Args: - request (:class:`~.admin.ListTopicSubscriptionsRequest`): + request (:class:`google.cloud.pubsublite_v1.types.ListTopicSubscriptionsRequest`): The request object. Request for ListTopicSubscriptions. name (:class:`str`): Required. The name of the topic whose subscriptions to list. + This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -625,7 +664,7 @@ async def list_topic_subscriptions( sent along with the request as metadata. Returns: - ~.pagers.ListTopicSubscriptionsAsyncPager: + google.cloud.pubsublite_v1.services.admin_service.pagers.ListTopicSubscriptionsAsyncPager: Response for ListTopicSubscriptions. Iterating over this object will yield results and resolve additional pages @@ -690,18 +729,20 @@ async def create_subscription( r"""Creates a new subscription. Args: - request (:class:`~.admin.CreateSubscriptionRequest`): + request (:class:`google.cloud.pubsublite_v1.types.CreateSubscriptionRequest`): The request object. Request for CreateSubscription. parent (:class:`str`): Required. The parent location in which to create the subscription. Structured like ``projects/{project_number}/locations/{location}``. + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - subscription (:class:`~.common.Subscription`): + subscription (:class:`google.cloud.pubsublite_v1.types.Subscription`): Required. Configuration of the subscription to create. Its ``name`` field is ignored. + This corresponds to the ``subscription`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -710,6 +751,7 @@ async def create_subscription( become the final component of the subscription's name. This value is structured like: ``my-sub-name``. + This corresponds to the ``subscription_id`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -721,7 +763,7 @@ async def create_subscription( sent along with the request as metadata. Returns: - ~.common.Subscription: + google.cloud.pubsublite_v1.types.Subscription: Metadata about a subscription resource. @@ -780,12 +822,13 @@ async def get_subscription( r"""Returns the subscription configuration. Args: - request (:class:`~.admin.GetSubscriptionRequest`): + request (:class:`google.cloud.pubsublite_v1.types.GetSubscriptionRequest`): The request object. Request for GetSubscription. name (:class:`str`): Required. The name of the subscription whose configuration to return. + This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -797,7 +840,7 @@ async def get_subscription( sent along with the request as metadata. Returns: - ~.common.Subscription: + google.cloud.pubsublite_v1.types.Subscription: Metadata about a subscription resource. @@ -853,12 +896,13 @@ async def list_subscriptions( project. Args: - request (:class:`~.admin.ListSubscriptionsRequest`): + request (:class:`google.cloud.pubsublite_v1.types.ListSubscriptionsRequest`): The request object. Request for ListSubscriptions. parent (:class:`str`): Required. The parent whose subscriptions are to be listed. Structured like ``projects/{project_number}/locations/{location}``. + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -870,7 +914,7 @@ async def list_subscriptions( sent along with the request as metadata. Returns: - ~.pagers.ListSubscriptionsAsyncPager: + google.cloud.pubsublite_v1.services.admin_service.pagers.ListSubscriptionsAsyncPager: Response for ListSubscriptions. Iterating over this object will yield results and resolve additional pages @@ -934,17 +978,19 @@ async def update_subscription( r"""Updates properties of the specified subscription. Args: - request (:class:`~.admin.UpdateSubscriptionRequest`): + request (:class:`google.cloud.pubsublite_v1.types.UpdateSubscriptionRequest`): The request object. Request for UpdateSubscription. - subscription (:class:`~.common.Subscription`): + subscription (:class:`google.cloud.pubsublite_v1.types.Subscription`): Required. The subscription to update. Its ``name`` field must be populated. Topic field must not be populated. + This corresponds to the ``subscription`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - update_mask (:class:`~.field_mask.FieldMask`): + update_mask (:class:`google.protobuf.field_mask_pb2.FieldMask`): Required. A mask specifying the subscription fields to change. + This corresponds to the ``update_mask`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -956,7 +1002,7 @@ async def update_subscription( sent along with the request as metadata. Returns: - ~.common.Subscription: + google.cloud.pubsublite_v1.types.Subscription: Metadata about a subscription resource. @@ -1015,11 +1061,12 @@ async def delete_subscription( r"""Deletes the specified subscription. Args: - request (:class:`~.admin.DeleteSubscriptionRequest`): + request (:class:`google.cloud.pubsublite_v1.types.DeleteSubscriptionRequest`): The request object. Request for DeleteSubscription. name (:class:`str`): Required. The name of the subscription to delete. + This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. diff --git a/google/cloud/pubsublite_v1/services/admin_service/client.py b/google/cloud/pubsublite_v1/services/admin_service/client.py index 159211ab..622d4cae 100644 --- a/google/cloud/pubsublite_v1/services/admin_service/client.py +++ b/google/cloud/pubsublite_v1/services/admin_service/client.py @@ -113,6 +113,22 @@ def _get_default_mtls_endpoint(api_endpoint): DEFAULT_ENDPOINT ) + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + AdminServiceClient: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_info(info) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + @classmethod def from_service_account_file(cls, filename: str, *args, **kwargs): """Creates an instance of this client using the provided credentials @@ -125,7 +141,7 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): kwargs: Additional arguments to pass to the constructor. Returns: - {@api.name}: The constructed client. + AdminServiceClient: The constructed client. """ credentials = service_account.Credentials.from_service_account_file(filename) kwargs["credentials"] = credentials @@ -249,10 +265,10 @@ def __init__( credentials identify the application to the service; if none are specified, the client will attempt to ascertain the credentials from the environment. - transport (Union[str, ~.AdminServiceTransport]): The + transport (Union[str, AdminServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (client_options_lib.ClientOptions): Custom options for the + client_options (google.api_core.client_options.ClientOptions): Custom options for the client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT @@ -288,21 +304,17 @@ def __init__( util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) ) - ssl_credentials = None + client_cert_source_func = None is_mtls = False if use_client_cert: if client_options.client_cert_source: - import grpc # type: ignore - - cert, key = client_options.client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) is_mtls = True + client_cert_source_func = client_options.client_cert_source else: - creds = SslCredentials() - is_mtls = creds.is_mtls - ssl_credentials = creds.ssl_credentials if is_mtls else None + is_mtls = mtls.has_default_client_cert_source() + client_cert_source_func = ( + mtls.default_client_cert_source() if is_mtls else None + ) # Figure out which api endpoint to use. if client_options.api_endpoint is not None: @@ -345,7 +357,7 @@ def __init__( credentials_file=client_options.credentials_file, host=api_endpoint, scopes=client_options.scopes, - ssl_channel_credentials=ssl_credentials, + client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, ) @@ -364,26 +376,29 @@ def create_topic( r"""Creates a new topic. Args: - request (:class:`~.admin.CreateTopicRequest`): + request (google.cloud.pubsublite_v1.types.CreateTopicRequest): The request object. Request for CreateTopic. - parent (:class:`str`): + parent (str): Required. The parent location in which to create the topic. Structured like ``projects/{project_number}/locations/{location}``. + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - topic (:class:`~.common.Topic`): + topic (google.cloud.pubsublite_v1.types.Topic): Required. Configuration of the topic to create. Its ``name`` field is ignored. + This corresponds to the ``topic`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - topic_id (:class:`str`): + topic_id (str): Required. The ID to use for the topic, which will become the final component of the topic's name. This value is structured like: ``my-topic-name``. + This corresponds to the ``topic_id`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -395,7 +410,7 @@ def create_topic( sent along with the request as metadata. Returns: - ~.common.Topic: + google.cloud.pubsublite_v1.types.Topic: Metadata about a topic resource. """ # Create or coerce a protobuf request object. @@ -453,11 +468,12 @@ def get_topic( r"""Returns the topic configuration. Args: - request (:class:`~.admin.GetTopicRequest`): + request (google.cloud.pubsublite_v1.types.GetTopicRequest): The request object. Request for GetTopic. - name (:class:`str`): + name (str): Required. The name of the topic whose configuration to return. + This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -469,7 +485,7 @@ def get_topic( sent along with the request as metadata. Returns: - ~.common.Topic: + google.cloud.pubsublite_v1.types.Topic: Metadata about a topic resource. """ # Create or coerce a protobuf request object. @@ -524,11 +540,12 @@ def get_topic_partitions( topic. Args: - request (:class:`~.admin.GetTopicPartitionsRequest`): + request (google.cloud.pubsublite_v1.types.GetTopicPartitionsRequest): The request object. Request for GetTopicPartitions. - name (:class:`str`): + name (str): Required. The topic whose partition information to return. + This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -540,7 +557,7 @@ def get_topic_partitions( sent along with the request as metadata. Returns: - ~.admin.TopicPartitions: + google.cloud.pubsublite_v1.types.TopicPartitions: Response for GetTopicPartitions. """ # Create or coerce a protobuf request object. @@ -594,12 +611,13 @@ def list_topics( r"""Returns the list of topics for the given project. Args: - request (:class:`~.admin.ListTopicsRequest`): + request (google.cloud.pubsublite_v1.types.ListTopicsRequest): The request object. Request for ListTopics. - parent (:class:`str`): + parent (str): Required. The parent whose topics are to be listed. Structured like ``projects/{project_number}/locations/{location}``. + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -611,7 +629,7 @@ def list_topics( sent along with the request as metadata. Returns: - ~.pagers.ListTopicsPager: + google.cloud.pubsublite_v1.services.admin_service.pagers.ListTopicsPager: Response for ListTopics. Iterating over this object will yield results and resolve additional pages @@ -676,17 +694,19 @@ def update_topic( r"""Updates properties of the specified topic. Args: - request (:class:`~.admin.UpdateTopicRequest`): + request (google.cloud.pubsublite_v1.types.UpdateTopicRequest): The request object. Request for UpdateTopic. - topic (:class:`~.common.Topic`): + topic (google.cloud.pubsublite_v1.types.Topic): Required. The topic to update. Its ``name`` field must be populated. + This corresponds to the ``topic`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - update_mask (:class:`~.field_mask.FieldMask`): + update_mask (google.protobuf.field_mask_pb2.FieldMask): Required. A mask specifying the topic fields to change. + This corresponds to the ``update_mask`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -698,7 +718,7 @@ def update_topic( sent along with the request as metadata. Returns: - ~.common.Topic: + google.cloud.pubsublite_v1.types.Topic: Metadata about a topic resource. """ # Create or coerce a protobuf request object. @@ -756,11 +776,12 @@ def delete_topic( r"""Deletes the specified topic. Args: - request (:class:`~.admin.DeleteTopicRequest`): + request (google.cloud.pubsublite_v1.types.DeleteTopicRequest): The request object. Request for DeleteTopic. - name (:class:`str`): + name (str): Required. The name of the topic to delete. + This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -822,11 +843,12 @@ def list_topic_subscriptions( topic. Args: - request (:class:`~.admin.ListTopicSubscriptionsRequest`): + request (google.cloud.pubsublite_v1.types.ListTopicSubscriptionsRequest): The request object. Request for ListTopicSubscriptions. - name (:class:`str`): + name (str): Required. The name of the topic whose subscriptions to list. + This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -838,7 +860,7 @@ def list_topic_subscriptions( sent along with the request as metadata. Returns: - ~.pagers.ListTopicSubscriptionsPager: + google.cloud.pubsublite_v1.services.admin_service.pagers.ListTopicSubscriptionsPager: Response for ListTopicSubscriptions. Iterating over this object will yield results and resolve additional pages @@ -904,26 +926,29 @@ def create_subscription( r"""Creates a new subscription. Args: - request (:class:`~.admin.CreateSubscriptionRequest`): + request (google.cloud.pubsublite_v1.types.CreateSubscriptionRequest): The request object. Request for CreateSubscription. - parent (:class:`str`): + parent (str): Required. The parent location in which to create the subscription. Structured like ``projects/{project_number}/locations/{location}``. + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - subscription (:class:`~.common.Subscription`): + subscription (google.cloud.pubsublite_v1.types.Subscription): Required. Configuration of the subscription to create. Its ``name`` field is ignored. + This corresponds to the ``subscription`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - subscription_id (:class:`str`): + subscription_id (str): Required. The ID to use for the subscription, which will become the final component of the subscription's name. This value is structured like: ``my-sub-name``. + This corresponds to the ``subscription_id`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -935,7 +960,7 @@ def create_subscription( sent along with the request as metadata. Returns: - ~.common.Subscription: + google.cloud.pubsublite_v1.types.Subscription: Metadata about a subscription resource. @@ -995,12 +1020,13 @@ def get_subscription( r"""Returns the subscription configuration. Args: - request (:class:`~.admin.GetSubscriptionRequest`): + request (google.cloud.pubsublite_v1.types.GetSubscriptionRequest): The request object. Request for GetSubscription. - name (:class:`str`): + name (str): Required. The name of the subscription whose configuration to return. + This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -1012,7 +1038,7 @@ def get_subscription( sent along with the request as metadata. Returns: - ~.common.Subscription: + google.cloud.pubsublite_v1.types.Subscription: Metadata about a subscription resource. @@ -1069,12 +1095,13 @@ def list_subscriptions( project. Args: - request (:class:`~.admin.ListSubscriptionsRequest`): + request (google.cloud.pubsublite_v1.types.ListSubscriptionsRequest): The request object. Request for ListSubscriptions. - parent (:class:`str`): + parent (str): Required. The parent whose subscriptions are to be listed. Structured like ``projects/{project_number}/locations/{location}``. + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -1086,7 +1113,7 @@ def list_subscriptions( sent along with the request as metadata. Returns: - ~.pagers.ListSubscriptionsPager: + google.cloud.pubsublite_v1.services.admin_service.pagers.ListSubscriptionsPager: Response for ListSubscriptions. Iterating over this object will yield results and resolve additional pages @@ -1151,17 +1178,19 @@ def update_subscription( r"""Updates properties of the specified subscription. Args: - request (:class:`~.admin.UpdateSubscriptionRequest`): + request (google.cloud.pubsublite_v1.types.UpdateSubscriptionRequest): The request object. Request for UpdateSubscription. - subscription (:class:`~.common.Subscription`): + subscription (google.cloud.pubsublite_v1.types.Subscription): Required. The subscription to update. Its ``name`` field must be populated. Topic field must not be populated. + This corresponds to the ``subscription`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - update_mask (:class:`~.field_mask.FieldMask`): + update_mask (google.protobuf.field_mask_pb2.FieldMask): Required. A mask specifying the subscription fields to change. + This corresponds to the ``update_mask`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -1173,7 +1202,7 @@ def update_subscription( sent along with the request as metadata. Returns: - ~.common.Subscription: + google.cloud.pubsublite_v1.types.Subscription: Metadata about a subscription resource. @@ -1233,11 +1262,12 @@ def delete_subscription( r"""Deletes the specified subscription. Args: - request (:class:`~.admin.DeleteSubscriptionRequest`): + request (google.cloud.pubsublite_v1.types.DeleteSubscriptionRequest): The request object. Request for DeleteSubscription. - name (:class:`str`): + name (str): Required. The name of the subscription to delete. + This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. diff --git a/google/cloud/pubsublite_v1/services/admin_service/pagers.py b/google/cloud/pubsublite_v1/services/admin_service/pagers.py index 8b37d64e..a0fe4d65 100644 --- a/google/cloud/pubsublite_v1/services/admin_service/pagers.py +++ b/google/cloud/pubsublite_v1/services/admin_service/pagers.py @@ -15,7 +15,16 @@ # limitations under the License. # -from typing import Any, AsyncIterable, Awaitable, Callable, Iterable, Sequence, Tuple +from typing import ( + Any, + AsyncIterable, + Awaitable, + Callable, + Iterable, + Sequence, + Tuple, + Optional, +) from google.cloud.pubsublite_v1.types import admin from google.cloud.pubsublite_v1.types import common @@ -25,7 +34,7 @@ class ListTopicsPager: """A pager for iterating through ``list_topics`` requests. This class thinly wraps an initial - :class:`~.admin.ListTopicsResponse` object, and + :class:`google.cloud.pubsublite_v1.types.ListTopicsResponse` object, and provides an ``__iter__`` method to iterate through its ``topics`` field. @@ -34,7 +43,7 @@ class ListTopicsPager: through the ``topics`` field on the corresponding responses. - All the usual :class:`~.admin.ListTopicsResponse` + All the usual :class:`google.cloud.pubsublite_v1.types.ListTopicsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -52,9 +61,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.admin.ListTopicsRequest`): + request (google.cloud.pubsublite_v1.types.ListTopicsRequest): The initial request object. - response (:class:`~.admin.ListTopicsResponse`): + response (google.cloud.pubsublite_v1.types.ListTopicsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. @@ -87,7 +96,7 @@ class ListTopicsAsyncPager: """A pager for iterating through ``list_topics`` requests. This class thinly wraps an initial - :class:`~.admin.ListTopicsResponse` object, and + :class:`google.cloud.pubsublite_v1.types.ListTopicsResponse` object, and provides an ``__aiter__`` method to iterate through its ``topics`` field. @@ -96,7 +105,7 @@ class ListTopicsAsyncPager: through the ``topics`` field on the corresponding responses. - All the usual :class:`~.admin.ListTopicsResponse` + All the usual :class:`google.cloud.pubsublite_v1.types.ListTopicsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -114,9 +123,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.admin.ListTopicsRequest`): + request (google.cloud.pubsublite_v1.types.ListTopicsRequest): The initial request object. - response (:class:`~.admin.ListTopicsResponse`): + response (google.cloud.pubsublite_v1.types.ListTopicsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. @@ -153,7 +162,7 @@ class ListTopicSubscriptionsPager: """A pager for iterating through ``list_topic_subscriptions`` requests. This class thinly wraps an initial - :class:`~.admin.ListTopicSubscriptionsResponse` object, and + :class:`google.cloud.pubsublite_v1.types.ListTopicSubscriptionsResponse` object, and provides an ``__iter__`` method to iterate through its ``subscriptions`` field. @@ -162,7 +171,7 @@ class ListTopicSubscriptionsPager: through the ``subscriptions`` field on the corresponding responses. - All the usual :class:`~.admin.ListTopicSubscriptionsResponse` + All the usual :class:`google.cloud.pubsublite_v1.types.ListTopicSubscriptionsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -180,9 +189,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.admin.ListTopicSubscriptionsRequest`): + request (google.cloud.pubsublite_v1.types.ListTopicSubscriptionsRequest): The initial request object. - response (:class:`~.admin.ListTopicSubscriptionsResponse`): + response (google.cloud.pubsublite_v1.types.ListTopicSubscriptionsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. @@ -215,7 +224,7 @@ class ListTopicSubscriptionsAsyncPager: """A pager for iterating through ``list_topic_subscriptions`` requests. This class thinly wraps an initial - :class:`~.admin.ListTopicSubscriptionsResponse` object, and + :class:`google.cloud.pubsublite_v1.types.ListTopicSubscriptionsResponse` object, and provides an ``__aiter__`` method to iterate through its ``subscriptions`` field. @@ -224,7 +233,7 @@ class ListTopicSubscriptionsAsyncPager: through the ``subscriptions`` field on the corresponding responses. - All the usual :class:`~.admin.ListTopicSubscriptionsResponse` + All the usual :class:`google.cloud.pubsublite_v1.types.ListTopicSubscriptionsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -242,9 +251,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.admin.ListTopicSubscriptionsRequest`): + request (google.cloud.pubsublite_v1.types.ListTopicSubscriptionsRequest): The initial request object. - response (:class:`~.admin.ListTopicSubscriptionsResponse`): + response (google.cloud.pubsublite_v1.types.ListTopicSubscriptionsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. @@ -281,7 +290,7 @@ class ListSubscriptionsPager: """A pager for iterating through ``list_subscriptions`` requests. This class thinly wraps an initial - :class:`~.admin.ListSubscriptionsResponse` object, and + :class:`google.cloud.pubsublite_v1.types.ListSubscriptionsResponse` object, and provides an ``__iter__`` method to iterate through its ``subscriptions`` field. @@ -290,7 +299,7 @@ class ListSubscriptionsPager: through the ``subscriptions`` field on the corresponding responses. - All the usual :class:`~.admin.ListSubscriptionsResponse` + All the usual :class:`google.cloud.pubsublite_v1.types.ListSubscriptionsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -308,9 +317,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.admin.ListSubscriptionsRequest`): + request (google.cloud.pubsublite_v1.types.ListSubscriptionsRequest): The initial request object. - response (:class:`~.admin.ListSubscriptionsResponse`): + response (google.cloud.pubsublite_v1.types.ListSubscriptionsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. @@ -343,7 +352,7 @@ class ListSubscriptionsAsyncPager: """A pager for iterating through ``list_subscriptions`` requests. This class thinly wraps an initial - :class:`~.admin.ListSubscriptionsResponse` object, and + :class:`google.cloud.pubsublite_v1.types.ListSubscriptionsResponse` object, and provides an ``__aiter__`` method to iterate through its ``subscriptions`` field. @@ -352,7 +361,7 @@ class ListSubscriptionsAsyncPager: through the ``subscriptions`` field on the corresponding responses. - All the usual :class:`~.admin.ListSubscriptionsResponse` + All the usual :class:`google.cloud.pubsublite_v1.types.ListSubscriptionsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -370,9 +379,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.admin.ListSubscriptionsRequest`): + request (google.cloud.pubsublite_v1.types.ListSubscriptionsRequest): The initial request object. - response (:class:`~.admin.ListSubscriptionsResponse`): + response (google.cloud.pubsublite_v1.types.ListSubscriptionsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. diff --git a/google/cloud/pubsublite_v1/services/admin_service/transports/__init__.py b/google/cloud/pubsublite_v1/services/admin_service/transports/__init__.py index 4debddc7..aee0db06 100644 --- a/google/cloud/pubsublite_v1/services/admin_service/transports/__init__.py +++ b/google/cloud/pubsublite_v1/services/admin_service/transports/__init__.py @@ -28,7 +28,6 @@ _transport_registry["grpc"] = AdminServiceGrpcTransport _transport_registry["grpc_asyncio"] = AdminServiceGrpcAsyncIOTransport - __all__ = ( "AdminServiceTransport", "AdminServiceGrpcTransport", diff --git a/google/cloud/pubsublite_v1/services/admin_service/transports/grpc.py b/google/cloud/pubsublite_v1/services/admin_service/transports/grpc.py index d1778c5f..51f97260 100644 --- a/google/cloud/pubsublite_v1/services/admin_service/transports/grpc.py +++ b/google/cloud/pubsublite_v1/services/admin_service/transports/grpc.py @@ -61,6 +61,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -91,6 +92,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -107,6 +112,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -116,11 +126,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -150,6 +155,10 @@ def __init__( ssl_credentials=ssl_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._ssl_channel_credentials = ssl_credentials else: @@ -160,14 +169,24 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._stubs = {} # type: Dict[str, Callable] @@ -194,7 +213,7 @@ def create_channel( ) -> grpc.Channel: """Create and return a gRPC channel object. Args: - address (Optionsl[str]): The host for the channel to use. + address (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If diff --git a/google/cloud/pubsublite_v1/services/admin_service/transports/grpc_asyncio.py b/google/cloud/pubsublite_v1/services/admin_service/transports/grpc_asyncio.py index 03ffcea5..f35deee6 100644 --- a/google/cloud/pubsublite_v1/services/admin_service/transports/grpc_asyncio.py +++ b/google/cloud/pubsublite_v1/services/admin_service/transports/grpc_asyncio.py @@ -105,6 +105,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -136,6 +137,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -152,6 +157,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -161,11 +171,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -195,6 +200,10 @@ def __init__( ssl_credentials=ssl_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._ssl_channel_credentials = ssl_credentials else: @@ -205,14 +214,24 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) # Run the base constructor. diff --git a/google/cloud/pubsublite_v1/services/cursor_service/async_client.py b/google/cloud/pubsublite_v1/services/cursor_service/async_client.py index 8ad4b2ec..00077822 100644 --- a/google/cloud/pubsublite_v1/services/cursor_service/async_client.py +++ b/google/cloud/pubsublite_v1/services/cursor_service/async_client.py @@ -89,7 +89,36 @@ class CursorServiceAsyncClient: CursorServiceClient.parse_common_location_path ) - from_service_account_file = CursorServiceClient.from_service_account_file + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + CursorServiceAsyncClient: The constructed client. + """ + return CursorServiceClient.from_service_account_info.__func__(CursorServiceAsyncClient, info, *args, **kwargs) # type: ignore + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + CursorServiceAsyncClient: The constructed client. + """ + return CursorServiceClient.from_service_account_file.__func__(CursorServiceAsyncClient, filename, *args, **kwargs) # type: ignore + from_service_account_json = from_service_account_file @property @@ -165,7 +194,7 @@ def streaming_commit_cursor( committed cursors. Args: - requests (AsyncIterator[`~.cursor.StreamingCommitCursorRequest`]): + requests (AsyncIterator[`google.cloud.pubsublite_v1.types.StreamingCommitCursorRequest`]): The request object AsyncIterator. A request sent from the client to the server on a stream. retry (google.api_core.retry.Retry): Designation of what errors, if any, @@ -175,7 +204,7 @@ def streaming_commit_cursor( sent along with the request as metadata. Returns: - AsyncIterable[~.cursor.StreamingCommitCursorResponse]: + AsyncIterable[google.cloud.pubsublite_v1.types.StreamingCommitCursorResponse]: Response to a StreamingCommitCursorRequest. @@ -206,7 +235,7 @@ async def commit_cursor( r"""Updates the committed cursor. Args: - request (:class:`~.cursor.CommitCursorRequest`): + request (:class:`google.cloud.pubsublite_v1.types.CommitCursorRequest`): The request object. Request for CommitCursor. retry (google.api_core.retry.Retry): Designation of what errors, if any, @@ -216,7 +245,7 @@ async def commit_cursor( sent along with the request as metadata. Returns: - ~.cursor.CommitCursorResponse: + google.cloud.pubsublite_v1.types.CommitCursorResponse: Response for CommitCursor. """ # Create or coerce a protobuf request object. @@ -262,12 +291,13 @@ async def list_partition_cursors( subscription. Args: - request (:class:`~.cursor.ListPartitionCursorsRequest`): + request (:class:`google.cloud.pubsublite_v1.types.ListPartitionCursorsRequest`): The request object. Request for ListPartitionCursors. parent (:class:`str`): Required. The subscription for which to retrieve cursors. Structured like ``projects/{project_number}/locations/{location}/subscriptions/{subscription_id}``. + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -279,7 +309,7 @@ async def list_partition_cursors( sent along with the request as metadata. Returns: - ~.pagers.ListPartitionCursorsAsyncPager: + google.cloud.pubsublite_v1.services.cursor_service.pagers.ListPartitionCursorsAsyncPager: Response for ListPartitionCursors Iterating over this object will yield results and resolve additional pages diff --git a/google/cloud/pubsublite_v1/services/cursor_service/client.py b/google/cloud/pubsublite_v1/services/cursor_service/client.py index e0f89a2a..0f107803 100644 --- a/google/cloud/pubsublite_v1/services/cursor_service/client.py +++ b/google/cloud/pubsublite_v1/services/cursor_service/client.py @@ -122,6 +122,22 @@ def _get_default_mtls_endpoint(api_endpoint): DEFAULT_ENDPOINT ) + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + CursorServiceClient: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_info(info) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + @classmethod def from_service_account_file(cls, filename: str, *args, **kwargs): """Creates an instance of this client using the provided credentials @@ -134,7 +150,7 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): kwargs: Additional arguments to pass to the constructor. Returns: - {@api.name}: The constructed client. + CursorServiceClient: The constructed client. """ credentials = service_account.Credentials.from_service_account_file(filename) kwargs["credentials"] = credentials @@ -242,10 +258,10 @@ def __init__( credentials identify the application to the service; if none are specified, the client will attempt to ascertain the credentials from the environment. - transport (Union[str, ~.CursorServiceTransport]): The + transport (Union[str, CursorServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (client_options_lib.ClientOptions): Custom options for the + client_options (google.api_core.client_options.ClientOptions): Custom options for the client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT @@ -281,21 +297,17 @@ def __init__( util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) ) - ssl_credentials = None + client_cert_source_func = None is_mtls = False if use_client_cert: if client_options.client_cert_source: - import grpc # type: ignore - - cert, key = client_options.client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) is_mtls = True + client_cert_source_func = client_options.client_cert_source else: - creds = SslCredentials() - is_mtls = creds.is_mtls - ssl_credentials = creds.ssl_credentials if is_mtls else None + is_mtls = mtls.has_default_client_cert_source() + client_cert_source_func = ( + mtls.default_client_cert_source() if is_mtls else None + ) # Figure out which api endpoint to use. if client_options.api_endpoint is not None: @@ -338,7 +350,7 @@ def __init__( credentials_file=client_options.credentials_file, host=api_endpoint, scopes=client_options.scopes, - ssl_channel_credentials=ssl_credentials, + client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, ) @@ -355,7 +367,7 @@ def streaming_commit_cursor( committed cursors. Args: - requests (Iterator[`~.cursor.StreamingCommitCursorRequest`]): + requests (Iterator[google.cloud.pubsublite_v1.types.StreamingCommitCursorRequest]): The request object iterator. A request sent from the client to the server on a stream. retry (google.api_core.retry.Retry): Designation of what errors, if any, @@ -365,7 +377,7 @@ def streaming_commit_cursor( sent along with the request as metadata. Returns: - Iterable[~.cursor.StreamingCommitCursorResponse]: + Iterable[google.cloud.pubsublite_v1.types.StreamingCommitCursorResponse]: Response to a StreamingCommitCursorRequest. @@ -392,7 +404,7 @@ def commit_cursor( r"""Updates the committed cursor. Args: - request (:class:`~.cursor.CommitCursorRequest`): + request (google.cloud.pubsublite_v1.types.CommitCursorRequest): The request object. Request for CommitCursor. retry (google.api_core.retry.Retry): Designation of what errors, if any, @@ -402,7 +414,7 @@ def commit_cursor( sent along with the request as metadata. Returns: - ~.cursor.CommitCursorResponse: + google.cloud.pubsublite_v1.types.CommitCursorResponse: Response for CommitCursor. """ # Create or coerce a protobuf request object. @@ -437,12 +449,13 @@ def list_partition_cursors( subscription. Args: - request (:class:`~.cursor.ListPartitionCursorsRequest`): + request (google.cloud.pubsublite_v1.types.ListPartitionCursorsRequest): The request object. Request for ListPartitionCursors. - parent (:class:`str`): + parent (str): Required. The subscription for which to retrieve cursors. Structured like ``projects/{project_number}/locations/{location}/subscriptions/{subscription_id}``. + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -454,7 +467,7 @@ def list_partition_cursors( sent along with the request as metadata. Returns: - ~.pagers.ListPartitionCursorsPager: + google.cloud.pubsublite_v1.services.cursor_service.pagers.ListPartitionCursorsPager: Response for ListPartitionCursors Iterating over this object will yield results and resolve additional pages diff --git a/google/cloud/pubsublite_v1/services/cursor_service/pagers.py b/google/cloud/pubsublite_v1/services/cursor_service/pagers.py index be5b3744..87d02908 100644 --- a/google/cloud/pubsublite_v1/services/cursor_service/pagers.py +++ b/google/cloud/pubsublite_v1/services/cursor_service/pagers.py @@ -15,7 +15,16 @@ # limitations under the License. # -from typing import Any, AsyncIterable, Awaitable, Callable, Iterable, Sequence, Tuple +from typing import ( + Any, + AsyncIterable, + Awaitable, + Callable, + Iterable, + Sequence, + Tuple, + Optional, +) from google.cloud.pubsublite_v1.types import cursor @@ -24,7 +33,7 @@ class ListPartitionCursorsPager: """A pager for iterating through ``list_partition_cursors`` requests. This class thinly wraps an initial - :class:`~.cursor.ListPartitionCursorsResponse` object, and + :class:`google.cloud.pubsublite_v1.types.ListPartitionCursorsResponse` object, and provides an ``__iter__`` method to iterate through its ``partition_cursors`` field. @@ -33,7 +42,7 @@ class ListPartitionCursorsPager: through the ``partition_cursors`` field on the corresponding responses. - All the usual :class:`~.cursor.ListPartitionCursorsResponse` + All the usual :class:`google.cloud.pubsublite_v1.types.ListPartitionCursorsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -51,9 +60,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.cursor.ListPartitionCursorsRequest`): + request (google.cloud.pubsublite_v1.types.ListPartitionCursorsRequest): The initial request object. - response (:class:`~.cursor.ListPartitionCursorsResponse`): + response (google.cloud.pubsublite_v1.types.ListPartitionCursorsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. @@ -86,7 +95,7 @@ class ListPartitionCursorsAsyncPager: """A pager for iterating through ``list_partition_cursors`` requests. This class thinly wraps an initial - :class:`~.cursor.ListPartitionCursorsResponse` object, and + :class:`google.cloud.pubsublite_v1.types.ListPartitionCursorsResponse` object, and provides an ``__aiter__`` method to iterate through its ``partition_cursors`` field. @@ -95,7 +104,7 @@ class ListPartitionCursorsAsyncPager: through the ``partition_cursors`` field on the corresponding responses. - All the usual :class:`~.cursor.ListPartitionCursorsResponse` + All the usual :class:`google.cloud.pubsublite_v1.types.ListPartitionCursorsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -113,9 +122,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.cursor.ListPartitionCursorsRequest`): + request (google.cloud.pubsublite_v1.types.ListPartitionCursorsRequest): The initial request object. - response (:class:`~.cursor.ListPartitionCursorsResponse`): + response (google.cloud.pubsublite_v1.types.ListPartitionCursorsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. diff --git a/google/cloud/pubsublite_v1/services/cursor_service/transports/__init__.py b/google/cloud/pubsublite_v1/services/cursor_service/transports/__init__.py index cee721b1..16fd507b 100644 --- a/google/cloud/pubsublite_v1/services/cursor_service/transports/__init__.py +++ b/google/cloud/pubsublite_v1/services/cursor_service/transports/__init__.py @@ -28,7 +28,6 @@ _transport_registry["grpc"] = CursorServiceGrpcTransport _transport_registry["grpc_asyncio"] = CursorServiceGrpcAsyncIOTransport - __all__ = ( "CursorServiceTransport", "CursorServiceGrpcTransport", diff --git a/google/cloud/pubsublite_v1/services/cursor_service/transports/grpc.py b/google/cloud/pubsublite_v1/services/cursor_service/transports/grpc.py index cd45da36..c2a7c3ee 100644 --- a/google/cloud/pubsublite_v1/services/cursor_service/transports/grpc.py +++ b/google/cloud/pubsublite_v1/services/cursor_service/transports/grpc.py @@ -60,6 +60,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -90,6 +91,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -106,6 +111,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -115,11 +125,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -149,6 +154,10 @@ def __init__( ssl_credentials=ssl_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._ssl_channel_credentials = ssl_credentials else: @@ -159,14 +168,24 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._stubs = {} # type: Dict[str, Callable] @@ -193,7 +212,7 @@ def create_channel( ) -> grpc.Channel: """Create and return a gRPC channel object. Args: - address (Optionsl[str]): The host for the channel to use. + address (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If diff --git a/google/cloud/pubsublite_v1/services/cursor_service/transports/grpc_asyncio.py b/google/cloud/pubsublite_v1/services/cursor_service/transports/grpc_asyncio.py index 58b5d08b..9a4902f1 100644 --- a/google/cloud/pubsublite_v1/services/cursor_service/transports/grpc_asyncio.py +++ b/google/cloud/pubsublite_v1/services/cursor_service/transports/grpc_asyncio.py @@ -104,6 +104,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -135,6 +136,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -151,6 +156,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -160,11 +170,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -194,6 +199,10 @@ def __init__( ssl_credentials=ssl_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._ssl_channel_credentials = ssl_credentials else: @@ -204,14 +213,24 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) # Run the base constructor. diff --git a/google/cloud/pubsublite_v1/services/partition_assignment_service/async_client.py b/google/cloud/pubsublite_v1/services/partition_assignment_service/async_client.py index 37084819..1f014b21 100644 --- a/google/cloud/pubsublite_v1/services/partition_assignment_service/async_client.py +++ b/google/cloud/pubsublite_v1/services/partition_assignment_service/async_client.py @@ -89,9 +89,36 @@ class PartitionAssignmentServiceAsyncClient: PartitionAssignmentServiceClient.parse_common_location_path ) - from_service_account_file = ( - PartitionAssignmentServiceClient.from_service_account_file - ) + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + PartitionAssignmentServiceAsyncClient: The constructed client. + """ + return PartitionAssignmentServiceClient.from_service_account_info.__func__(PartitionAssignmentServiceAsyncClient, info, *args, **kwargs) # type: ignore + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + PartitionAssignmentServiceAsyncClient: The constructed client. + """ + return PartitionAssignmentServiceClient.from_service_account_file.__func__(PartitionAssignmentServiceAsyncClient, filename, *args, **kwargs) # type: ignore + from_service_account_json = from_service_account_file @property @@ -175,7 +202,7 @@ def assign_partitions( the new assignment. Args: - requests (AsyncIterator[`~.subscriber.PartitionAssignmentRequest`]): + requests (AsyncIterator[`google.cloud.pubsublite_v1.types.PartitionAssignmentRequest`]): The request object AsyncIterator. A request on the PartitionAssignment stream. retry (google.api_core.retry.Retry): Designation of what errors, if any, @@ -185,7 +212,7 @@ def assign_partitions( sent along with the request as metadata. Returns: - AsyncIterable[~.subscriber.PartitionAssignment]: + AsyncIterable[google.cloud.pubsublite_v1.types.PartitionAssignment]: PartitionAssignments should not race with acknowledgements. There should be exactly one unacknowledged diff --git a/google/cloud/pubsublite_v1/services/partition_assignment_service/client.py b/google/cloud/pubsublite_v1/services/partition_assignment_service/client.py index adf1095f..91aa5dbe 100644 --- a/google/cloud/pubsublite_v1/services/partition_assignment_service/client.py +++ b/google/cloud/pubsublite_v1/services/partition_assignment_service/client.py @@ -123,6 +123,22 @@ def _get_default_mtls_endpoint(api_endpoint): DEFAULT_ENDPOINT ) + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + PartitionAssignmentServiceClient: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_info(info) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + @classmethod def from_service_account_file(cls, filename: str, *args, **kwargs): """Creates an instance of this client using the provided credentials @@ -135,7 +151,7 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): kwargs: Additional arguments to pass to the constructor. Returns: - {@api.name}: The constructed client. + PartitionAssignmentServiceClient: The constructed client. """ credentials = service_account.Credentials.from_service_account_file(filename) kwargs["credentials"] = credentials @@ -227,10 +243,10 @@ def __init__( credentials identify the application to the service; if none are specified, the client will attempt to ascertain the credentials from the environment. - transport (Union[str, ~.PartitionAssignmentServiceTransport]): The + transport (Union[str, PartitionAssignmentServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (client_options_lib.ClientOptions): Custom options for the + client_options (google.api_core.client_options.ClientOptions): Custom options for the client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT @@ -266,21 +282,17 @@ def __init__( util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) ) - ssl_credentials = None + client_cert_source_func = None is_mtls = False if use_client_cert: if client_options.client_cert_source: - import grpc # type: ignore - - cert, key = client_options.client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) is_mtls = True + client_cert_source_func = client_options.client_cert_source else: - creds = SslCredentials() - is_mtls = creds.is_mtls - ssl_credentials = creds.ssl_credentials if is_mtls else None + is_mtls = mtls.has_default_client_cert_source() + client_cert_source_func = ( + mtls.default_client_cert_source() if is_mtls else None + ) # Figure out which api endpoint to use. if client_options.api_endpoint is not None: @@ -323,7 +335,7 @@ def __init__( credentials_file=client_options.credentials_file, host=api_endpoint, scopes=client_options.scopes, - ssl_channel_credentials=ssl_credentials, + client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, ) @@ -347,7 +359,7 @@ def assign_partitions( the new assignment. Args: - requests (Iterator[`~.subscriber.PartitionAssignmentRequest`]): + requests (Iterator[google.cloud.pubsublite_v1.types.PartitionAssignmentRequest]): The request object iterator. A request on the PartitionAssignment stream. retry (google.api_core.retry.Retry): Designation of what errors, if any, @@ -357,7 +369,7 @@ def assign_partitions( sent along with the request as metadata. Returns: - Iterable[~.subscriber.PartitionAssignment]: + Iterable[google.cloud.pubsublite_v1.types.PartitionAssignment]: PartitionAssignments should not race with acknowledgements. There should be exactly one unacknowledged diff --git a/google/cloud/pubsublite_v1/services/partition_assignment_service/transports/__init__.py b/google/cloud/pubsublite_v1/services/partition_assignment_service/transports/__init__.py index 29c8d8be..10ec3e4b 100644 --- a/google/cloud/pubsublite_v1/services/partition_assignment_service/transports/__init__.py +++ b/google/cloud/pubsublite_v1/services/partition_assignment_service/transports/__init__.py @@ -30,7 +30,6 @@ _transport_registry["grpc"] = PartitionAssignmentServiceGrpcTransport _transport_registry["grpc_asyncio"] = PartitionAssignmentServiceGrpcAsyncIOTransport - __all__ = ( "PartitionAssignmentServiceTransport", "PartitionAssignmentServiceGrpcTransport", diff --git a/google/cloud/pubsublite_v1/services/partition_assignment_service/transports/grpc.py b/google/cloud/pubsublite_v1/services/partition_assignment_service/transports/grpc.py index 4fb66ce3..5b9cbad7 100644 --- a/google/cloud/pubsublite_v1/services/partition_assignment_service/transports/grpc.py +++ b/google/cloud/pubsublite_v1/services/partition_assignment_service/transports/grpc.py @@ -58,6 +58,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -88,6 +89,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -104,6 +109,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -113,11 +123,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -147,6 +152,10 @@ def __init__( ssl_credentials=ssl_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._ssl_channel_credentials = ssl_credentials else: @@ -157,14 +166,24 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._stubs = {} # type: Dict[str, Callable] @@ -191,7 +210,7 @@ def create_channel( ) -> grpc.Channel: """Create and return a gRPC channel object. Args: - address (Optionsl[str]): The host for the channel to use. + address (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If diff --git a/google/cloud/pubsublite_v1/services/partition_assignment_service/transports/grpc_asyncio.py b/google/cloud/pubsublite_v1/services/partition_assignment_service/transports/grpc_asyncio.py index 7a6b1cf0..5b9674ea 100644 --- a/google/cloud/pubsublite_v1/services/partition_assignment_service/transports/grpc_asyncio.py +++ b/google/cloud/pubsublite_v1/services/partition_assignment_service/transports/grpc_asyncio.py @@ -104,6 +104,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -135,6 +136,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -151,6 +156,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -160,11 +170,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -194,6 +199,10 @@ def __init__( ssl_credentials=ssl_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._ssl_channel_credentials = ssl_credentials else: @@ -204,14 +213,24 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) # Run the base constructor. diff --git a/google/cloud/pubsublite_v1/services/publisher_service/async_client.py b/google/cloud/pubsublite_v1/services/publisher_service/async_client.py index 57c02aa3..901b2fdb 100644 --- a/google/cloud/pubsublite_v1/services/publisher_service/async_client.py +++ b/google/cloud/pubsublite_v1/services/publisher_service/async_client.py @@ -86,7 +86,36 @@ class PublisherServiceAsyncClient: PublisherServiceClient.parse_common_location_path ) - from_service_account_file = PublisherServiceClient.from_service_account_file + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + PublisherServiceAsyncClient: The constructed client. + """ + return PublisherServiceClient.from_service_account_info.__func__(PublisherServiceAsyncClient, info, *args, **kwargs) # type: ignore + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + PublisherServiceAsyncClient: The constructed client. + """ + return PublisherServiceClient.from_service_account_file.__func__(PublisherServiceAsyncClient, filename, *args, **kwargs) # type: ignore + from_service_account_json = from_service_account_file @property @@ -169,7 +198,7 @@ def publish( they are sent by the client on a given stream. Args: - requests (AsyncIterator[`~.publisher.PublishRequest`]): + requests (AsyncIterator[`google.cloud.pubsublite_v1.types.PublishRequest`]): The request object AsyncIterator. Request sent from the client to the server on a stream. retry (google.api_core.retry.Retry): Designation of what errors, if any, @@ -179,7 +208,7 @@ def publish( sent along with the request as metadata. Returns: - AsyncIterable[~.publisher.PublishResponse]: + AsyncIterable[google.cloud.pubsublite_v1.types.PublishResponse]: Response to a PublishRequest. """ diff --git a/google/cloud/pubsublite_v1/services/publisher_service/client.py b/google/cloud/pubsublite_v1/services/publisher_service/client.py index 1d55b422..202c0782 100644 --- a/google/cloud/pubsublite_v1/services/publisher_service/client.py +++ b/google/cloud/pubsublite_v1/services/publisher_service/client.py @@ -124,6 +124,22 @@ def _get_default_mtls_endpoint(api_endpoint): DEFAULT_ENDPOINT ) + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + PublisherServiceClient: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_info(info) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + @classmethod def from_service_account_file(cls, filename: str, *args, **kwargs): """Creates an instance of this client using the provided credentials @@ -136,7 +152,7 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): kwargs: Additional arguments to pass to the constructor. Returns: - {@api.name}: The constructed client. + PublisherServiceClient: The constructed client. """ credentials = service_account.Credentials.from_service_account_file(filename) kwargs["credentials"] = credentials @@ -228,10 +244,10 @@ def __init__( credentials identify the application to the service; if none are specified, the client will attempt to ascertain the credentials from the environment. - transport (Union[str, ~.PublisherServiceTransport]): The + transport (Union[str, PublisherServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (client_options_lib.ClientOptions): Custom options for the + client_options (google.api_core.client_options.ClientOptions): Custom options for the client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT @@ -267,21 +283,17 @@ def __init__( util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) ) - ssl_credentials = None + client_cert_source_func = None is_mtls = False if use_client_cert: if client_options.client_cert_source: - import grpc # type: ignore - - cert, key = client_options.client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) is_mtls = True + client_cert_source_func = client_options.client_cert_source else: - creds = SslCredentials() - is_mtls = creds.is_mtls - ssl_credentials = creds.ssl_credentials if is_mtls else None + is_mtls = mtls.has_default_client_cert_source() + client_cert_source_func = ( + mtls.default_client_cert_source() if is_mtls else None + ) # Figure out which api endpoint to use. if client_options.api_endpoint is not None: @@ -324,7 +336,7 @@ def __init__( credentials_file=client_options.credentials_file, host=api_endpoint, scopes=client_options.scopes, - ssl_channel_credentials=ssl_credentials, + client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, ) @@ -348,7 +360,7 @@ def publish( they are sent by the client on a given stream. Args: - requests (Iterator[`~.publisher.PublishRequest`]): + requests (Iterator[google.cloud.pubsublite_v1.types.PublishRequest]): The request object iterator. Request sent from the client to the server on a stream. retry (google.api_core.retry.Retry): Designation of what errors, if any, @@ -358,7 +370,7 @@ def publish( sent along with the request as metadata. Returns: - Iterable[~.publisher.PublishResponse]: + Iterable[google.cloud.pubsublite_v1.types.PublishResponse]: Response to a PublishRequest. """ diff --git a/google/cloud/pubsublite_v1/services/publisher_service/transports/__init__.py b/google/cloud/pubsublite_v1/services/publisher_service/transports/__init__.py index f46d8c16..485b0b7c 100644 --- a/google/cloud/pubsublite_v1/services/publisher_service/transports/__init__.py +++ b/google/cloud/pubsublite_v1/services/publisher_service/transports/__init__.py @@ -28,7 +28,6 @@ _transport_registry["grpc"] = PublisherServiceGrpcTransport _transport_registry["grpc_asyncio"] = PublisherServiceGrpcAsyncIOTransport - __all__ = ( "PublisherServiceTransport", "PublisherServiceGrpcTransport", diff --git a/google/cloud/pubsublite_v1/services/publisher_service/transports/grpc.py b/google/cloud/pubsublite_v1/services/publisher_service/transports/grpc.py index 553e89cc..bd6b44ac 100644 --- a/google/cloud/pubsublite_v1/services/publisher_service/transports/grpc.py +++ b/google/cloud/pubsublite_v1/services/publisher_service/transports/grpc.py @@ -61,6 +61,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -91,6 +92,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -107,6 +112,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -116,11 +126,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -150,6 +155,10 @@ def __init__( ssl_credentials=ssl_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._ssl_channel_credentials = ssl_credentials else: @@ -160,14 +169,24 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._stubs = {} # type: Dict[str, Callable] @@ -194,7 +213,7 @@ def create_channel( ) -> grpc.Channel: """Create and return a gRPC channel object. Args: - address (Optionsl[str]): The host for the channel to use. + address (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If diff --git a/google/cloud/pubsublite_v1/services/publisher_service/transports/grpc_asyncio.py b/google/cloud/pubsublite_v1/services/publisher_service/transports/grpc_asyncio.py index 783353c6..a0fa51b7 100644 --- a/google/cloud/pubsublite_v1/services/publisher_service/transports/grpc_asyncio.py +++ b/google/cloud/pubsublite_v1/services/publisher_service/transports/grpc_asyncio.py @@ -105,6 +105,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -136,6 +137,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -152,6 +157,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -161,11 +171,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -195,6 +200,10 @@ def __init__( ssl_credentials=ssl_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._ssl_channel_credentials = ssl_credentials else: @@ -205,14 +214,24 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) # Run the base constructor. diff --git a/google/cloud/pubsublite_v1/services/subscriber_service/async_client.py b/google/cloud/pubsublite_v1/services/subscriber_service/async_client.py index 64cf2318..fa2c66b5 100644 --- a/google/cloud/pubsublite_v1/services/subscriber_service/async_client.py +++ b/google/cloud/pubsublite_v1/services/subscriber_service/async_client.py @@ -83,7 +83,36 @@ class SubscriberServiceAsyncClient: SubscriberServiceClient.parse_common_location_path ) - from_service_account_file = SubscriberServiceClient.from_service_account_file + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + SubscriberServiceAsyncClient: The constructed client. + """ + return SubscriberServiceClient.from_service_account_info.__func__(SubscriberServiceAsyncClient, info, *args, **kwargs) # type: ignore + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + SubscriberServiceAsyncClient: The constructed client. + """ + return SubscriberServiceClient.from_service_account_file.__func__(SubscriberServiceAsyncClient, filename, *args, **kwargs) # type: ignore + from_service_account_json = from_service_account_file @property @@ -159,7 +188,7 @@ def subscribe( messages. Args: - requests (AsyncIterator[`~.subscriber.SubscribeRequest`]): + requests (AsyncIterator[`google.cloud.pubsublite_v1.types.SubscribeRequest`]): The request object AsyncIterator. A request sent from the client to the server on a stream. retry (google.api_core.retry.Retry): Designation of what errors, if any, @@ -169,7 +198,7 @@ def subscribe( sent along with the request as metadata. Returns: - AsyncIterable[~.subscriber.SubscribeResponse]: + AsyncIterable[google.cloud.pubsublite_v1.types.SubscribeResponse]: Response to SubscribeRequest. """ diff --git a/google/cloud/pubsublite_v1/services/subscriber_service/client.py b/google/cloud/pubsublite_v1/services/subscriber_service/client.py index 8f1b5a3c..6fc767ad 100644 --- a/google/cloud/pubsublite_v1/services/subscriber_service/client.py +++ b/google/cloud/pubsublite_v1/services/subscriber_service/client.py @@ -123,6 +123,22 @@ def _get_default_mtls_endpoint(api_endpoint): DEFAULT_ENDPOINT ) + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + SubscriberServiceClient: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_info(info) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + @classmethod def from_service_account_file(cls, filename: str, *args, **kwargs): """Creates an instance of this client using the provided credentials @@ -135,7 +151,7 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): kwargs: Additional arguments to pass to the constructor. Returns: - {@api.name}: The constructed client. + SubscriberServiceClient: The constructed client. """ credentials = service_account.Credentials.from_service_account_file(filename) kwargs["credentials"] = credentials @@ -227,10 +243,10 @@ def __init__( credentials identify the application to the service; if none are specified, the client will attempt to ascertain the credentials from the environment. - transport (Union[str, ~.SubscriberServiceTransport]): The + transport (Union[str, SubscriberServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (client_options_lib.ClientOptions): Custom options for the + client_options (google.api_core.client_options.ClientOptions): Custom options for the client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT @@ -266,21 +282,17 @@ def __init__( util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) ) - ssl_credentials = None + client_cert_source_func = None is_mtls = False if use_client_cert: if client_options.client_cert_source: - import grpc # type: ignore - - cert, key = client_options.client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) is_mtls = True + client_cert_source_func = client_options.client_cert_source else: - creds = SslCredentials() - is_mtls = creds.is_mtls - ssl_credentials = creds.ssl_credentials if is_mtls else None + is_mtls = mtls.has_default_client_cert_source() + client_cert_source_func = ( + mtls.default_client_cert_source() if is_mtls else None + ) # Figure out which api endpoint to use. if client_options.api_endpoint is not None: @@ -323,7 +335,7 @@ def __init__( credentials_file=client_options.credentials_file, host=api_endpoint, scopes=client_options.scopes, - ssl_channel_credentials=ssl_credentials, + client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, ) @@ -340,7 +352,7 @@ def subscribe( messages. Args: - requests (Iterator[`~.subscriber.SubscribeRequest`]): + requests (Iterator[google.cloud.pubsublite_v1.types.SubscribeRequest]): The request object iterator. A request sent from the client to the server on a stream. retry (google.api_core.retry.Retry): Designation of what errors, if any, @@ -350,7 +362,7 @@ def subscribe( sent along with the request as metadata. Returns: - Iterable[~.subscriber.SubscribeResponse]: + Iterable[google.cloud.pubsublite_v1.types.SubscribeResponse]: Response to SubscribeRequest. """ diff --git a/google/cloud/pubsublite_v1/services/subscriber_service/transports/__init__.py b/google/cloud/pubsublite_v1/services/subscriber_service/transports/__init__.py index cd8b0bd2..a1da8b59 100644 --- a/google/cloud/pubsublite_v1/services/subscriber_service/transports/__init__.py +++ b/google/cloud/pubsublite_v1/services/subscriber_service/transports/__init__.py @@ -28,7 +28,6 @@ _transport_registry["grpc"] = SubscriberServiceGrpcTransport _transport_registry["grpc_asyncio"] = SubscriberServiceGrpcAsyncIOTransport - __all__ = ( "SubscriberServiceTransport", "SubscriberServiceGrpcTransport", diff --git a/google/cloud/pubsublite_v1/services/subscriber_service/transports/grpc.py b/google/cloud/pubsublite_v1/services/subscriber_service/transports/grpc.py index aaaaeb3d..87f56ced 100644 --- a/google/cloud/pubsublite_v1/services/subscriber_service/transports/grpc.py +++ b/google/cloud/pubsublite_v1/services/subscriber_service/transports/grpc.py @@ -58,6 +58,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -88,6 +89,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -104,6 +109,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -113,11 +123,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -147,6 +152,10 @@ def __init__( ssl_credentials=ssl_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._ssl_channel_credentials = ssl_credentials else: @@ -157,14 +166,24 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._stubs = {} # type: Dict[str, Callable] @@ -191,7 +210,7 @@ def create_channel( ) -> grpc.Channel: """Create and return a gRPC channel object. Args: - address (Optionsl[str]): The host for the channel to use. + address (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If diff --git a/google/cloud/pubsublite_v1/services/subscriber_service/transports/grpc_asyncio.py b/google/cloud/pubsublite_v1/services/subscriber_service/transports/grpc_asyncio.py index 5f51cdcb..71f6a9a4 100644 --- a/google/cloud/pubsublite_v1/services/subscriber_service/transports/grpc_asyncio.py +++ b/google/cloud/pubsublite_v1/services/subscriber_service/transports/grpc_asyncio.py @@ -102,6 +102,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -133,6 +134,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -149,6 +154,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -158,11 +168,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -192,6 +197,10 @@ def __init__( ssl_credentials=ssl_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._ssl_channel_credentials = ssl_credentials else: @@ -202,14 +211,24 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) # Run the base constructor. diff --git a/google/cloud/pubsublite_v1/services/topic_stats_service/async_client.py b/google/cloud/pubsublite_v1/services/topic_stats_service/async_client.py index 21ce7f6f..1b8cf299 100644 --- a/google/cloud/pubsublite_v1/services/topic_stats_service/async_client.py +++ b/google/cloud/pubsublite_v1/services/topic_stats_service/async_client.py @@ -28,6 +28,7 @@ from google.auth import credentials # type: ignore from google.oauth2 import service_account # type: ignore +from google.cloud.pubsublite_v1.types import common from google.cloud.pubsublite_v1.types import topic_stats from google.protobuf import timestamp_pb2 as timestamp # type: ignore @@ -78,7 +79,36 @@ class TopicStatsServiceAsyncClient: TopicStatsServiceClient.parse_common_location_path ) - from_service_account_file = TopicStatsServiceClient.from_service_account_file + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + TopicStatsServiceAsyncClient: The constructed client. + """ + return TopicStatsServiceClient.from_service_account_info.__func__(TopicStatsServiceAsyncClient, info, *args, **kwargs) # type: ignore + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + TopicStatsServiceAsyncClient: The constructed client. + """ + return TopicStatsServiceClient.from_service_account_file.__func__(TopicStatsServiceAsyncClient, filename, *args, **kwargs) # type: ignore + from_service_account_json = from_service_account_file @property @@ -154,7 +184,7 @@ async def compute_message_stats( given topic and partition. Args: - request (:class:`~.topic_stats.ComputeMessageStatsRequest`): + request (:class:`google.cloud.pubsublite_v1.types.ComputeMessageStatsRequest`): The request object. Compute statistics about a range of messages in a given topic and partition. @@ -165,7 +195,7 @@ async def compute_message_stats( sent along with the request as metadata. Returns: - ~.topic_stats.ComputeMessageStatsResponse: + google.cloud.pubsublite_v1.types.ComputeMessageStatsResponse: Response containing stats for messages in the requested topic and partition. @@ -207,6 +237,63 @@ async def compute_message_stats( # Done; return the response. return response + async def compute_head_cursor( + self, + request: topic_stats.ComputeHeadCursorRequest = None, + *, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> topic_stats.ComputeHeadCursorResponse: + r"""Compute the head cursor for the partition. + The head cursor's offset is guaranteed to be less than + or equal to all messages which have not yet been + acknowledged as published, and greater than the offset + of any message whose publish has already been + acknowledged. It is zero if there have never been + messages in the partition. + + Args: + request (:class:`google.cloud.pubsublite_v1.types.ComputeHeadCursorRequest`): + The request object. Compute the current head cursor for + a partition. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + google.cloud.pubsublite_v1.types.ComputeHeadCursorResponse: + Response containing the head cursor + for the requested topic and partition. + + """ + # Create or coerce a protobuf request object. + + request = topic_stats.ComputeHeadCursorRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.compute_head_cursor, + default_timeout=None, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("topic", request.topic),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + try: DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( diff --git a/google/cloud/pubsublite_v1/services/topic_stats_service/client.py b/google/cloud/pubsublite_v1/services/topic_stats_service/client.py index a66963fe..57018295 100644 --- a/google/cloud/pubsublite_v1/services/topic_stats_service/client.py +++ b/google/cloud/pubsublite_v1/services/topic_stats_service/client.py @@ -32,6 +32,7 @@ from google.auth.exceptions import MutualTLSChannelError # type: ignore from google.oauth2 import service_account # type: ignore +from google.cloud.pubsublite_v1.types import common from google.cloud.pubsublite_v1.types import topic_stats from google.protobuf import timestamp_pb2 as timestamp # type: ignore @@ -114,6 +115,22 @@ def _get_default_mtls_endpoint(api_endpoint): DEFAULT_ENDPOINT ) + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + TopicStatsServiceClient: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_info(info) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + @classmethod def from_service_account_file(cls, filename: str, *args, **kwargs): """Creates an instance of this client using the provided credentials @@ -126,7 +143,7 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): kwargs: Additional arguments to pass to the constructor. Returns: - {@api.name}: The constructed client. + TopicStatsServiceClient: The constructed client. """ credentials = service_account.Credentials.from_service_account_file(filename) kwargs["credentials"] = credentials @@ -234,10 +251,10 @@ def __init__( credentials identify the application to the service; if none are specified, the client will attempt to ascertain the credentials from the environment. - transport (Union[str, ~.TopicStatsServiceTransport]): The + transport (Union[str, TopicStatsServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (client_options_lib.ClientOptions): Custom options for the + client_options (google.api_core.client_options.ClientOptions): Custom options for the client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT @@ -273,21 +290,17 @@ def __init__( util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) ) - ssl_credentials = None + client_cert_source_func = None is_mtls = False if use_client_cert: if client_options.client_cert_source: - import grpc # type: ignore - - cert, key = client_options.client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) is_mtls = True + client_cert_source_func = client_options.client_cert_source else: - creds = SslCredentials() - is_mtls = creds.is_mtls - ssl_credentials = creds.ssl_credentials if is_mtls else None + is_mtls = mtls.has_default_client_cert_source() + client_cert_source_func = ( + mtls.default_client_cert_source() if is_mtls else None + ) # Figure out which api endpoint to use. if client_options.api_endpoint is not None: @@ -330,7 +343,7 @@ def __init__( credentials_file=client_options.credentials_file, host=api_endpoint, scopes=client_options.scopes, - ssl_channel_credentials=ssl_credentials, + client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, ) @@ -347,7 +360,7 @@ def compute_message_stats( given topic and partition. Args: - request (:class:`~.topic_stats.ComputeMessageStatsRequest`): + request (google.cloud.pubsublite_v1.types.ComputeMessageStatsRequest): The request object. Compute statistics about a range of messages in a given topic and partition. @@ -358,7 +371,7 @@ def compute_message_stats( sent along with the request as metadata. Returns: - ~.topic_stats.ComputeMessageStatsResponse: + google.cloud.pubsublite_v1.types.ComputeMessageStatsResponse: Response containing stats for messages in the requested topic and partition. @@ -389,6 +402,64 @@ def compute_message_stats( # Done; return the response. return response + def compute_head_cursor( + self, + request: topic_stats.ComputeHeadCursorRequest = None, + *, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> topic_stats.ComputeHeadCursorResponse: + r"""Compute the head cursor for the partition. + The head cursor's offset is guaranteed to be less than + or equal to all messages which have not yet been + acknowledged as published, and greater than the offset + of any message whose publish has already been + acknowledged. It is zero if there have never been + messages in the partition. + + Args: + request (google.cloud.pubsublite_v1.types.ComputeHeadCursorRequest): + The request object. Compute the current head cursor for + a partition. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + google.cloud.pubsublite_v1.types.ComputeHeadCursorResponse: + Response containing the head cursor + for the requested topic and partition. + + """ + # Create or coerce a protobuf request object. + + # Minor optimization to avoid making a copy if the user passes + # in a topic_stats.ComputeHeadCursorRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, topic_stats.ComputeHeadCursorRequest): + request = topic_stats.ComputeHeadCursorRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.compute_head_cursor] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("topic", request.topic),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + try: DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( diff --git a/google/cloud/pubsublite_v1/services/topic_stats_service/transports/__init__.py b/google/cloud/pubsublite_v1/services/topic_stats_service/transports/__init__.py index 83891172..91ce8fb5 100644 --- a/google/cloud/pubsublite_v1/services/topic_stats_service/transports/__init__.py +++ b/google/cloud/pubsublite_v1/services/topic_stats_service/transports/__init__.py @@ -28,7 +28,6 @@ _transport_registry["grpc"] = TopicStatsServiceGrpcTransport _transport_registry["grpc_asyncio"] = TopicStatsServiceGrpcAsyncIOTransport - __all__ = ( "TopicStatsServiceTransport", "TopicStatsServiceGrpcTransport", diff --git a/google/cloud/pubsublite_v1/services/topic_stats_service/transports/base.py b/google/cloud/pubsublite_v1/services/topic_stats_service/transports/base.py index f7c4146f..1eda2715 100644 --- a/google/cloud/pubsublite_v1/services/topic_stats_service/transports/base.py +++ b/google/cloud/pubsublite_v1/services/topic_stats_service/transports/base.py @@ -123,6 +123,9 @@ def _prep_wrapped_messages(self, client_info): default_timeout=600.0, client_info=client_info, ), + self.compute_head_cursor: gapic_v1.method.wrap_method( + self.compute_head_cursor, default_timeout=None, client_info=client_info, + ), } @property @@ -137,5 +140,17 @@ def compute_message_stats( ]: raise NotImplementedError() + @property + def compute_head_cursor( + self, + ) -> typing.Callable[ + [topic_stats.ComputeHeadCursorRequest], + typing.Union[ + topic_stats.ComputeHeadCursorResponse, + typing.Awaitable[topic_stats.ComputeHeadCursorResponse], + ], + ]: + raise NotImplementedError() + __all__ = ("TopicStatsServiceTransport",) diff --git a/google/cloud/pubsublite_v1/services/topic_stats_service/transports/grpc.py b/google/cloud/pubsublite_v1/services/topic_stats_service/transports/grpc.py index 3e25decb..ff57fb65 100644 --- a/google/cloud/pubsublite_v1/services/topic_stats_service/transports/grpc.py +++ b/google/cloud/pubsublite_v1/services/topic_stats_service/transports/grpc.py @@ -58,6 +58,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -88,6 +89,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -104,6 +109,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -113,11 +123,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -147,6 +152,10 @@ def __init__( ssl_credentials=ssl_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._ssl_channel_credentials = ssl_credentials else: @@ -157,14 +166,24 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._stubs = {} # type: Dict[str, Callable] @@ -191,7 +210,7 @@ def create_channel( ) -> grpc.Channel: """Create and return a gRPC channel object. Args: - address (Optionsl[str]): The host for the channel to use. + address (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If @@ -260,5 +279,39 @@ def compute_message_stats( ) return self._stubs["compute_message_stats"] + @property + def compute_head_cursor( + self, + ) -> Callable[ + [topic_stats.ComputeHeadCursorRequest], topic_stats.ComputeHeadCursorResponse + ]: + r"""Return a callable for the compute head cursor method over gRPC. + + Compute the head cursor for the partition. + The head cursor's offset is guaranteed to be less than + or equal to all messages which have not yet been + acknowledged as published, and greater than the offset + of any message whose publish has already been + acknowledged. It is zero if there have never been + messages in the partition. + + Returns: + Callable[[~.ComputeHeadCursorRequest], + ~.ComputeHeadCursorResponse]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "compute_head_cursor" not in self._stubs: + self._stubs["compute_head_cursor"] = self.grpc_channel.unary_unary( + "/google.cloud.pubsublite.v1.TopicStatsService/ComputeHeadCursor", + request_serializer=topic_stats.ComputeHeadCursorRequest.serialize, + response_deserializer=topic_stats.ComputeHeadCursorResponse.deserialize, + ) + return self._stubs["compute_head_cursor"] + __all__ = ("TopicStatsServiceGrpcTransport",) diff --git a/google/cloud/pubsublite_v1/services/topic_stats_service/transports/grpc_asyncio.py b/google/cloud/pubsublite_v1/services/topic_stats_service/transports/grpc_asyncio.py index ee787ba5..93d938a3 100644 --- a/google/cloud/pubsublite_v1/services/topic_stats_service/transports/grpc_asyncio.py +++ b/google/cloud/pubsublite_v1/services/topic_stats_service/transports/grpc_asyncio.py @@ -102,6 +102,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -133,6 +134,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -149,6 +154,11 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -158,11 +168,6 @@ def __init__( self._grpc_channel = channel self._ssl_channel_credentials = None elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -192,6 +197,10 @@ def __init__( ssl_credentials=ssl_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) self._ssl_channel_credentials = ssl_credentials else: @@ -202,14 +211,24 @@ def __init__( scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + # create a new channel. The provided one is ignored. self._grpc_channel = type(self).create_channel( host, credentials=credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, + ssl_credentials=self._ssl_channel_credentials, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) # Run the base constructor. @@ -264,5 +283,40 @@ def compute_message_stats( ) return self._stubs["compute_message_stats"] + @property + def compute_head_cursor( + self, + ) -> Callable[ + [topic_stats.ComputeHeadCursorRequest], + Awaitable[topic_stats.ComputeHeadCursorResponse], + ]: + r"""Return a callable for the compute head cursor method over gRPC. + + Compute the head cursor for the partition. + The head cursor's offset is guaranteed to be less than + or equal to all messages which have not yet been + acknowledged as published, and greater than the offset + of any message whose publish has already been + acknowledged. It is zero if there have never been + messages in the partition. + + Returns: + Callable[[~.ComputeHeadCursorRequest], + Awaitable[~.ComputeHeadCursorResponse]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "compute_head_cursor" not in self._stubs: + self._stubs["compute_head_cursor"] = self.grpc_channel.unary_unary( + "/google.cloud.pubsublite.v1.TopicStatsService/ComputeHeadCursor", + request_serializer=topic_stats.ComputeHeadCursorRequest.serialize, + response_deserializer=topic_stats.ComputeHeadCursorResponse.deserialize, + ) + return self._stubs["compute_head_cursor"] + __all__ = ("TopicStatsServiceGrpcAsyncIOTransport",) diff --git a/google/cloud/pubsublite_v1/types/__init__.py b/google/cloud/pubsublite_v1/types/__init__.py index 957e9223..15ed9aeb 100644 --- a/google/cloud/pubsublite_v1/types/__init__.py +++ b/google/cloud/pubsublite_v1/types/__init__.py @@ -15,44 +15,44 @@ # limitations under the License. # -from .common import ( - AttributeValues, - PubSubMessage, - Cursor, - SequencedMessage, - Topic, - Subscription, -) from .admin import ( + CreateSubscriptionRequest, CreateTopicRequest, - GetTopicRequest, + DeleteSubscriptionRequest, + DeleteTopicRequest, + GetSubscriptionRequest, GetTopicPartitionsRequest, - TopicPartitions, + GetTopicRequest, + ListSubscriptionsRequest, + ListSubscriptionsResponse, ListTopicsRequest, ListTopicsResponse, - UpdateTopicRequest, - DeleteTopicRequest, ListTopicSubscriptionsRequest, ListTopicSubscriptionsResponse, - CreateSubscriptionRequest, - GetSubscriptionRequest, - ListSubscriptionsRequest, - ListSubscriptionsResponse, + TopicPartitions, UpdateSubscriptionRequest, - DeleteSubscriptionRequest, + UpdateTopicRequest, +) +from .common import ( + AttributeValues, + Cursor, + PubSubMessage, + SequencedMessage, + Subscription, + Topic, ) from .cursor import ( + CommitCursorRequest, + CommitCursorResponse, InitialCommitCursorRequest, InitialCommitCursorResponse, + ListPartitionCursorsRequest, + ListPartitionCursorsResponse, + PartitionCursor, SequencedCommitCursorRequest, SequencedCommitCursorResponse, StreamingCommitCursorRequest, StreamingCommitCursorResponse, - CommitCursorRequest, - CommitCursorResponse, - ListPartitionCursorsRequest, - PartitionCursor, - ListPartitionCursorsResponse, ) from .publisher import ( InitialPublishRequest, @@ -63,77 +63,80 @@ PublishResponse, ) from .subscriber import ( + FlowControlRequest, + InitialPartitionAssignmentRequest, InitialSubscribeRequest, InitialSubscribeResponse, - SeekRequest, - SeekResponse, - FlowControlRequest, - SubscribeRequest, MessageResponse, - SubscribeResponse, - InitialPartitionAssignmentRequest, PartitionAssignment, PartitionAssignmentAck, PartitionAssignmentRequest, + SeekRequest, + SeekResponse, + SubscribeRequest, + SubscribeResponse, ) from .topic_stats import ( + ComputeHeadCursorRequest, + ComputeHeadCursorResponse, ComputeMessageStatsRequest, ComputeMessageStatsResponse, ) - __all__ = ( - "AttributeValues", - "PubSubMessage", - "Cursor", - "SequencedMessage", - "Topic", - "Subscription", + "CreateSubscriptionRequest", "CreateTopicRequest", - "GetTopicRequest", + "DeleteSubscriptionRequest", + "DeleteTopicRequest", + "GetSubscriptionRequest", "GetTopicPartitionsRequest", - "TopicPartitions", + "GetTopicRequest", + "ListSubscriptionsRequest", + "ListSubscriptionsResponse", "ListTopicsRequest", "ListTopicsResponse", - "UpdateTopicRequest", - "DeleteTopicRequest", "ListTopicSubscriptionsRequest", "ListTopicSubscriptionsResponse", - "CreateSubscriptionRequest", - "GetSubscriptionRequest", - "ListSubscriptionsRequest", - "ListSubscriptionsResponse", + "TopicPartitions", "UpdateSubscriptionRequest", - "DeleteSubscriptionRequest", + "UpdateTopicRequest", + "AttributeValues", + "Cursor", + "PubSubMessage", + "SequencedMessage", + "Subscription", + "Topic", + "CommitCursorRequest", + "CommitCursorResponse", "InitialCommitCursorRequest", "InitialCommitCursorResponse", + "ListPartitionCursorsRequest", + "ListPartitionCursorsResponse", + "PartitionCursor", "SequencedCommitCursorRequest", "SequencedCommitCursorResponse", "StreamingCommitCursorRequest", "StreamingCommitCursorResponse", - "CommitCursorRequest", - "CommitCursorResponse", - "ListPartitionCursorsRequest", - "PartitionCursor", - "ListPartitionCursorsResponse", "InitialPublishRequest", "InitialPublishResponse", "MessagePublishRequest", "MessagePublishResponse", "PublishRequest", "PublishResponse", + "FlowControlRequest", + "InitialPartitionAssignmentRequest", "InitialSubscribeRequest", "InitialSubscribeResponse", - "SeekRequest", - "SeekResponse", - "FlowControlRequest", - "SubscribeRequest", "MessageResponse", - "SubscribeResponse", - "InitialPartitionAssignmentRequest", "PartitionAssignment", "PartitionAssignmentAck", "PartitionAssignmentRequest", + "SeekRequest", + "SeekResponse", + "SubscribeRequest", + "SubscribeResponse", + "ComputeHeadCursorRequest", + "ComputeHeadCursorResponse", "ComputeMessageStatsRequest", "ComputeMessageStatsResponse", ) diff --git a/google/cloud/pubsublite_v1/types/admin.py b/google/cloud/pubsublite_v1/types/admin.py index 55b8d626..c8585e92 100644 --- a/google/cloud/pubsublite_v1/types/admin.py +++ b/google/cloud/pubsublite_v1/types/admin.py @@ -53,7 +53,7 @@ class CreateTopicRequest(proto.Message): Required. The parent location in which to create the topic. Structured like ``projects/{project_number}/locations/{location}``. - topic (~.common.Topic): + topic (google.cloud.pubsublite_v1.types.Topic): Required. Configuration of the topic to create. Its ``name`` field is ignored. topic_id (str): @@ -138,7 +138,7 @@ class ListTopicsResponse(proto.Message): r"""Response for ListTopics. Attributes: - topics (Sequence[~.common.Topic]): + topics (Sequence[google.cloud.pubsublite_v1.types.Topic]): The list of topic in the requested parent. The order of the topics is unspecified. next_page_token (str): @@ -160,10 +160,10 @@ class UpdateTopicRequest(proto.Message): r"""Request for UpdateTopic. Attributes: - topic (~.common.Topic): + topic (google.cloud.pubsublite_v1.types.Topic): Required. The topic to update. Its ``name`` field must be populated. - update_mask (~.field_mask.FieldMask): + update_mask (google.protobuf.field_mask_pb2.FieldMask): Required. A mask specifying the topic fields to change. """ @@ -244,7 +244,7 @@ class CreateSubscriptionRequest(proto.Message): Required. The parent location in which to create the subscription. Structured like ``projects/{project_number}/locations/{location}``. - subscription (~.common.Subscription): + subscription (google.cloud.pubsublite_v1.types.Subscription): Required. Configuration of the subscription to create. Its ``name`` field is ignored. subscription_id (str): @@ -252,6 +252,12 @@ class CreateSubscriptionRequest(proto.Message): become the final component of the subscription's name. This value is structured like: ``my-sub-name``. + skip_backlog (bool): + If true, the newly created subscription will + only receive messages published after the + subscription was created. Otherwise, the entire + message backlog will be received on the + subscription. Defaults to false. """ parent = proto.Field(proto.STRING, number=1) @@ -260,6 +266,8 @@ class CreateSubscriptionRequest(proto.Message): subscription_id = proto.Field(proto.STRING, number=3) + skip_backlog = proto.Field(proto.BOOL, number=4) + class GetSubscriptionRequest(proto.Message): r"""Request for GetSubscription. @@ -306,7 +314,7 @@ class ListSubscriptionsResponse(proto.Message): r"""Response for ListSubscriptions. Attributes: - subscriptions (Sequence[~.common.Subscription]): + subscriptions (Sequence[google.cloud.pubsublite_v1.types.Subscription]): The list of subscriptions in the requested parent. The order of the subscriptions is unspecified. @@ -331,10 +339,10 @@ class UpdateSubscriptionRequest(proto.Message): r"""Request for UpdateSubscription. Attributes: - subscription (~.common.Subscription): + subscription (google.cloud.pubsublite_v1.types.Subscription): Required. The subscription to update. Its ``name`` field must be populated. Topic field must not be populated. - update_mask (~.field_mask.FieldMask): + update_mask (google.protobuf.field_mask_pb2.FieldMask): Required. A mask specifying the subscription fields to change. """ diff --git a/google/cloud/pubsublite_v1/types/common.py b/google/cloud/pubsublite_v1/types/common.py index 6d1c605e..0b94b4b8 100644 --- a/google/cloud/pubsublite_v1/types/common.py +++ b/google/cloud/pubsublite_v1/types/common.py @@ -58,10 +58,10 @@ class PubSubMessage(proto.Message): the message is routed to an arbitrary partition. data (bytes): The payload of the message. - attributes (Sequence[~.common.PubSubMessage.AttributesEntry]): + attributes (Sequence[google.cloud.pubsublite_v1.types.PubSubMessage.AttributesEntry]): Optional attributes that can be used for message metadata/headers. - event_time (~.timestamp.Timestamp): + event_time (google.protobuf.timestamp_pb2.Timestamp): An optional, user-specified event time. """ @@ -94,13 +94,13 @@ class SequencedMessage(proto.Message): Lite system. Attributes: - cursor (~.common.Cursor): + cursor (google.cloud.pubsublite_v1.types.Cursor): The position of a message within the partition where it is stored. - publish_time (~.timestamp.Timestamp): + publish_time (google.protobuf.timestamp_pb2.Timestamp): The time when the message was received by the server when it was first published. - message (~.common.PubSubMessage): + message (google.cloud.pubsublite_v1.types.PubSubMessage): The user message. size_bytes (int): The size in bytes of this message for flow @@ -123,9 +123,9 @@ class Topic(proto.Message): name (str): The name of the topic. Structured like: projects/{project_number}/locations/{location}/topics/{topic_id} - partition_config (~.common.Topic.PartitionConfig): + partition_config (google.cloud.pubsublite_v1.types.Topic.PartitionConfig): The settings for this topic's partitions. - retention_config (~.common.Topic.RetentionConfig): + retention_config (google.cloud.pubsublite_v1.types.Topic.RetentionConfig): The settings for this topic's message retention. """ @@ -135,8 +135,12 @@ class PartitionConfig(proto.Message): Attributes: count (int): - The number of partitions in the topic. Must - be at least 1. + The number of partitions in the topic. Must be at least 1. + + Once a topic has been created the number of partitions can + be increased but not decreased. Message ordering is not + guaranteed across a topic resize. For more information see + https://cloud.google.com/pubsub/lite/docs/topics#scaling_capacity scale (int): DEPRECATED: Use capacity instead which can express a superset of configurations. @@ -147,7 +151,7 @@ class PartitionConfig(proto.Message): this topic; a topic with ``scale`` of 2 and count of 10 is charged for 20 partitions. This value must be in the range [1,4]. - capacity (~.common.Topic.PartitionConfig.Capacity): + capacity (google.cloud.pubsublite_v1.types.Topic.PartitionConfig.Capacity): The capacity configuration. """ @@ -188,7 +192,7 @@ class RetentionConfig(proto.Message): grows beyond this value, older messages will be dropped to make room for newer ones, regardless of the value of ``period``. - period (~.duration.Duration): + period (google.protobuf.duration_pb2.Duration): How long a published message is retained. If unset, messages will be retained as long as the bytes retained for each partition is below ``per_partition_bytes``. @@ -216,7 +220,7 @@ class Subscription(proto.Message): The name of the topic this subscription is attached to. Structured like: projects/{project_number}/locations/{location}/topics/{topic_id} - delivery_config (~.common.Subscription.DeliveryConfig): + delivery_config (google.cloud.pubsublite_v1.types.Subscription.DeliveryConfig): The settings for this subscription's message delivery. """ @@ -225,7 +229,7 @@ class DeliveryConfig(proto.Message): r"""The settings for a subscription's message delivery. Attributes: - delivery_requirement (~.common.Subscription.DeliveryConfig.DeliveryRequirement): + delivery_requirement (google.cloud.pubsublite_v1.types.Subscription.DeliveryConfig.DeliveryRequirement): The DeliveryRequirement for this subscription. """ diff --git a/google/cloud/pubsublite_v1/types/cursor.py b/google/cloud/pubsublite_v1/types/cursor.py index 1f31c8d9..094f972a 100644 --- a/google/cloud/pubsublite_v1/types/cursor.py +++ b/google/cloud/pubsublite_v1/types/cursor.py @@ -68,7 +68,7 @@ class SequencedCommitCursorRequest(proto.Message): SequencedCommitCursorRequests override outstanding ones. Attributes: - cursor (~.common.Cursor): + cursor (google.cloud.pubsublite_v1.types.Cursor): The new value for the committed cursor. """ @@ -94,9 +94,9 @@ class StreamingCommitCursorRequest(proto.Message): r"""A request sent from the client to the server on a stream. Attributes: - initial (~.gcp_cursor.InitialCommitCursorRequest): + initial (google.cloud.pubsublite_v1.types.InitialCommitCursorRequest): Initial request on the stream. - commit (~.gcp_cursor.SequencedCommitCursorRequest): + commit (google.cloud.pubsublite_v1.types.SequencedCommitCursorRequest): Request to commit a new cursor value. """ @@ -116,9 +116,9 @@ class StreamingCommitCursorResponse(proto.Message): r"""Response to a StreamingCommitCursorRequest. Attributes: - initial (~.gcp_cursor.InitialCommitCursorResponse): + initial (google.cloud.pubsublite_v1.types.InitialCommitCursorResponse): Initial response on the stream. - commit (~.gcp_cursor.SequencedCommitCursorResponse): + commit (google.cloud.pubsublite_v1.types.SequencedCommitCursorResponse): Response to committing a new cursor value. """ @@ -145,7 +145,7 @@ class CommitCursorRequest(proto.Message): The partition for which to update the cursor. Partitions are zero indexed, so ``partition`` must be in the range [0, topic.num_partitions). - cursor (~.common.Cursor): + cursor (google.cloud.pubsublite_v1.types.Cursor): The new value for the committed cursor. """ @@ -196,7 +196,7 @@ class PartitionCursor(proto.Message): Attributes: partition (int): The partition this is for. - cursor (~.common.Cursor): + cursor (google.cloud.pubsublite_v1.types.Cursor): The value of the cursor. """ @@ -209,7 +209,7 @@ class ListPartitionCursorsResponse(proto.Message): r"""Response for ListPartitionCursors Attributes: - partition_cursors (Sequence[~.gcp_cursor.PartitionCursor]): + partition_cursors (Sequence[google.cloud.pubsublite_v1.types.PartitionCursor]): The partition cursors from this request. next_page_token (str): A token, which can be sent as ``page_token`` to retrieve the diff --git a/google/cloud/pubsublite_v1/types/publisher.py b/google/cloud/pubsublite_v1/types/publisher.py index d8129236..d8ca68cb 100644 --- a/google/cloud/pubsublite_v1/types/publisher.py +++ b/google/cloud/pubsublite_v1/types/publisher.py @@ -59,7 +59,7 @@ class MessagePublishRequest(proto.Message): r"""Request to publish messages to the topic. Attributes: - messages (Sequence[~.common.PubSubMessage]): + messages (Sequence[google.cloud.pubsublite_v1.types.PubSubMessage]): The messages to publish. """ @@ -72,7 +72,7 @@ class MessagePublishResponse(proto.Message): r"""Response to a MessagePublishRequest. Attributes: - start_cursor (~.common.Cursor): + start_cursor (google.cloud.pubsublite_v1.types.Cursor): The cursor of the first published message in the batch. The cursors for any remaining messages in the batch are guaranteed to be @@ -86,9 +86,9 @@ class PublishRequest(proto.Message): r"""Request sent from the client to the server on a stream. Attributes: - initial_request (~.publisher.InitialPublishRequest): + initial_request (google.cloud.pubsublite_v1.types.InitialPublishRequest): Initial request on the stream. - message_publish_request (~.publisher.MessagePublishRequest): + message_publish_request (google.cloud.pubsublite_v1.types.MessagePublishRequest): Request to publish messages. """ @@ -105,9 +105,9 @@ class PublishResponse(proto.Message): r"""Response to a PublishRequest. Attributes: - initial_response (~.publisher.InitialPublishResponse): + initial_response (google.cloud.pubsublite_v1.types.InitialPublishResponse): Initial response on the stream. - message_response (~.publisher.MessagePublishResponse): + message_response (google.cloud.pubsublite_v1.types.MessagePublishResponse): Response to publishing messages. """ diff --git a/google/cloud/pubsublite_v1/types/subscriber.py b/google/cloud/pubsublite_v1/types/subscriber.py index b2e53c71..1f5a7799 100644 --- a/google/cloud/pubsublite_v1/types/subscriber.py +++ b/google/cloud/pubsublite_v1/types/subscriber.py @@ -64,7 +64,7 @@ class InitialSubscribeResponse(proto.Message): r"""Response to an InitialSubscribeRequest. Attributes: - cursor (~.common.Cursor): + cursor (google.cloud.pubsublite_v1.types.Cursor): The cursor from which the subscriber will start receiving messages once flow control tokens become available. @@ -81,9 +81,9 @@ class SeekRequest(proto.Message): stream. SeekRequests past head result in stream breakage. Attributes: - named_target (~.subscriber.SeekRequest.NamedTarget): + named_target (google.cloud.pubsublite_v1.types.SeekRequest.NamedTarget): A named target. - cursor (~.common.Cursor): + cursor (google.cloud.pubsublite_v1.types.Cursor): A target corresponding to the cursor, pointing to anywhere in the topic partition. """ @@ -107,7 +107,7 @@ class SeekResponse(proto.Message): r"""Response to a SeekRequest. Attributes: - cursor (~.common.Cursor): + cursor (google.cloud.pubsublite_v1.types.Cursor): The new delivery cursor for the current stream. """ @@ -137,12 +137,12 @@ class SubscribeRequest(proto.Message): r"""A request sent from the client to the server on a stream. Attributes: - initial (~.subscriber.InitialSubscribeRequest): + initial (google.cloud.pubsublite_v1.types.InitialSubscribeRequest): Initial request on the stream. - seek (~.subscriber.SeekRequest): + seek (google.cloud.pubsublite_v1.types.SeekRequest): Request to update the stream's delivery cursor. - flow_control (~.subscriber.FlowControlRequest): + flow_control (google.cloud.pubsublite_v1.types.FlowControlRequest): Request to grant tokens to the server, """ @@ -167,7 +167,7 @@ class MessageResponse(proto.Message): available to the server. Attributes: - messages (Sequence[~.common.SequencedMessage]): + messages (Sequence[google.cloud.pubsublite_v1.types.SequencedMessage]): Messages from the topic partition. """ @@ -180,11 +180,11 @@ class SubscribeResponse(proto.Message): r"""Response to SubscribeRequest. Attributes: - initial (~.subscriber.InitialSubscribeResponse): + initial (google.cloud.pubsublite_v1.types.InitialSubscribeResponse): Initial response on the stream. - seek (~.subscriber.SeekResponse): + seek (google.cloud.pubsublite_v1.types.SeekResponse): Response to a Seek operation. - messages (~.subscriber.MessageResponse): + messages (google.cloud.pubsublite_v1.types.MessageResponse): Response containing messages from the topic partition. """ @@ -258,9 +258,9 @@ class PartitionAssignmentRequest(proto.Message): r"""A request on the PartitionAssignment stream. Attributes: - initial (~.subscriber.InitialPartitionAssignmentRequest): + initial (google.cloud.pubsublite_v1.types.InitialPartitionAssignmentRequest): Initial request on the stream. - ack (~.subscriber.PartitionAssignmentAck): + ack (google.cloud.pubsublite_v1.types.PartitionAssignmentAck): Acknowledgement of a partition assignment. """ diff --git a/google/cloud/pubsublite_v1/types/topic_stats.py b/google/cloud/pubsublite_v1/types/topic_stats.py index 54098b9d..003c2f3a 100644 --- a/google/cloud/pubsublite_v1/types/topic_stats.py +++ b/google/cloud/pubsublite_v1/types/topic_stats.py @@ -24,7 +24,12 @@ __protobuf__ = proto.module( package="google.cloud.pubsublite.v1", - manifest={"ComputeMessageStatsRequest", "ComputeMessageStatsResponse",}, + manifest={ + "ComputeMessageStatsRequest", + "ComputeMessageStatsResponse", + "ComputeHeadCursorRequest", + "ComputeHeadCursorResponse", + }, ) @@ -39,9 +44,9 @@ class ComputeMessageStatsRequest(proto.Message): partition (int): Required. The partition for which we should compute message stats. - start_cursor (~.common.Cursor): + start_cursor (google.cloud.pubsublite_v1.types.Cursor): The inclusive start of the range. - end_cursor (~.common.Cursor): + end_cursor (google.cloud.pubsublite_v1.types.Cursor): The exclusive end of the range. The range is empty if end_cursor <= start_cursor. Specifying a start_cursor before the first message and an end_cursor after the last message @@ -67,12 +72,13 @@ class ComputeMessageStatsResponse(proto.Message): message_bytes (int): The number of quota bytes accounted to these messages. - minimum_publish_time (~.timestamp.Timestamp): + minimum_publish_time (google.protobuf.timestamp_pb2.Timestamp): The minimum publish timestamp across these messages. Note that publish timestamps within a - partition are non-decreasing. The timestamp will - be unset if there are no messages. - minimum_event_time (~.timestamp.Timestamp): + partition are not guaranteed to be non- + decreasing. The timestamp will be unset if there + are no messages. + minimum_event_time (google.protobuf.timestamp_pb2.Timestamp): The minimum event timestamp across these messages. For the purposes of this computation, if a message does not have an event time, we use @@ -93,4 +99,33 @@ class ComputeMessageStatsResponse(proto.Message): ) +class ComputeHeadCursorRequest(proto.Message): + r"""Compute the current head cursor for a partition. + + Attributes: + topic (str): + Required. The topic for which we should + compute the head cursor. + partition (int): + Required. The partition for which we should + compute the head cursor. + """ + + topic = proto.Field(proto.STRING, number=1) + + partition = proto.Field(proto.INT64, number=2) + + +class ComputeHeadCursorResponse(proto.Message): + r"""Response containing the head cursor for the requested topic + and partition. + + Attributes: + head_cursor (google.cloud.pubsublite_v1.types.Cursor): + The head cursor. + """ + + head_cursor = proto.Field(proto.MESSAGE, number=1, message=common.Cursor,) + + __all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/synth.metadata b/synth.metadata index 57d521d2..68419ef3 100644 --- a/synth.metadata +++ b/synth.metadata @@ -11,8 +11,8 @@ "git": { "name": "googleapis", "remote": "https://github.com/googleapis/googleapis.git", - "sha": "6a69c750c3f01a69017662395f90515bbf1fe1ff", - "internalRef": "342721036" + "sha": "d652c6370bf66e325da6ac9ad82989fe7ee7bb4b", + "internalRef": "362183999" } }, { @@ -42,6 +42,7 @@ } ], "generatedFiles": [ + ".coveragerc", ".flake8", ".github/CONTRIBUTING.md", ".github/ISSUE_TEMPLATE/bug_report.md", diff --git a/tests/unit/gapic/pubsublite_v1/__init__.py b/tests/unit/gapic/pubsublite_v1/__init__.py index 8b137891..42ffdf2b 100644 --- a/tests/unit/gapic/pubsublite_v1/__init__.py +++ b/tests/unit/gapic/pubsublite_v1/__init__.py @@ -1 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/tests/unit/gapic/pubsublite_v1/test_admin_service.py b/tests/unit/gapic/pubsublite_v1/test_admin_service.py index 0685d58b..7ea93dd7 100644 --- a/tests/unit/gapic/pubsublite_v1/test_admin_service.py +++ b/tests/unit/gapic/pubsublite_v1/test_admin_service.py @@ -84,7 +84,22 @@ def test__get_default_mtls_endpoint(): assert AdminServiceClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi -@pytest.mark.parametrize("client_class", [AdminServiceClient, AdminServiceAsyncClient]) +@pytest.mark.parametrize("client_class", [AdminServiceClient, AdminServiceAsyncClient,]) +def test_admin_service_client_from_service_account_info(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_info" + ) as factory: + factory.return_value = creds + info = {"valid": True} + client = client_class.from_service_account_info(info) + assert client.transport._credentials == creds + assert isinstance(client, client_class) + + assert client.transport._host == "pubsublite.googleapis.com:443" + + +@pytest.mark.parametrize("client_class", [AdminServiceClient, AdminServiceAsyncClient,]) def test_admin_service_client_from_service_account_file(client_class): creds = credentials.AnonymousCredentials() with mock.patch.object( @@ -93,16 +108,21 @@ def test_admin_service_client_from_service_account_file(client_class): factory.return_value = creds client = client_class.from_service_account_file("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) client = client_class.from_service_account_json("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "pubsublite.googleapis.com:443" def test_admin_service_client_get_transport_class(): transport = AdminServiceClient.get_transport_class() - assert transport == transports.AdminServiceGrpcTransport + available_transports = [ + transports.AdminServiceGrpcTransport, + ] + assert transport in available_transports transport = AdminServiceClient.get_transport_class("grpc") assert transport == transports.AdminServiceGrpcTransport @@ -151,7 +171,7 @@ def test_admin_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -167,7 +187,7 @@ def test_admin_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -183,7 +203,7 @@ def test_admin_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -211,7 +231,7 @@ def test_admin_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id="octopus", client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -260,29 +280,25 @@ def test_admin_service_client_mtls_env_auto( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - ssl_channel_creds = mock.Mock() - with mock.patch( - "grpc.ssl_channel_credentials", return_value=ssl_channel_creds - ): - patched.return_value = None - client = client_class(client_options=options) + patched.return_value = None + client = client_class(client_options=options) - if use_client_cert_env == "false": - expected_ssl_channel_creds = None - expected_host = client.DEFAULT_ENDPOINT - else: - expected_ssl_channel_creds = ssl_channel_creds - expected_host = client.DEFAULT_MTLS_ENDPOINT + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) # Check the case ADC client cert is provided. Whether client cert is used depends on # GOOGLE_API_USE_CLIENT_CERTIFICATE value. @@ -291,66 +307,53 @@ def test_admin_service_client_mtls_env_auto( ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, ): with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.ssl_credentials", - new_callable=mock.PropertyMock, - ) as ssl_credentials_mock: - if use_client_cert_env == "false": - is_mtls_mock.return_value = False - ssl_credentials_mock.return_value = None - expected_host = client.DEFAULT_ENDPOINT - expected_ssl_channel_creds = None - else: - is_mtls_mock.return_value = True - ssl_credentials_mock.return_value = mock.Mock() - expected_host = client.DEFAULT_MTLS_ENDPOINT - expected_ssl_channel_creds = ( - ssl_credentials_mock.return_value - ) - - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client.DEFAULT_ENDPOINT + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback - # Check the case client_cert_source and ADC client cert are not provided. - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} - ): - with mock.patch.object(transport_class, "__init__") as patched: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None - ): - with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - is_mtls_mock.return_value = False patched.return_value = None client = client_class() patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_ENDPOINT, + host=expected_host, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + @pytest.mark.parametrize( "client_class,transport_class,transport_name", @@ -376,7 +379,7 @@ def test_admin_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -406,7 +409,7 @@ def test_admin_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -423,7 +426,7 @@ def test_admin_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -462,6 +465,22 @@ def test_create_topic_from_dict(): test_create_topic(request_type=dict) +def test_create_topic_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = AdminServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.create_topic), "__call__") as call: + client.create_topic() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == admin.CreateTopicRequest() + + @pytest.mark.asyncio async def test_create_topic_async( transport: str = "grpc_asyncio", request_type=admin.CreateTopicRequest @@ -669,6 +688,22 @@ def test_get_topic_from_dict(): test_get_topic(request_type=dict) +def test_get_topic_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = AdminServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_topic), "__call__") as call: + client.get_topic() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == admin.GetTopicRequest() + + @pytest.mark.asyncio async def test_get_topic_async( transport: str = "grpc_asyncio", request_type=admin.GetTopicRequest @@ -858,6 +893,24 @@ def test_get_topic_partitions_from_dict(): test_get_topic_partitions(request_type=dict) +def test_get_topic_partitions_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = AdminServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.get_topic_partitions), "__call__" + ) as call: + client.get_topic_partitions() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == admin.GetTopicPartitionsRequest() + + @pytest.mark.asyncio async def test_get_topic_partitions_async( transport: str = "grpc_asyncio", request_type=admin.GetTopicPartitionsRequest @@ -1059,6 +1112,22 @@ def test_list_topics_from_dict(): test_list_topics(request_type=dict) +def test_list_topics_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = AdminServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_topics), "__call__") as call: + client.list_topics() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == admin.ListTopicsRequest() + + @pytest.mark.asyncio async def test_list_topics_async( transport: str = "grpc_asyncio", request_type=admin.ListTopicsRequest @@ -1354,6 +1423,22 @@ def test_update_topic_from_dict(): test_update_topic(request_type=dict) +def test_update_topic_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = AdminServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_topic), "__call__") as call: + client.update_topic() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == admin.UpdateTopicRequest() + + @pytest.mark.asyncio async def test_update_topic_async( transport: str = "grpc_asyncio", request_type=admin.UpdateTopicRequest @@ -1550,6 +1635,22 @@ def test_delete_topic_from_dict(): test_delete_topic(request_type=dict) +def test_delete_topic_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = AdminServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.delete_topic), "__call__") as call: + client.delete_topic() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == admin.DeleteTopicRequest() + + @pytest.mark.asyncio async def test_delete_topic_async( transport: str = "grpc_asyncio", request_type=admin.DeleteTopicRequest @@ -1740,6 +1841,24 @@ def test_list_topic_subscriptions_from_dict(): test_list_topic_subscriptions(request_type=dict) +def test_list_topic_subscriptions_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = AdminServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_topic_subscriptions), "__call__" + ) as call: + client.list_topic_subscriptions() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == admin.ListTopicSubscriptionsRequest() + + @pytest.mark.asyncio async def test_list_topic_subscriptions_async( transport: str = "grpc_asyncio", request_type=admin.ListTopicSubscriptionsRequest @@ -2076,6 +2195,24 @@ def test_create_subscription_from_dict(): test_create_subscription(request_type=dict) +def test_create_subscription_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = AdminServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.create_subscription), "__call__" + ) as call: + client.create_subscription() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == admin.CreateSubscriptionRequest() + + @pytest.mark.asyncio async def test_create_subscription_async( transport: str = "grpc_asyncio", request_type=admin.CreateSubscriptionRequest @@ -2299,6 +2436,22 @@ def test_get_subscription_from_dict(): test_get_subscription(request_type=dict) +def test_get_subscription_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = AdminServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_subscription), "__call__") as call: + client.get_subscription() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == admin.GetSubscriptionRequest() + + @pytest.mark.asyncio async def test_get_subscription_async( transport: str = "grpc_asyncio", request_type=admin.GetSubscriptionRequest @@ -2492,6 +2645,24 @@ def test_list_subscriptions_from_dict(): test_list_subscriptions(request_type=dict) +def test_list_subscriptions_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = AdminServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_subscriptions), "__call__" + ) as call: + client.list_subscriptions() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == admin.ListSubscriptionsRequest() + + @pytest.mark.asyncio async def test_list_subscriptions_async( transport: str = "grpc_asyncio", request_type=admin.ListSubscriptionsRequest @@ -2843,6 +3014,24 @@ def test_update_subscription_from_dict(): test_update_subscription(request_type=dict) +def test_update_subscription_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = AdminServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.update_subscription), "__call__" + ) as call: + client.update_subscription() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == admin.UpdateSubscriptionRequest() + + @pytest.mark.asyncio async def test_update_subscription_async( transport: str = "grpc_asyncio", request_type=admin.UpdateSubscriptionRequest @@ -3061,6 +3250,24 @@ def test_delete_subscription_from_dict(): test_delete_subscription(request_type=dict) +def test_delete_subscription_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = AdminServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.delete_subscription), "__call__" + ) as call: + client.delete_subscription() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == admin.DeleteSubscriptionRequest() + + @pytest.mark.asyncio async def test_delete_subscription_async( transport: str = "grpc_asyncio", request_type=admin.DeleteSubscriptionRequest @@ -3275,7 +3482,10 @@ def test_transport_get_channel(): @pytest.mark.parametrize( "transport_class", - [transports.AdminServiceGrpcTransport, transports.AdminServiceGrpcAsyncIOTransport], + [ + transports.AdminServiceGrpcTransport, + transports.AdminServiceGrpcAsyncIOTransport, + ], ) def test_transport_adc(transport_class): # Test default credentials are used if not provided. @@ -3386,6 +3596,48 @@ def test_admin_service_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class", + [transports.AdminServiceGrpcTransport, transports.AdminServiceGrpcAsyncIOTransport], +) +def test_admin_service_grpc_transport_client_cert_source_for_mtls(transport_class): + cred = credentials.AnonymousCredentials() + + # Check ssl_channel_credentials is used if provided. + with mock.patch.object(transport_class, "create_channel") as mock_create_channel: + mock_ssl_channel_creds = mock.Mock() + transport_class( + host="squid.clam.whelk", + credentials=cred, + ssl_channel_credentials=mock_ssl_channel_creds, + ) + mock_create_channel.assert_called_once_with( + "squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ssl_credentials=mock_ssl_channel_creds, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls + # is used. + with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()): + with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred: + transport_class( + credentials=cred, + client_cert_source_for_mtls=client_cert_source_callback, + ) + expected_cert, expected_key = client_cert_source_callback() + mock_ssl_cred.assert_called_once_with( + certificate_chain=expected_cert, private_key=expected_key + ) + + def test_admin_service_host_no_port(): client = AdminServiceClient( credentials=credentials.AnonymousCredentials(), @@ -3407,7 +3659,7 @@ def test_admin_service_host_with_port(): def test_admin_service_grpc_transport_channel(): - channel = grpc.insecure_channel("http://localhost/") + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.AdminServiceGrpcTransport( @@ -3419,7 +3671,7 @@ def test_admin_service_grpc_transport_channel(): def test_admin_service_grpc_asyncio_transport_channel(): - channel = aio.insecure_channel("http://localhost/") + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.AdminServiceGrpcAsyncIOTransport( @@ -3430,6 +3682,8 @@ def test_admin_service_grpc_asyncio_transport_channel(): assert transport._ssl_channel_credentials == None +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [transports.AdminServiceGrpcTransport, transports.AdminServiceGrpcAsyncIOTransport], @@ -3439,7 +3693,7 @@ def test_admin_service_transport_channel_mtls_with_client_cert_source(transport_ "grpc.ssl_channel_credentials", autospec=True ) as grpc_ssl_channel_cred: with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_ssl_cred = mock.Mock() grpc_ssl_channel_cred.return_value = mock_ssl_cred @@ -3468,11 +3722,17 @@ def test_admin_service_transport_channel_mtls_with_client_cert_source(transport_ scopes=("https://www.googleapis.com/auth/cloud-platform",), ssl_credentials=mock_ssl_cred, quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) assert transport.grpc_channel == mock_grpc_channel assert transport._ssl_channel_credentials == mock_ssl_cred +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [transports.AdminServiceGrpcTransport, transports.AdminServiceGrpcAsyncIOTransport], @@ -3485,7 +3745,7 @@ def test_admin_service_transport_channel_mtls_with_adc(transport_class): ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_grpc_channel = mock.Mock() grpc_create_channel.return_value = mock_grpc_channel @@ -3506,6 +3766,10 @@ def test_admin_service_transport_channel_mtls_with_adc(transport_class): scopes=("https://www.googleapis.com/auth/cloud-platform",), ssl_credentials=mock_ssl_cred, quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) assert transport.grpc_channel == mock_grpc_channel diff --git a/tests/unit/gapic/pubsublite_v1/test_cursor_service.py b/tests/unit/gapic/pubsublite_v1/test_cursor_service.py index f4981ca4..181b141f 100644 --- a/tests/unit/gapic/pubsublite_v1/test_cursor_service.py +++ b/tests/unit/gapic/pubsublite_v1/test_cursor_service.py @@ -86,7 +86,24 @@ def test__get_default_mtls_endpoint(): @pytest.mark.parametrize( - "client_class", [CursorServiceClient, CursorServiceAsyncClient] + "client_class", [CursorServiceClient, CursorServiceAsyncClient,] +) +def test_cursor_service_client_from_service_account_info(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_info" + ) as factory: + factory.return_value = creds + info = {"valid": True} + client = client_class.from_service_account_info(info) + assert client.transport._credentials == creds + assert isinstance(client, client_class) + + assert client.transport._host == "pubsublite.googleapis.com:443" + + +@pytest.mark.parametrize( + "client_class", [CursorServiceClient, CursorServiceAsyncClient,] ) def test_cursor_service_client_from_service_account_file(client_class): creds = credentials.AnonymousCredentials() @@ -96,16 +113,21 @@ def test_cursor_service_client_from_service_account_file(client_class): factory.return_value = creds client = client_class.from_service_account_file("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) client = client_class.from_service_account_json("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "pubsublite.googleapis.com:443" def test_cursor_service_client_get_transport_class(): transport = CursorServiceClient.get_transport_class() - assert transport == transports.CursorServiceGrpcTransport + available_transports = [ + transports.CursorServiceGrpcTransport, + ] + assert transport in available_transports transport = CursorServiceClient.get_transport_class("grpc") assert transport == transports.CursorServiceGrpcTransport @@ -156,7 +178,7 @@ def test_cursor_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -172,7 +194,7 @@ def test_cursor_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -188,7 +210,7 @@ def test_cursor_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -216,7 +238,7 @@ def test_cursor_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id="octopus", client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -267,29 +289,25 @@ def test_cursor_service_client_mtls_env_auto( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - ssl_channel_creds = mock.Mock() - with mock.patch( - "grpc.ssl_channel_credentials", return_value=ssl_channel_creds - ): - patched.return_value = None - client = client_class(client_options=options) + patched.return_value = None + client = client_class(client_options=options) - if use_client_cert_env == "false": - expected_ssl_channel_creds = None - expected_host = client.DEFAULT_ENDPOINT - else: - expected_ssl_channel_creds = ssl_channel_creds - expected_host = client.DEFAULT_MTLS_ENDPOINT + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) # Check the case ADC client cert is provided. Whether client cert is used depends on # GOOGLE_API_USE_CLIENT_CERTIFICATE value. @@ -298,66 +316,53 @@ def test_cursor_service_client_mtls_env_auto( ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, ): with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.ssl_credentials", - new_callable=mock.PropertyMock, - ) as ssl_credentials_mock: - if use_client_cert_env == "false": - is_mtls_mock.return_value = False - ssl_credentials_mock.return_value = None - expected_host = client.DEFAULT_ENDPOINT - expected_ssl_channel_creds = None - else: - is_mtls_mock.return_value = True - ssl_credentials_mock.return_value = mock.Mock() - expected_host = client.DEFAULT_MTLS_ENDPOINT - expected_ssl_channel_creds = ( - ssl_credentials_mock.return_value - ) - - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client.DEFAULT_ENDPOINT + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback - # Check the case client_cert_source and ADC client cert are not provided. - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} - ): - with mock.patch.object(transport_class, "__init__") as patched: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None - ): - with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - is_mtls_mock.return_value = False patched.return_value = None client = client_class() patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_ENDPOINT, + host=expected_host, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + @pytest.mark.parametrize( "client_class,transport_class,transport_name", @@ -383,7 +388,7 @@ def test_cursor_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -413,7 +418,7 @@ def test_cursor_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -432,7 +437,7 @@ def test_cursor_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -550,6 +555,22 @@ def test_commit_cursor_from_dict(): test_commit_cursor(request_type=dict) +def test_commit_cursor_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = CursorServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.commit_cursor), "__call__") as call: + client.commit_cursor() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == cursor.CommitCursorRequest() + + @pytest.mark.asyncio async def test_commit_cursor_async( transport: str = "grpc_asyncio", request_type=cursor.CommitCursorRequest @@ -625,6 +646,24 @@ def test_list_partition_cursors_from_dict(): test_list_partition_cursors(request_type=dict) +def test_list_partition_cursors_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = CursorServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_partition_cursors), "__call__" + ) as call: + client.list_partition_cursors() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == cursor.ListPartitionCursorsRequest() + + @pytest.mark.asyncio async def test_list_partition_cursors_async( transport: str = "grpc_asyncio", request_type=cursor.ListPartitionCursorsRequest @@ -1108,6 +1147,51 @@ def test_cursor_service_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class", + [ + transports.CursorServiceGrpcTransport, + transports.CursorServiceGrpcAsyncIOTransport, + ], +) +def test_cursor_service_grpc_transport_client_cert_source_for_mtls(transport_class): + cred = credentials.AnonymousCredentials() + + # Check ssl_channel_credentials is used if provided. + with mock.patch.object(transport_class, "create_channel") as mock_create_channel: + mock_ssl_channel_creds = mock.Mock() + transport_class( + host="squid.clam.whelk", + credentials=cred, + ssl_channel_credentials=mock_ssl_channel_creds, + ) + mock_create_channel.assert_called_once_with( + "squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ssl_credentials=mock_ssl_channel_creds, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls + # is used. + with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()): + with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred: + transport_class( + credentials=cred, + client_cert_source_for_mtls=client_cert_source_callback, + ) + expected_cert, expected_key = client_cert_source_callback() + mock_ssl_cred.assert_called_once_with( + certificate_chain=expected_cert, private_key=expected_key + ) + + def test_cursor_service_host_no_port(): client = CursorServiceClient( credentials=credentials.AnonymousCredentials(), @@ -1129,7 +1213,7 @@ def test_cursor_service_host_with_port(): def test_cursor_service_grpc_transport_channel(): - channel = grpc.insecure_channel("http://localhost/") + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.CursorServiceGrpcTransport( @@ -1141,7 +1225,7 @@ def test_cursor_service_grpc_transport_channel(): def test_cursor_service_grpc_asyncio_transport_channel(): - channel = aio.insecure_channel("http://localhost/") + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.CursorServiceGrpcAsyncIOTransport( @@ -1152,6 +1236,8 @@ def test_cursor_service_grpc_asyncio_transport_channel(): assert transport._ssl_channel_credentials == None +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ @@ -1164,7 +1250,7 @@ def test_cursor_service_transport_channel_mtls_with_client_cert_source(transport "grpc.ssl_channel_credentials", autospec=True ) as grpc_ssl_channel_cred: with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_ssl_cred = mock.Mock() grpc_ssl_channel_cred.return_value = mock_ssl_cred @@ -1193,11 +1279,17 @@ def test_cursor_service_transport_channel_mtls_with_client_cert_source(transport scopes=("https://www.googleapis.com/auth/cloud-platform",), ssl_credentials=mock_ssl_cred, quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) assert transport.grpc_channel == mock_grpc_channel assert transport._ssl_channel_credentials == mock_ssl_cred +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ @@ -1213,7 +1305,7 @@ def test_cursor_service_transport_channel_mtls_with_adc(transport_class): ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_grpc_channel = mock.Mock() grpc_create_channel.return_value = mock_grpc_channel @@ -1234,6 +1326,10 @@ def test_cursor_service_transport_channel_mtls_with_adc(transport_class): scopes=("https://www.googleapis.com/auth/cloud-platform",), ssl_credentials=mock_ssl_cred, quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) assert transport.grpc_channel == mock_grpc_channel diff --git a/tests/unit/gapic/pubsublite_v1/test_partition_assignment_service.py b/tests/unit/gapic/pubsublite_v1/test_partition_assignment_service.py index 35bf8481..45ac59a9 100644 --- a/tests/unit/gapic/pubsublite_v1/test_partition_assignment_service.py +++ b/tests/unit/gapic/pubsublite_v1/test_partition_assignment_service.py @@ -92,7 +92,25 @@ def test__get_default_mtls_endpoint(): @pytest.mark.parametrize( "client_class", - [PartitionAssignmentServiceClient, PartitionAssignmentServiceAsyncClient], + [PartitionAssignmentServiceClient, PartitionAssignmentServiceAsyncClient,], +) +def test_partition_assignment_service_client_from_service_account_info(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_info" + ) as factory: + factory.return_value = creds + info = {"valid": True} + client = client_class.from_service_account_info(info) + assert client.transport._credentials == creds + assert isinstance(client, client_class) + + assert client.transport._host == "pubsublite.googleapis.com:443" + + +@pytest.mark.parametrize( + "client_class", + [PartitionAssignmentServiceClient, PartitionAssignmentServiceAsyncClient,], ) def test_partition_assignment_service_client_from_service_account_file(client_class): creds = credentials.AnonymousCredentials() @@ -102,16 +120,21 @@ def test_partition_assignment_service_client_from_service_account_file(client_cl factory.return_value = creds client = client_class.from_service_account_file("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) client = client_class.from_service_account_json("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "pubsublite.googleapis.com:443" def test_partition_assignment_service_client_get_transport_class(): transport = PartitionAssignmentServiceClient.get_transport_class() - assert transport == transports.PartitionAssignmentServiceGrpcTransport + available_transports = [ + transports.PartitionAssignmentServiceGrpcTransport, + ] + assert transport in available_transports transport = PartitionAssignmentServiceClient.get_transport_class("grpc") assert transport == transports.PartitionAssignmentServiceGrpcTransport @@ -170,7 +193,7 @@ def test_partition_assignment_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -186,7 +209,7 @@ def test_partition_assignment_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -202,7 +225,7 @@ def test_partition_assignment_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -230,7 +253,7 @@ def test_partition_assignment_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id="octopus", client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -291,29 +314,25 @@ def test_partition_assignment_service_client_mtls_env_auto( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - ssl_channel_creds = mock.Mock() - with mock.patch( - "grpc.ssl_channel_credentials", return_value=ssl_channel_creds - ): - patched.return_value = None - client = client_class(client_options=options) + patched.return_value = None + client = client_class(client_options=options) - if use_client_cert_env == "false": - expected_ssl_channel_creds = None - expected_host = client.DEFAULT_ENDPOINT - else: - expected_ssl_channel_creds = ssl_channel_creds - expected_host = client.DEFAULT_MTLS_ENDPOINT + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) # Check the case ADC client cert is provided. Whether client cert is used depends on # GOOGLE_API_USE_CLIENT_CERTIFICATE value. @@ -322,66 +341,53 @@ def test_partition_assignment_service_client_mtls_env_auto( ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, ): with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.ssl_credentials", - new_callable=mock.PropertyMock, - ) as ssl_credentials_mock: - if use_client_cert_env == "false": - is_mtls_mock.return_value = False - ssl_credentials_mock.return_value = None - expected_host = client.DEFAULT_ENDPOINT - expected_ssl_channel_creds = None - else: - is_mtls_mock.return_value = True - ssl_credentials_mock.return_value = mock.Mock() - expected_host = client.DEFAULT_MTLS_ENDPOINT - expected_ssl_channel_creds = ( - ssl_credentials_mock.return_value - ) - - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client.DEFAULT_ENDPOINT + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback - # Check the case client_cert_source and ADC client cert are not provided. - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} - ): - with mock.patch.object(transport_class, "__init__") as patched: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None - ): - with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - is_mtls_mock.return_value = False patched.return_value = None client = client_class() patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_ENDPOINT, + host=expected_host, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + @pytest.mark.parametrize( "client_class,transport_class,transport_name", @@ -411,7 +417,7 @@ def test_partition_assignment_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -445,7 +451,7 @@ def test_partition_assignment_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -464,7 +470,7 @@ def test_partition_assignment_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -710,6 +716,53 @@ def test_partition_assignment_service_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class", + [ + transports.PartitionAssignmentServiceGrpcTransport, + transports.PartitionAssignmentServiceGrpcAsyncIOTransport, + ], +) +def test_partition_assignment_service_grpc_transport_client_cert_source_for_mtls( + transport_class, +): + cred = credentials.AnonymousCredentials() + + # Check ssl_channel_credentials is used if provided. + with mock.patch.object(transport_class, "create_channel") as mock_create_channel: + mock_ssl_channel_creds = mock.Mock() + transport_class( + host="squid.clam.whelk", + credentials=cred, + ssl_channel_credentials=mock_ssl_channel_creds, + ) + mock_create_channel.assert_called_once_with( + "squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ssl_credentials=mock_ssl_channel_creds, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls + # is used. + with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()): + with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred: + transport_class( + credentials=cred, + client_cert_source_for_mtls=client_cert_source_callback, + ) + expected_cert, expected_key = client_cert_source_callback() + mock_ssl_cred.assert_called_once_with( + certificate_chain=expected_cert, private_key=expected_key + ) + + def test_partition_assignment_service_host_no_port(): client = PartitionAssignmentServiceClient( credentials=credentials.AnonymousCredentials(), @@ -731,7 +784,7 @@ def test_partition_assignment_service_host_with_port(): def test_partition_assignment_service_grpc_transport_channel(): - channel = grpc.insecure_channel("http://localhost/") + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.PartitionAssignmentServiceGrpcTransport( @@ -743,7 +796,7 @@ def test_partition_assignment_service_grpc_transport_channel(): def test_partition_assignment_service_grpc_asyncio_transport_channel(): - channel = aio.insecure_channel("http://localhost/") + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.PartitionAssignmentServiceGrpcAsyncIOTransport( @@ -754,6 +807,8 @@ def test_partition_assignment_service_grpc_asyncio_transport_channel(): assert transport._ssl_channel_credentials == None +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ @@ -768,7 +823,7 @@ def test_partition_assignment_service_transport_channel_mtls_with_client_cert_so "grpc.ssl_channel_credentials", autospec=True ) as grpc_ssl_channel_cred: with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_ssl_cred = mock.Mock() grpc_ssl_channel_cred.return_value = mock_ssl_cred @@ -797,11 +852,17 @@ def test_partition_assignment_service_transport_channel_mtls_with_client_cert_so scopes=("https://www.googleapis.com/auth/cloud-platform",), ssl_credentials=mock_ssl_cred, quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) assert transport.grpc_channel == mock_grpc_channel assert transport._ssl_channel_credentials == mock_ssl_cred +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ @@ -817,7 +878,7 @@ def test_partition_assignment_service_transport_channel_mtls_with_adc(transport_ ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_grpc_channel = mock.Mock() grpc_create_channel.return_value = mock_grpc_channel @@ -838,6 +899,10 @@ def test_partition_assignment_service_transport_channel_mtls_with_adc(transport_ scopes=("https://www.googleapis.com/auth/cloud-platform",), ssl_credentials=mock_ssl_cred, quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) assert transport.grpc_channel == mock_grpc_channel diff --git a/tests/unit/gapic/pubsublite_v1/test_publisher_service.py b/tests/unit/gapic/pubsublite_v1/test_publisher_service.py index c85679a9..abc1ff8c 100644 --- a/tests/unit/gapic/pubsublite_v1/test_publisher_service.py +++ b/tests/unit/gapic/pubsublite_v1/test_publisher_service.py @@ -89,7 +89,24 @@ def test__get_default_mtls_endpoint(): @pytest.mark.parametrize( - "client_class", [PublisherServiceClient, PublisherServiceAsyncClient] + "client_class", [PublisherServiceClient, PublisherServiceAsyncClient,] +) +def test_publisher_service_client_from_service_account_info(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_info" + ) as factory: + factory.return_value = creds + info = {"valid": True} + client = client_class.from_service_account_info(info) + assert client.transport._credentials == creds + assert isinstance(client, client_class) + + assert client.transport._host == "pubsublite.googleapis.com:443" + + +@pytest.mark.parametrize( + "client_class", [PublisherServiceClient, PublisherServiceAsyncClient,] ) def test_publisher_service_client_from_service_account_file(client_class): creds = credentials.AnonymousCredentials() @@ -99,16 +116,21 @@ def test_publisher_service_client_from_service_account_file(client_class): factory.return_value = creds client = client_class.from_service_account_file("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) client = client_class.from_service_account_json("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "pubsublite.googleapis.com:443" def test_publisher_service_client_get_transport_class(): transport = PublisherServiceClient.get_transport_class() - assert transport == transports.PublisherServiceGrpcTransport + available_transports = [ + transports.PublisherServiceGrpcTransport, + ] + assert transport in available_transports transport = PublisherServiceClient.get_transport_class("grpc") assert transport == transports.PublisherServiceGrpcTransport @@ -159,7 +181,7 @@ def test_publisher_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -175,7 +197,7 @@ def test_publisher_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -191,7 +213,7 @@ def test_publisher_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -219,7 +241,7 @@ def test_publisher_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id="octopus", client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -280,29 +302,25 @@ def test_publisher_service_client_mtls_env_auto( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - ssl_channel_creds = mock.Mock() - with mock.patch( - "grpc.ssl_channel_credentials", return_value=ssl_channel_creds - ): - patched.return_value = None - client = client_class(client_options=options) + patched.return_value = None + client = client_class(client_options=options) - if use_client_cert_env == "false": - expected_ssl_channel_creds = None - expected_host = client.DEFAULT_ENDPOINT - else: - expected_ssl_channel_creds = ssl_channel_creds - expected_host = client.DEFAULT_MTLS_ENDPOINT + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) # Check the case ADC client cert is provided. Whether client cert is used depends on # GOOGLE_API_USE_CLIENT_CERTIFICATE value. @@ -311,66 +329,53 @@ def test_publisher_service_client_mtls_env_auto( ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, ): with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.ssl_credentials", - new_callable=mock.PropertyMock, - ) as ssl_credentials_mock: - if use_client_cert_env == "false": - is_mtls_mock.return_value = False - ssl_credentials_mock.return_value = None - expected_host = client.DEFAULT_ENDPOINT - expected_ssl_channel_creds = None - else: - is_mtls_mock.return_value = True - ssl_credentials_mock.return_value = mock.Mock() - expected_host = client.DEFAULT_MTLS_ENDPOINT - expected_ssl_channel_creds = ( - ssl_credentials_mock.return_value - ) - - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client.DEFAULT_ENDPOINT + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback - # Check the case client_cert_source and ADC client cert are not provided. - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} - ): - with mock.patch.object(transport_class, "__init__") as patched: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None - ): - with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - is_mtls_mock.return_value = False patched.return_value = None client = client_class() patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_ENDPOINT, + host=expected_host, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + @pytest.mark.parametrize( "client_class,transport_class,transport_name", @@ -396,7 +401,7 @@ def test_publisher_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -426,7 +431,7 @@ def test_publisher_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -445,7 +450,7 @@ def test_publisher_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -681,6 +686,51 @@ def test_publisher_service_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class", + [ + transports.PublisherServiceGrpcTransport, + transports.PublisherServiceGrpcAsyncIOTransport, + ], +) +def test_publisher_service_grpc_transport_client_cert_source_for_mtls(transport_class): + cred = credentials.AnonymousCredentials() + + # Check ssl_channel_credentials is used if provided. + with mock.patch.object(transport_class, "create_channel") as mock_create_channel: + mock_ssl_channel_creds = mock.Mock() + transport_class( + host="squid.clam.whelk", + credentials=cred, + ssl_channel_credentials=mock_ssl_channel_creds, + ) + mock_create_channel.assert_called_once_with( + "squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ssl_credentials=mock_ssl_channel_creds, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls + # is used. + with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()): + with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred: + transport_class( + credentials=cred, + client_cert_source_for_mtls=client_cert_source_callback, + ) + expected_cert, expected_key = client_cert_source_callback() + mock_ssl_cred.assert_called_once_with( + certificate_chain=expected_cert, private_key=expected_key + ) + + def test_publisher_service_host_no_port(): client = PublisherServiceClient( credentials=credentials.AnonymousCredentials(), @@ -702,7 +752,7 @@ def test_publisher_service_host_with_port(): def test_publisher_service_grpc_transport_channel(): - channel = grpc.insecure_channel("http://localhost/") + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.PublisherServiceGrpcTransport( @@ -714,7 +764,7 @@ def test_publisher_service_grpc_transport_channel(): def test_publisher_service_grpc_asyncio_transport_channel(): - channel = aio.insecure_channel("http://localhost/") + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.PublisherServiceGrpcAsyncIOTransport( @@ -725,6 +775,8 @@ def test_publisher_service_grpc_asyncio_transport_channel(): assert transport._ssl_channel_credentials == None +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ @@ -739,7 +791,7 @@ def test_publisher_service_transport_channel_mtls_with_client_cert_source( "grpc.ssl_channel_credentials", autospec=True ) as grpc_ssl_channel_cred: with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_ssl_cred = mock.Mock() grpc_ssl_channel_cred.return_value = mock_ssl_cred @@ -768,11 +820,17 @@ def test_publisher_service_transport_channel_mtls_with_client_cert_source( scopes=("https://www.googleapis.com/auth/cloud-platform",), ssl_credentials=mock_ssl_cred, quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) assert transport.grpc_channel == mock_grpc_channel assert transport._ssl_channel_credentials == mock_ssl_cred +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ @@ -788,7 +846,7 @@ def test_publisher_service_transport_channel_mtls_with_adc(transport_class): ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_grpc_channel = mock.Mock() grpc_create_channel.return_value = mock_grpc_channel @@ -809,6 +867,10 @@ def test_publisher_service_transport_channel_mtls_with_adc(transport_class): scopes=("https://www.googleapis.com/auth/cloud-platform",), ssl_credentials=mock_ssl_cred, quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) assert transport.grpc_channel == mock_grpc_channel diff --git a/tests/unit/gapic/pubsublite_v1/test_subscriber_service.py b/tests/unit/gapic/pubsublite_v1/test_subscriber_service.py index ca5464dc..6118ade5 100644 --- a/tests/unit/gapic/pubsublite_v1/test_subscriber_service.py +++ b/tests/unit/gapic/pubsublite_v1/test_subscriber_service.py @@ -90,7 +90,24 @@ def test__get_default_mtls_endpoint(): @pytest.mark.parametrize( - "client_class", [SubscriberServiceClient, SubscriberServiceAsyncClient] + "client_class", [SubscriberServiceClient, SubscriberServiceAsyncClient,] +) +def test_subscriber_service_client_from_service_account_info(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_info" + ) as factory: + factory.return_value = creds + info = {"valid": True} + client = client_class.from_service_account_info(info) + assert client.transport._credentials == creds + assert isinstance(client, client_class) + + assert client.transport._host == "pubsublite.googleapis.com:443" + + +@pytest.mark.parametrize( + "client_class", [SubscriberServiceClient, SubscriberServiceAsyncClient,] ) def test_subscriber_service_client_from_service_account_file(client_class): creds = credentials.AnonymousCredentials() @@ -100,16 +117,21 @@ def test_subscriber_service_client_from_service_account_file(client_class): factory.return_value = creds client = client_class.from_service_account_file("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) client = client_class.from_service_account_json("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "pubsublite.googleapis.com:443" def test_subscriber_service_client_get_transport_class(): transport = SubscriberServiceClient.get_transport_class() - assert transport == transports.SubscriberServiceGrpcTransport + available_transports = [ + transports.SubscriberServiceGrpcTransport, + ] + assert transport in available_transports transport = SubscriberServiceClient.get_transport_class("grpc") assert transport == transports.SubscriberServiceGrpcTransport @@ -160,7 +182,7 @@ def test_subscriber_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -176,7 +198,7 @@ def test_subscriber_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -192,7 +214,7 @@ def test_subscriber_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -220,7 +242,7 @@ def test_subscriber_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id="octopus", client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -281,29 +303,25 @@ def test_subscriber_service_client_mtls_env_auto( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - ssl_channel_creds = mock.Mock() - with mock.patch( - "grpc.ssl_channel_credentials", return_value=ssl_channel_creds - ): - patched.return_value = None - client = client_class(client_options=options) + patched.return_value = None + client = client_class(client_options=options) - if use_client_cert_env == "false": - expected_ssl_channel_creds = None - expected_host = client.DEFAULT_ENDPOINT - else: - expected_ssl_channel_creds = ssl_channel_creds - expected_host = client.DEFAULT_MTLS_ENDPOINT + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) # Check the case ADC client cert is provided. Whether client cert is used depends on # GOOGLE_API_USE_CLIENT_CERTIFICATE value. @@ -312,66 +330,53 @@ def test_subscriber_service_client_mtls_env_auto( ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, ): with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.ssl_credentials", - new_callable=mock.PropertyMock, - ) as ssl_credentials_mock: - if use_client_cert_env == "false": - is_mtls_mock.return_value = False - ssl_credentials_mock.return_value = None - expected_host = client.DEFAULT_ENDPOINT - expected_ssl_channel_creds = None - else: - is_mtls_mock.return_value = True - ssl_credentials_mock.return_value = mock.Mock() - expected_host = client.DEFAULT_MTLS_ENDPOINT - expected_ssl_channel_creds = ( - ssl_credentials_mock.return_value - ) - - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client.DEFAULT_ENDPOINT + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback - # Check the case client_cert_source and ADC client cert are not provided. - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} - ): - with mock.patch.object(transport_class, "__init__") as patched: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None - ): - with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - is_mtls_mock.return_value = False patched.return_value = None client = client_class() patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_ENDPOINT, + host=expected_host, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + @pytest.mark.parametrize( "client_class,transport_class,transport_name", @@ -397,7 +402,7 @@ def test_subscriber_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -427,7 +432,7 @@ def test_subscriber_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -446,7 +451,7 @@ def test_subscriber_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -682,6 +687,51 @@ def test_subscriber_service_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class", + [ + transports.SubscriberServiceGrpcTransport, + transports.SubscriberServiceGrpcAsyncIOTransport, + ], +) +def test_subscriber_service_grpc_transport_client_cert_source_for_mtls(transport_class): + cred = credentials.AnonymousCredentials() + + # Check ssl_channel_credentials is used if provided. + with mock.patch.object(transport_class, "create_channel") as mock_create_channel: + mock_ssl_channel_creds = mock.Mock() + transport_class( + host="squid.clam.whelk", + credentials=cred, + ssl_channel_credentials=mock_ssl_channel_creds, + ) + mock_create_channel.assert_called_once_with( + "squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ssl_credentials=mock_ssl_channel_creds, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls + # is used. + with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()): + with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred: + transport_class( + credentials=cred, + client_cert_source_for_mtls=client_cert_source_callback, + ) + expected_cert, expected_key = client_cert_source_callback() + mock_ssl_cred.assert_called_once_with( + certificate_chain=expected_cert, private_key=expected_key + ) + + def test_subscriber_service_host_no_port(): client = SubscriberServiceClient( credentials=credentials.AnonymousCredentials(), @@ -703,7 +753,7 @@ def test_subscriber_service_host_with_port(): def test_subscriber_service_grpc_transport_channel(): - channel = grpc.insecure_channel("http://localhost/") + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.SubscriberServiceGrpcTransport( @@ -715,7 +765,7 @@ def test_subscriber_service_grpc_transport_channel(): def test_subscriber_service_grpc_asyncio_transport_channel(): - channel = aio.insecure_channel("http://localhost/") + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.SubscriberServiceGrpcAsyncIOTransport( @@ -726,6 +776,8 @@ def test_subscriber_service_grpc_asyncio_transport_channel(): assert transport._ssl_channel_credentials == None +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ @@ -740,7 +792,7 @@ def test_subscriber_service_transport_channel_mtls_with_client_cert_source( "grpc.ssl_channel_credentials", autospec=True ) as grpc_ssl_channel_cred: with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_ssl_cred = mock.Mock() grpc_ssl_channel_cred.return_value = mock_ssl_cred @@ -769,11 +821,17 @@ def test_subscriber_service_transport_channel_mtls_with_client_cert_source( scopes=("https://www.googleapis.com/auth/cloud-platform",), ssl_credentials=mock_ssl_cred, quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) assert transport.grpc_channel == mock_grpc_channel assert transport._ssl_channel_credentials == mock_ssl_cred +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ @@ -789,7 +847,7 @@ def test_subscriber_service_transport_channel_mtls_with_adc(transport_class): ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_grpc_channel = mock.Mock() grpc_create_channel.return_value = mock_grpc_channel @@ -810,6 +868,10 @@ def test_subscriber_service_transport_channel_mtls_with_adc(transport_class): scopes=("https://www.googleapis.com/auth/cloud-platform",), ssl_credentials=mock_ssl_cred, quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) assert transport.grpc_channel == mock_grpc_channel diff --git a/tests/unit/gapic/pubsublite_v1/test_topic_stats_service.py b/tests/unit/gapic/pubsublite_v1/test_topic_stats_service.py index cd0f54e1..5c53bbb8 100644 --- a/tests/unit/gapic/pubsublite_v1/test_topic_stats_service.py +++ b/tests/unit/gapic/pubsublite_v1/test_topic_stats_service.py @@ -91,7 +91,24 @@ def test__get_default_mtls_endpoint(): @pytest.mark.parametrize( - "client_class", [TopicStatsServiceClient, TopicStatsServiceAsyncClient] + "client_class", [TopicStatsServiceClient, TopicStatsServiceAsyncClient,] +) +def test_topic_stats_service_client_from_service_account_info(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_info" + ) as factory: + factory.return_value = creds + info = {"valid": True} + client = client_class.from_service_account_info(info) + assert client.transport._credentials == creds + assert isinstance(client, client_class) + + assert client.transport._host == "pubsublite.googleapis.com:443" + + +@pytest.mark.parametrize( + "client_class", [TopicStatsServiceClient, TopicStatsServiceAsyncClient,] ) def test_topic_stats_service_client_from_service_account_file(client_class): creds = credentials.AnonymousCredentials() @@ -101,16 +118,21 @@ def test_topic_stats_service_client_from_service_account_file(client_class): factory.return_value = creds client = client_class.from_service_account_file("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) client = client_class.from_service_account_json("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "pubsublite.googleapis.com:443" def test_topic_stats_service_client_get_transport_class(): transport = TopicStatsServiceClient.get_transport_class() - assert transport == transports.TopicStatsServiceGrpcTransport + available_transports = [ + transports.TopicStatsServiceGrpcTransport, + ] + assert transport in available_transports transport = TopicStatsServiceClient.get_transport_class("grpc") assert transport == transports.TopicStatsServiceGrpcTransport @@ -161,7 +183,7 @@ def test_topic_stats_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -177,7 +199,7 @@ def test_topic_stats_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -193,7 +215,7 @@ def test_topic_stats_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -221,7 +243,7 @@ def test_topic_stats_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id="octopus", client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -282,29 +304,25 @@ def test_topic_stats_service_client_mtls_env_auto( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - ssl_channel_creds = mock.Mock() - with mock.patch( - "grpc.ssl_channel_credentials", return_value=ssl_channel_creds - ): - patched.return_value = None - client = client_class(client_options=options) + patched.return_value = None + client = client_class(client_options=options) - if use_client_cert_env == "false": - expected_ssl_channel_creds = None - expected_host = client.DEFAULT_ENDPOINT - else: - expected_ssl_channel_creds = ssl_channel_creds - expected_host = client.DEFAULT_MTLS_ENDPOINT + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) # Check the case ADC client cert is provided. Whether client cert is used depends on # GOOGLE_API_USE_CLIENT_CERTIFICATE value. @@ -313,66 +331,53 @@ def test_topic_stats_service_client_mtls_env_auto( ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, ): with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.ssl_credentials", - new_callable=mock.PropertyMock, - ) as ssl_credentials_mock: - if use_client_cert_env == "false": - is_mtls_mock.return_value = False - ssl_credentials_mock.return_value = None - expected_host = client.DEFAULT_ENDPOINT - expected_ssl_channel_creds = None - else: - is_mtls_mock.return_value = True - ssl_credentials_mock.return_value = mock.Mock() - expected_host = client.DEFAULT_MTLS_ENDPOINT - expected_ssl_channel_creds = ( - ssl_credentials_mock.return_value - ) - - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client.DEFAULT_ENDPOINT + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback - # Check the case client_cert_source and ADC client cert are not provided. - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} - ): - with mock.patch.object(transport_class, "__init__") as patched: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None - ): - with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - is_mtls_mock.return_value = False patched.return_value = None client = client_class() patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_ENDPOINT, + host=expected_host, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + @pytest.mark.parametrize( "client_class,transport_class,transport_name", @@ -398,7 +403,7 @@ def test_topic_stats_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -428,7 +433,7 @@ def test_topic_stats_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -447,7 +452,7 @@ def test_topic_stats_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -494,6 +499,24 @@ def test_compute_message_stats_from_dict(): test_compute_message_stats(request_type=dict) +def test_compute_message_stats_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = TopicStatsServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.compute_message_stats), "__call__" + ) as call: + client.compute_message_stats() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == topic_stats.ComputeMessageStatsRequest() + + @pytest.mark.asyncio async def test_compute_message_stats_async( transport: str = "grpc_asyncio", request_type=topic_stats.ComputeMessageStatsRequest @@ -595,6 +618,154 @@ async def test_compute_message_stats_field_headers_async(): assert ("x-goog-request-params", "topic=topic/value",) in kw["metadata"] +def test_compute_head_cursor( + transport: str = "grpc", request_type=topic_stats.ComputeHeadCursorRequest +): + client = TopicStatsServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.compute_head_cursor), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = topic_stats.ComputeHeadCursorResponse() + + response = client.compute_head_cursor(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == topic_stats.ComputeHeadCursorRequest() + + # Establish that the response is the type that we expect. + + assert isinstance(response, topic_stats.ComputeHeadCursorResponse) + + +def test_compute_head_cursor_from_dict(): + test_compute_head_cursor(request_type=dict) + + +def test_compute_head_cursor_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = TopicStatsServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.compute_head_cursor), "__call__" + ) as call: + client.compute_head_cursor() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == topic_stats.ComputeHeadCursorRequest() + + +@pytest.mark.asyncio +async def test_compute_head_cursor_async( + transport: str = "grpc_asyncio", request_type=topic_stats.ComputeHeadCursorRequest +): + client = TopicStatsServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.compute_head_cursor), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + topic_stats.ComputeHeadCursorResponse() + ) + + response = await client.compute_head_cursor(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == topic_stats.ComputeHeadCursorRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, topic_stats.ComputeHeadCursorResponse) + + +@pytest.mark.asyncio +async def test_compute_head_cursor_async_from_dict(): + await test_compute_head_cursor_async(request_type=dict) + + +def test_compute_head_cursor_field_headers(): + client = TopicStatsServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = topic_stats.ComputeHeadCursorRequest() + request.topic = "topic/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.compute_head_cursor), "__call__" + ) as call: + call.return_value = topic_stats.ComputeHeadCursorResponse() + + client.compute_head_cursor(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "topic=topic/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_compute_head_cursor_field_headers_async(): + client = TopicStatsServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), + ) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = topic_stats.ComputeHeadCursorRequest() + request.topic = "topic/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.compute_head_cursor), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + topic_stats.ComputeHeadCursorResponse() + ) + + await client.compute_head_cursor(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "topic=topic/value",) in kw["metadata"] + + def test_credentials_transport_error(): # It is an error to provide credentials and a transport instance. transport = transports.TopicStatsServiceGrpcTransport( @@ -691,7 +862,10 @@ def test_topic_stats_service_base_transport(): # Every method on the transport should just blindly # raise NotImplementedError. - methods = ("compute_message_stats",) + methods = ( + "compute_message_stats", + "compute_head_cursor", + ) for method in methods: with pytest.raises(NotImplementedError): getattr(transport, method)(request=object()) @@ -752,6 +926,53 @@ def test_topic_stats_service_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class", + [ + transports.TopicStatsServiceGrpcTransport, + transports.TopicStatsServiceGrpcAsyncIOTransport, + ], +) +def test_topic_stats_service_grpc_transport_client_cert_source_for_mtls( + transport_class, +): + cred = credentials.AnonymousCredentials() + + # Check ssl_channel_credentials is used if provided. + with mock.patch.object(transport_class, "create_channel") as mock_create_channel: + mock_ssl_channel_creds = mock.Mock() + transport_class( + host="squid.clam.whelk", + credentials=cred, + ssl_channel_credentials=mock_ssl_channel_creds, + ) + mock_create_channel.assert_called_once_with( + "squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ssl_credentials=mock_ssl_channel_creds, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls + # is used. + with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()): + with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred: + transport_class( + credentials=cred, + client_cert_source_for_mtls=client_cert_source_callback, + ) + expected_cert, expected_key = client_cert_source_callback() + mock_ssl_cred.assert_called_once_with( + certificate_chain=expected_cert, private_key=expected_key + ) + + def test_topic_stats_service_host_no_port(): client = TopicStatsServiceClient( credentials=credentials.AnonymousCredentials(), @@ -773,7 +994,7 @@ def test_topic_stats_service_host_with_port(): def test_topic_stats_service_grpc_transport_channel(): - channel = grpc.insecure_channel("http://localhost/") + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.TopicStatsServiceGrpcTransport( @@ -785,7 +1006,7 @@ def test_topic_stats_service_grpc_transport_channel(): def test_topic_stats_service_grpc_asyncio_transport_channel(): - channel = aio.insecure_channel("http://localhost/") + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.TopicStatsServiceGrpcAsyncIOTransport( @@ -796,6 +1017,8 @@ def test_topic_stats_service_grpc_asyncio_transport_channel(): assert transport._ssl_channel_credentials == None +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ @@ -810,7 +1033,7 @@ def test_topic_stats_service_transport_channel_mtls_with_client_cert_source( "grpc.ssl_channel_credentials", autospec=True ) as grpc_ssl_channel_cred: with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_ssl_cred = mock.Mock() grpc_ssl_channel_cred.return_value = mock_ssl_cred @@ -839,11 +1062,17 @@ def test_topic_stats_service_transport_channel_mtls_with_client_cert_source( scopes=("https://www.googleapis.com/auth/cloud-platform",), ssl_credentials=mock_ssl_cred, quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) assert transport.grpc_channel == mock_grpc_channel assert transport._ssl_channel_credentials == mock_ssl_cred +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ @@ -859,7 +1088,7 @@ def test_topic_stats_service_transport_channel_mtls_with_adc(transport_class): ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_grpc_channel = mock.Mock() grpc_create_channel.return_value = mock_grpc_channel @@ -880,6 +1109,10 @@ def test_topic_stats_service_transport_channel_mtls_with_adc(transport_class): scopes=("https://www.googleapis.com/auth/cloud-platform",), ssl_credentials=mock_ssl_cred, quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], ) assert transport.grpc_channel == mock_grpc_channel From 62e376c46bf184e3c99a6ef254a6670d56c68e66 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Mon, 15 Mar 2021 17:47:16 +0100 Subject: [PATCH 6/8] chore(deps): update precommit hook pycqa/flake8 to v3.9.0 (#104) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a9024b15..32302e48 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,6 +12,6 @@ repos: hooks: - id: black - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + rev: 3.9.0 hooks: - id: flake8 From 4d03d3a8ae8089fea87f5acd02a170697fa136fc Mon Sep 17 00:00:00 2001 From: hannahrogers-google <52459909+hannahrogers-google@users.noreply.github.com> Date: Mon, 22 Mar 2021 15:34:33 -0700 Subject: [PATCH 7/8] feat: adding ability to create subscriptions at HEAD (#106) * feat: adding ability to create subscriptions at head * fix: lint errors * fix: remove absl dependency * fix: lint * feat: use default keyword args * fix: rename offset location to backlog location * fix: broken samples --- google/cloud/pubsublite/admin_client.py | 9 +++++-- .../pubsublite/admin_client_interface.py | 11 +++++++-- .../internal/wire/admin_client_impl.py | 16 +++++++++---- google/cloud/pubsublite/types/__init__.py | 2 ++ .../pubsublite/types/backlog_location.py | 24 +++++++++++++++++++ 5 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 google/cloud/pubsublite/types/backlog_location.py diff --git a/google/cloud/pubsublite/admin_client.py b/google/cloud/pubsublite/admin_client.py index d5e82197..7d7a8dd5 100644 --- a/google/cloud/pubsublite/admin_client.py +++ b/google/cloud/pubsublite/admin_client.py @@ -30,6 +30,7 @@ SubscriptionPath, LocationPath, TopicPath, + BacklogLocation, ) from google.cloud.pubsublite_v1 import AdminServiceClient, Subscription, Topic @@ -101,8 +102,12 @@ def list_topic_subscriptions(self, topic_path: TopicPath): return self._impl.list_topic_subscriptions(topic_path) @overrides - def create_subscription(self, subscription: Subscription) -> Subscription: - return self._impl.create_subscription(subscription) + def create_subscription( + self, + subscription: Subscription, + starting_offset: BacklogLocation = BacklogLocation.END, + ) -> Subscription: + return self._impl.create_subscription(subscription, starting_offset) @overrides def get_subscription(self, subscription_path: SubscriptionPath) -> Subscription: diff --git a/google/cloud/pubsublite/admin_client_interface.py b/google/cloud/pubsublite/admin_client_interface.py index 2c59a102..38b90ba3 100644 --- a/google/cloud/pubsublite/admin_client_interface.py +++ b/google/cloud/pubsublite/admin_client_interface.py @@ -20,6 +20,7 @@ TopicPath, LocationPath, SubscriptionPath, + BacklogLocation, ) from google.cloud.pubsublite_v1 import Topic, Subscription from google.protobuf.field_mask_pb2 import FieldMask @@ -63,8 +64,14 @@ def list_topic_subscriptions(self, topic_path: TopicPath): """List the subscriptions that exist for a given topic.""" @abstractmethod - def create_subscription(self, subscription: Subscription) -> Subscription: - """Create a subscription, returns the created subscription.""" + def create_subscription( + self, + subscription: Subscription, + starting_offset: BacklogLocation = BacklogLocation.END, + ) -> Subscription: + """Create a subscription, returns the created subscription. By default + a subscription will only receive messages published after the + subscription was created.""" @abstractmethod def get_subscription(self, subscription_path: SubscriptionPath) -> Subscription: diff --git a/google/cloud/pubsublite/internal/wire/admin_client_impl.py b/google/cloud/pubsublite/internal/wire/admin_client_impl.py index 234916b9..ab5acba6 100644 --- a/google/cloud/pubsublite/internal/wire/admin_client_impl.py +++ b/google/cloud/pubsublite/internal/wire/admin_client_impl.py @@ -22,6 +22,7 @@ SubscriptionPath, LocationPath, TopicPath, + BacklogLocation, ) from google.cloud.pubsublite_v1 import ( Subscription, @@ -72,12 +73,19 @@ def list_topic_subscriptions(self, topic_path: TopicPath): ] return [SubscriptionPath.parse(x) for x in subscription_strings] - def create_subscription(self, subscription: Subscription) -> Subscription: + def create_subscription( + self, + subscription: Subscription, + starting_offset: BacklogLocation = BacklogLocation.END, + ) -> Subscription: path = SubscriptionPath.parse(subscription.name) return self._underlying.create_subscription( - parent=str(path.to_location_path()), - subscription=subscription, - subscription_id=path.name, + request={ + "parent": str(path.to_location_path()), + "subscription": subscription, + "subscription_id": path.name, + "skip_backlog": (starting_offset == BacklogLocation.END), + } ) def get_subscription(self, subscription_path: SubscriptionPath) -> Subscription: diff --git a/google/cloud/pubsublite/types/__init__.py b/google/cloud/pubsublite/types/__init__.py index 6304fd3c..fbf5cc00 100644 --- a/google/cloud/pubsublite/types/__init__.py +++ b/google/cloud/pubsublite/types/__init__.py @@ -18,6 +18,7 @@ from .paths import LocationPath, TopicPath, SubscriptionPath from .message_metadata import MessageMetadata from .flow_control_settings import FlowControlSettings, DISABLED_FLOW_CONTROL +from .backlog_location import BacklogLocation __all__ = ( "CloudRegion", @@ -28,4 +29,5 @@ "MessageMetadata", "SubscriptionPath", "TopicPath", + "BacklogLocation", ) diff --git a/google/cloud/pubsublite/types/backlog_location.py b/google/cloud/pubsublite/types/backlog_location.py new file mode 100644 index 00000000..42e64218 --- /dev/null +++ b/google/cloud/pubsublite/types/backlog_location.py @@ -0,0 +1,24 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import enum + + +class BacklogLocation(enum.Enum): + """A location with respect to the message backlog. BEGINNING refers to the + location of the oldest retained message. END refers to the location past + all currently published messages, skipping the entire message backlog.""" + + BEGINNING = 0 + END = 1 From e0dd51e4b92cee6ebc93c8588c52f13e618ae68b Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 23 Mar 2021 12:43:10 -0700 Subject: [PATCH 8/8] chore: release 0.4.0 (#107) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 7 +++++++ setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a7b44dc..56c1acff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.4.0](https://www.github.com/googleapis/python-pubsublite/compare/v0.3.0...v0.4.0) (2021-03-22) + + +### Features + +* adding ability to create subscriptions at HEAD ([#106](https://www.github.com/googleapis/python-pubsublite/issues/106)) ([4d03d3a](https://www.github.com/googleapis/python-pubsublite/commit/4d03d3a8ae8089fea87f5acd02a170697fa136fc)) + ## [0.3.0](https://www.github.com/googleapis/python-pubsublite/compare/v0.2.0...v0.3.0) (2021-03-09) diff --git a/setup.py b/setup.py index bdd97869..1829756b 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ import os import setuptools # type: ignore -version = "0.3.0" +version = "0.4.0" package_root = os.path.abspath(os.path.dirname(__file__))